4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "Task/TaskFileSeeYou.hpp"
25 #include "Util/Macros.hpp"
26 #include "IO/FileLineReader.hpp"
27 #include "Engine/Waypoint/Waypoints.hpp"
28 #include "Waypoint/WaypointReaderSeeYou.hpp"
29 #include "Task/ObservationZones/LineSectorZone.hpp"
30 #include "Task/ObservationZones/AnnularSectorZone.hpp"
31 #include "Task/ObservationZones/KeyholeZone.hpp"
32 #include "Engine/Task/Ordered/OrderedTask.hpp"
33 #include "Engine/Task/Ordered/Points/StartPoint.hpp"
34 #include "Engine/Task/Ordered/Points/FinishPoint.hpp"
35 #include "Engine/Task/Ordered/Points/AATPoint.hpp"
36 #include "Engine/Task/Ordered/Points/ASTPoint.hpp"
37 #include "Engine/Task/Factory/AbstractTaskFactory.hpp"
38 #include "Operation/Operation.hpp"
39 #include "Units/System.hpp"
41 struct SeeYouTaskInformation
{
42 /** True = RT, False = AAT */
44 /** AAT task time in seconds */
46 /** MaxAltStart in meters */
47 fixed max_start_altitude
;
49 SeeYouTaskInformation():
50 wp_dis(true), task_time(fixed(0)), max_start_altitude(fixed(0)) {}
53 struct SeeYouTurnpointInformation
{
54 /** CUP file contained info for this OZ */
68 fixed radius1
, radius2
, max_altitude
;
69 Angle angle1
, angle2
, angle12
;
71 SeeYouTurnpointInformation():
72 valid(false), style(SYMMETRICAL
), is_line(false), reduce(false),
73 radius1(fixed(500)), radius2(fixed(500)),
74 max_altitude(fixed(0)),
75 angle1(Angle::Zero()),
76 angle2(Angle::Zero()),
77 angle12(Angle::Zero()) {}
81 ParseTaskTime(const TCHAR
* str
)
83 int hh
= 0, mm
= 0, ss
= 0;
85 hh
= _tcstol(str
, &end
, 10);
86 if (str
!= end
&& _tcslen(str
) > 3 && str
[2] == _T(':')) {
87 mm
= _tcstol(str
+ 3, &end
, 10);
88 if (str
!= end
&& _tcslen(str
+ 3) > 3 && str
[5] == _T(':'))
89 ss
= _tcstol(str
+ 6, NULL
, 10);
91 return fixed(ss
+ mm
* 60 + hh
* 3600);
94 static SeeYouTurnpointInformation::Style
95 ParseStyle(const TCHAR
* str
)
99 style
= _tcstol(str
, &end
, 10);
103 return (SeeYouTurnpointInformation::Style
)style
;
107 ParseAngle(const TCHAR
* str
)
111 angle
= _tcstol(str
, &end
, 10);
115 return Angle::Degrees(angle
);
119 ParseRadius(const TCHAR
* str
)
123 radius
= _tcstol(str
, &end
, 10);
127 return fixed(radius
);
131 ParseMaxAlt(const TCHAR
* str
)
133 fixed maxalt
= fixed(0);
135 maxalt
= fixed(_tcstod(str
, &end
));
139 if (_tcslen(end
) >= 2 && end
[0] == _T('f') && end
[1] == _T('t'))
140 maxalt
= Units::ToSysUnit(maxalt
, Unit::FEET
);
146 * Parses the Options parameters line from See You task file for one task
147 * @param task_info Updated with Options
148 * @param params Input array of parameters preparsed from See You task file
149 * @param n_params number parameters in the line
152 ParseOptions(SeeYouTaskInformation
*task_info
, const TCHAR
*params
[],
153 const size_t n_params
)
155 // Iterate through available task options
156 for (unsigned i
= 1; i
< n_params
; i
++) {
157 if (_tcsncmp(params
[i
], _T("WpDis"), 5) == 0) {
158 // Parse WpDis option
159 if (_tcslen(params
[i
]) > 6 &&
160 _tcsncmp(params
[i
] + 6, _T("False"), 5) == 0)
161 task_info
->wp_dis
= false;
162 } else if (_tcsncmp(params
[i
], _T("TaskTime"), 8) == 0) {
163 // Parse TaskTime option
164 if (_tcslen(params
[i
]) > 9)
165 task_info
->task_time
= ParseTaskTime(params
[i
] + 9);
171 * Parses one ObsZone line from the See You task file
172 * @param turnpoint_infos Updated with the OZ info
173 * @param params Input array of parameters preparsed from See You task file
174 * @param n_params Number parameters in the line
175 * @return OZ index from CU (0 to n-1) or -1 if no OZ found
178 ParseOZs(SeeYouTurnpointInformation turnpoint_infos
[], const TCHAR
*params
[],
183 const int oz_index
= _tcstol(params
[0] + 8, &end
, 10);
184 if (params
[0] + 8 == end
|| oz_index
>= 30)
187 turnpoint_infos
[oz_index
].valid
= true;
188 // Iterate through available OZ options
189 for (unsigned i
= 1; i
< n_params
; i
++) {
190 const TCHAR
*pair
= params
[i
];
191 SeeYouTurnpointInformation
&tp_info
= turnpoint_infos
[oz_index
];
193 if (_tcsncmp(pair
, _T("style"), 5) == 0) {
194 if (_tcslen(pair
) > 6)
195 tp_info
.style
= ParseStyle(pair
+ 6);
196 } else if (_tcsncmp(pair
, _T("R1="), 3) == 0) {
197 if (_tcslen(pair
) > 3)
198 tp_info
.radius1
= ParseRadius(pair
+ 3);
199 } else if (_tcsncmp(pair
, _T("A1="), 3) == 0) {
200 if (_tcslen(pair
) > 3)
201 tp_info
.angle1
= ParseAngle(pair
+ 3);
202 } else if (_tcsncmp(pair
, _T("R2="), 3) == 0) {
203 if (_tcslen(pair
) > 3)
204 tp_info
.radius2
= ParseRadius(pair
+ 3);
205 } else if (_tcsncmp(pair
, _T("A2="), 3) == 0) {
206 if (_tcslen(pair
) > 3)
207 tp_info
.angle2
= ParseAngle(pair
+ 3);
208 } else if (_tcsncmp(pair
, _T("A12="), 4) == 0) {
209 if (_tcslen(pair
) > 3)
210 tp_info
.angle12
= ParseAngle(pair
+ 4);
211 } else if (_tcsncmp(pair
, _T("max_altitude="), 7) == 0) {
212 if (_tcslen(pair
) > 7)
213 tp_info
.max_altitude
= ParseMaxAlt(pair
+ 7);
214 } else if (_tcsncmp(pair
, _T("is_line"), 4) == 0) {
215 if (_tcslen(pair
) > 5 && pair
[5] == _T('1'))
216 tp_info
.is_line
= true;
217 } else if (_tcsncmp(pair
, _T("reduce"), 6) == 0) {
218 if (_tcslen(pair
) > 7 && pair
[7] == _T('1'))
219 tp_info
.reduce
= true;
226 * Parses Options and OZs from See You task file
227 * @param reader. Points to first line of task after task "Waypoint list" line
228 * @param task_info Loads this with CU task options info
229 * @param turnpoint_infos Loads this with CU task tp info
232 ParseCUTaskDetails(FileLineReader
&reader
, SeeYouTaskInformation
*task_info
,
233 SeeYouTurnpointInformation turnpoint_infos
[])
235 // Read options/observation zones
236 TCHAR params_buffer
[1024];
237 const TCHAR
*params
[20];
240 const unsigned int max_params
= ARRAY_SIZE(params
);
241 while ((line
= reader
.ReadLine()) != NULL
&&
242 line
[0] != _T('\"') && line
[0] != _T(',')) {
243 const size_t n_params
= WaypointReaderBase::
244 ExtractParameters(line
, params_buffer
, params
, max_params
, true);
246 if (_tcscmp(params
[0], _T("Options")) == 0) {
247 // Options line found
248 ParseOptions(task_info
, params
, n_params
);
250 } else if (_tcsncmp(params
[0], _T("ObsZone"), 7) == 0) {
251 // Observation zone line found
252 if (_tcslen(params
[0]) <= 8)
255 TPIndex
= ParseOZs(turnpoint_infos
, params
, n_params
);
257 task_info
->max_start_altitude
= turnpoint_infos
[TPIndex
].max_altitude
;
262 static bool isKeyhole(const SeeYouTurnpointInformation
&turnpoint_infos
)
264 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(45)) < fixed(2) &&
265 fabs(turnpoint_infos
.radius1
- fixed(10000)) < fixed(2) &&
266 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
267 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
270 static bool isBGAFixedCourseZone(const SeeYouTurnpointInformation
&turnpoint_infos
)
272 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(45)) < fixed(2) &&
273 fabs(turnpoint_infos
.radius1
- fixed(20000)) < fixed(2) &&
274 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
275 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
278 static bool isBGAEnhancedOptionZone(const SeeYouTurnpointInformation
281 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(90)) < fixed(2) &&
282 fabs(turnpoint_infos
.radius1
- fixed(10000)) < fixed(2) &&
283 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
284 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
288 * Creates the correct XCSoar OZ type from the See You OZ options for the point
289 * Note: there are several rules enforced here related to the combinations
290 * and types of Zones supported by XCSoar. When XCSoar adds more zone types,
291 * the logic below will need to be updated.
292 * @param turnpoint_infos Contains the See You turnpoint and OZ info
293 * @param current point position
294 * @param number wps in task
295 * @param array of wps for each point in task
296 * @param factType The XCSoar factory type
297 * @return the XCSoar OZ
299 static ObservationZonePoint
*
300 CreateOZ(const SeeYouTurnpointInformation
&turnpoint_infos
,
301 unsigned pos
, unsigned size
, const Waypoint
*wps
[],
302 TaskFactoryType factType
)
304 ObservationZonePoint
* oz
= NULL
;
305 const bool is_intermediate
= (pos
> 0) && (pos
< (size
- 1));
306 const Waypoint
*wp
= wps
[pos
];
308 if (!turnpoint_infos
.valid
)
311 if (factType
== TaskFactoryType::RACING
&&
312 is_intermediate
&& isKeyhole(turnpoint_infos
))
313 oz
= KeyholeZone::CreateDAeCKeyholeZone(wp
->location
);
315 else if (factType
== TaskFactoryType::RACING
&&
316 is_intermediate
&& isBGAEnhancedOptionZone(turnpoint_infos
))
317 oz
= KeyholeZone::CreateBGAEnhancedOptionZone(wp
->location
);
319 else if (factType
== TaskFactoryType::RACING
&&
320 is_intermediate
&& isBGAFixedCourseZone(turnpoint_infos
))
321 oz
= KeyholeZone::CreateBGAFixedCourseZone(wp
->location
);
323 else if (!is_intermediate
&& turnpoint_infos
.is_line
) // special case "is_line"
324 oz
= new LineSectorZone(wp
->location
, turnpoint_infos
.radius1
);
326 // special case "Cylinder"
327 else if (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(180)) < fixed(1) )
328 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
330 else if (factType
== TaskFactoryType::RACING
) {
332 // XCSoar does not support fixed sectors for RT
333 if (turnpoint_infos
.style
== SeeYouTurnpointInformation::FIXED
)
334 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
336 oz
= SymmetricSectorZone::CreateFAISectorZone(wp
->location
,
339 } else if (is_intermediate
) { //AAT intermediate point
341 assert(wps
[pos
+ 1]);
342 assert(wps
[pos
- 1]);
344 switch (turnpoint_infos
.style
) {
345 case SeeYouTurnpointInformation::FIXED
: {
346 A12adj
= turnpoint_infos
.angle12
.Reciprocal();
349 case SeeYouTurnpointInformation::SYMMETRICAL
: {
350 const Angle ap
= wps
[pos
- 1]->location
.Bearing(wp
->location
);
351 const Angle an
= wps
[pos
+ 1]->location
.Bearing(wp
->location
);
352 A12adj
= ap
.HalfAngle(an
).Reciprocal();
356 case SeeYouTurnpointInformation::TO_NEXT_POINT
: {
357 A12adj
= wps
[pos
+ 1]->location
.Bearing(wp
->location
);
360 case SeeYouTurnpointInformation::TO_PREVIOUS_POINT
: {
361 A12adj
= wps
[pos
- 1]->location
.Bearing(wp
->location
);
364 case SeeYouTurnpointInformation::TO_START_POINT
: {
365 A12adj
= wps
[0]->location
.Bearing(wp
->location
);
370 const Angle RadialStart
= (A12adj
- turnpoint_infos
.angle1
).AsBearing();
371 const Angle RadialEnd
= (A12adj
+ turnpoint_infos
.angle1
).AsBearing();
373 if (turnpoint_infos
.radius2
> fixed(0) &&
374 (turnpoint_infos
.angle2
.AsBearing().Degrees()) < fixed(1)) {
375 oz
= new AnnularSectorZone(wp
->location
, turnpoint_infos
.radius1
,
376 RadialStart
, RadialEnd
, turnpoint_infos
.radius2
);
378 oz
= new SectorZone(wp
->location
, turnpoint_infos
.radius1
,
379 RadialStart
, RadialEnd
);
382 } else { // catch-all
383 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
390 * Creates the XCSoar turnpoint from the See You parameters for the point
391 * @param pos The position of the point in the XCSoar task
392 * @param n_waypoints Number of points in the XCSoar task
393 * @param wp The waypoint
394 * @param fact The XCSoar factory
395 * @param oz The XCSoar OZ for the point
396 * @param factType The XCSoar factory type
399 static OrderedTaskPoint
*
400 CreatePoint(unsigned pos
, unsigned n_waypoints
, const Waypoint
*wp
,
401 AbstractTaskFactory
& fact
, ObservationZonePoint
* oz
,
402 const TaskFactoryType factType
)
404 OrderedTaskPoint
*pt
= NULL
;
407 pt
= (oz
? fact
.CreateStart(oz
, *wp
) : fact
.CreateStart(*wp
));
409 else if (pos
== n_waypoints
- 1)
410 pt
= (oz
? fact
.CreateFinish(oz
, *wp
) : fact
.CreateFinish(*wp
));
412 else if (factType
== TaskFactoryType::RACING
)
413 pt
= (oz
? fact
.CreateASTPoint(oz
, *wp
) : fact
.CreateIntermediate(*wp
));
416 pt
= (oz
? fact
.CreateAATPoint(oz
, *wp
) : fact
.CreateIntermediate(*wp
));
422 AdvanceReaderToTask(FileLineReader
&reader
, const unsigned index
)
424 // Skip lines until n-th task
426 bool in_task_section
= false;
428 for (unsigned i
= 0; (line
= reader
.ReadLine()) != NULL
; i
++) {
429 if (in_task_section
) {
430 if (line
[0] == _T('\"') || line
[0] == _T(',')) {
436 } else if (StringIsEqualIgnoreCase(line
, _T("-----Related Tasks-----"))) {
437 in_task_section
= true;
444 TaskFileSeeYou::GetTask(const TaskBehaviour
&task_behaviour
,
445 const Waypoints
*waypoints
, unsigned index
) const
447 // Create FileReader for reading the task
448 FileLineReader
reader(path
, ConvertLineReader::AUTO
);
452 // Read waypoints from the CUP file
453 Waypoints file_waypoints
;
455 WaypointReaderSeeYou
waypoint_file(0);
456 NullOperationEnvironment operation
;
457 waypoint_file
.Parse(file_waypoints
, reader
, operation
);
459 file_waypoints
.Optimise();
461 if (!reader
.Rewind())
464 TCHAR
*line
= AdvanceReaderToTask(reader
, index
);
468 // Read waypoint list
469 // e.g. "Club day 4 Racing task","085PRI","083BOJ","170D_K","065SKY","0844YY", "0844YY"
470 // TASK NAME , TAKEOFF, START , TP1 , TP2 , FINISH , LANDING
471 TCHAR waypoints_buffer
[1024];
472 const TCHAR
*wps
[30];
473 size_t n_waypoints
= WaypointReaderBase::
474 ExtractParameters(line
, waypoints_buffer
, wps
, 30, true, _T('"')) - 3;
476 SeeYouTaskInformation task_info
;
477 SeeYouTurnpointInformation turnpoint_infos
[30];
478 const Waypoint
*waypoints_in_task
[30];
480 ParseCUTaskDetails(reader
, &task_info
, turnpoint_infos
);
482 OrderedTask
*task
= new OrderedTask(task_behaviour
);
483 task
->SetFactory(task_info
.wp_dis
?
484 TaskFactoryType::RACING
: TaskFactoryType::AAT
);
485 AbstractTaskFactory
& fact
= task
->GetFactory();
486 const TaskFactoryType factType
= task
->GetFactoryType();
488 OrderedTaskBehaviour beh
= task
->GetOrderedTaskBehaviour();
489 if (factType
== TaskFactoryType::AAT
) {
490 beh
.aat_min_time
= task_info
.task_time
;
492 if (factType
== TaskFactoryType::AAT
||
493 factType
== TaskFactoryType::RACING
) {
494 beh
.start_constraints
.max_height
= (unsigned)task_info
.max_start_altitude
;
495 beh
.start_constraints
.max_height_ref
= AltitudeReference::MSL
;
497 task
->SetOrderedTaskBehaviour(beh
);
499 // mark task waypoints. Skip takeoff and landing point
500 for (unsigned i
= 0; i
< n_waypoints
; i
++) {
501 const Waypoint
* file_wp
= file_waypoints
.LookupName(wps
[i
+ 2]);
505 // Try to find waypoint by name
506 const Waypoint
* wp
= waypoints
->LookupName(file_wp
->name
);
508 // If waypoint by name found and closer than 10m to the original
510 wp
->location
.Distance(file_wp
->location
) <= fixed(10)) {
511 // Use this waypoint for the task
512 waypoints_in_task
[i
] = wp
;
516 // Try finding the closest waypoint to the original one
517 wp
= waypoints
->GetNearest(file_wp
->location
, fixed(10));
519 // If closest waypoint found and closer than 10m to the original
521 wp
->location
.Distance(file_wp
->location
) <= fixed(10)) {
522 // Use this waypoint for the task
523 waypoints_in_task
[i
] = wp
;
527 // Use the original waypoint
528 waypoints_in_task
[i
] = file_wp
;
531 //now create TPs and OZs
532 for (unsigned i
= 0; i
< n_waypoints
; i
++) {
534 ObservationZonePoint
* oz
= CreateOZ(turnpoint_infos
[i
], i
, n_waypoints
,
535 waypoints_in_task
, factType
);
536 assert(waypoints_in_task
[i
]);
537 OrderedTaskPoint
*pt
= CreatePoint(i
, n_waypoints
, waypoints_in_task
[i
],
541 fact
.Append(*pt
, false);
549 TaskFileSeeYou::Count()
551 // Reset internal task name memory
552 namesuffixes
.clear();
555 FileLineReader
reader(path
, ConvertLineReader::AUTO
);
560 bool in_task_section
= false;
562 while ((line
= reader
.ReadLine()) != NULL
) {
563 if (in_task_section
) {
564 // If the line starts with a string or "nothing" followed
565 // by a comma it is a new task definition line
566 if (line
[0] == _T('\"') || line
[0] == _T(',')) {
567 // If we still have space in the task name list
568 if (count
< namesuffixes
.capacity()) {
569 // If the task doesn't have a name inside the file
570 if (line
[0] == _T(','))
571 namesuffixes
.append(NULL
);
573 // Ignore starting quote (")
576 // Save pointer to first character
578 // Skip characters until next quote (") or end of string
579 while (line
[0] != _T('\"') && line
[0] != _T('\0'))
582 // Replace quote (") by end of string (null)
585 // Append task name to the list
586 if (_tcslen(name
) > 0)
587 namesuffixes
.append(_tcsdup(name
));
589 namesuffixes
.append(NULL
);
593 // Increase the task counter
596 } else if (StringIsEqualIgnoreCase(line
, _T("-----Related Tasks-----"))) {
597 // Found the marker -> all following lines are task lines
598 in_task_section
= true;
602 // Return number of tasks found in the CUP file