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/FAISectorZone.hpp"
32 #include "Task/ObservationZones/KeyholeZone.hpp"
33 #include "Task/ObservationZones/BGAEnhancedOptionZone.hpp"
34 #include "Task/ObservationZones/BGAFixedCourseZone.hpp"
35 #include "Engine/Task/Ordered/OrderedTask.hpp"
36 #include "Engine/Task/Ordered/Points/StartPoint.hpp"
37 #include "Engine/Task/Ordered/Points/FinishPoint.hpp"
38 #include "Engine/Task/Ordered/Points/AATPoint.hpp"
39 #include "Engine/Task/Ordered/Points/ASTPoint.hpp"
40 #include "Engine/Task/Factory/AbstractTaskFactory.hpp"
41 #include "Operation/Operation.hpp"
42 #include "Units/System.hpp"
44 struct SeeYouTaskInformation
{
45 /** True = RT, False = AAT */
47 /** AAT task time in seconds */
49 /** MaxAltStart in meters */
50 fixed max_start_altitude
;
52 SeeYouTaskInformation():
53 wp_dis(true), task_time(fixed(0)), max_start_altitude(fixed(0)) {}
56 struct SeeYouTurnpointInformation
{
57 /** CUP file contained info for this OZ */
71 fixed radius1
, radius2
, max_altitude
;
72 Angle angle1
, angle2
, angle12
;
74 SeeYouTurnpointInformation():
75 valid(false), style(SYMMETRICAL
), is_line(false), reduce(false),
76 radius1(fixed(500)), radius2(fixed(500)),
77 max_altitude(fixed(0)),
78 angle1(Angle::Zero()),
79 angle2(Angle::Zero()),
80 angle12(Angle::Zero()) {}
84 ParseTaskTime(const TCHAR
* str
)
86 int hh
= 0, mm
= 0, ss
= 0;
88 hh
= _tcstol(str
, &end
, 10);
89 if (str
!= end
&& _tcslen(str
) > 3 && str
[2] == _T(':')) {
90 mm
= _tcstol(str
+ 3, &end
, 10);
91 if (str
!= end
&& _tcslen(str
+ 3) > 3 && str
[5] == _T(':'))
92 ss
= _tcstol(str
+ 6, NULL
, 10);
94 return fixed(ss
+ mm
* 60 + hh
* 3600);
97 static SeeYouTurnpointInformation::Style
98 ParseStyle(const TCHAR
* str
)
102 style
= _tcstol(str
, &end
, 10);
106 return (SeeYouTurnpointInformation::Style
)style
;
110 ParseAngle(const TCHAR
* str
)
114 angle
= _tcstol(str
, &end
, 10);
118 return Angle::Degrees(angle
);
122 ParseRadius(const TCHAR
* str
)
126 radius
= _tcstol(str
, &end
, 10);
130 return fixed(radius
);
134 ParseMaxAlt(const TCHAR
* str
)
136 fixed maxalt
= fixed(0);
138 maxalt
= fixed(_tcstod(str
, &end
));
142 if (_tcslen(end
) >= 2 && end
[0] == _T('f') && end
[1] == _T('t'))
143 maxalt
= Units::ToSysUnit(maxalt
, Unit::FEET
);
149 * Parses the Options parameters line from See You task file for one task
150 * @param task_info Updated with Options
151 * @param params Input array of parameters preparsed from See You task file
152 * @param n_params number parameters in the line
155 ParseOptions(SeeYouTaskInformation
*task_info
, const TCHAR
*params
[],
156 const size_t n_params
)
158 // Iterate through available task options
159 for (unsigned i
= 1; i
< n_params
; i
++) {
160 if (_tcsncmp(params
[i
], _T("WpDis"), 5) == 0) {
161 // Parse WpDis option
162 if (_tcslen(params
[i
]) > 6 &&
163 _tcsncmp(params
[i
] + 6, _T("False"), 5) == 0)
164 task_info
->wp_dis
= false;
165 } else if (_tcsncmp(params
[i
], _T("TaskTime"), 8) == 0) {
166 // Parse TaskTime option
167 if (_tcslen(params
[i
]) > 9)
168 task_info
->task_time
= ParseTaskTime(params
[i
] + 9);
174 * Parses one ObsZone line from the See You task file
175 * @param turnpoint_infos Updated with the OZ info
176 * @param params Input array of parameters preparsed from See You task file
177 * @param n_params Number parameters in the line
178 * @return OZ index from CU (0 to n-1) or -1 if no OZ found
181 ParseOZs(SeeYouTurnpointInformation turnpoint_infos
[], const TCHAR
*params
[],
186 const int oz_index
= _tcstol(params
[0] + 8, &end
, 10);
187 if (params
[0] + 8 == end
|| oz_index
>= 30)
190 turnpoint_infos
[oz_index
].valid
= true;
191 // Iterate through available OZ options
192 for (unsigned i
= 1; i
< n_params
; i
++) {
193 const TCHAR
*pair
= params
[i
];
194 SeeYouTurnpointInformation
&tp_info
= turnpoint_infos
[oz_index
];
196 if (_tcsncmp(pair
, _T("style"), 5) == 0) {
197 if (_tcslen(pair
) > 6)
198 tp_info
.style
= ParseStyle(pair
+ 6);
199 } else if (_tcsncmp(pair
, _T("R1="), 3) == 0) {
200 if (_tcslen(pair
) > 3)
201 tp_info
.radius1
= ParseRadius(pair
+ 3);
202 } else if (_tcsncmp(pair
, _T("A1="), 3) == 0) {
203 if (_tcslen(pair
) > 3)
204 tp_info
.angle1
= ParseAngle(pair
+ 3);
205 } else if (_tcsncmp(pair
, _T("R2="), 3) == 0) {
206 if (_tcslen(pair
) > 3)
207 tp_info
.radius2
= ParseRadius(pair
+ 3);
208 } else if (_tcsncmp(pair
, _T("A2="), 3) == 0) {
209 if (_tcslen(pair
) > 3)
210 tp_info
.angle2
= ParseAngle(pair
+ 3);
211 } else if (_tcsncmp(pair
, _T("A12="), 4) == 0) {
212 if (_tcslen(pair
) > 3)
213 tp_info
.angle12
= ParseAngle(pair
+ 4);
214 } else if (_tcsncmp(pair
, _T("max_altitude="), 7) == 0) {
215 if (_tcslen(pair
) > 7)
216 tp_info
.max_altitude
= ParseMaxAlt(pair
+ 7);
217 } else if (_tcsncmp(pair
, _T("is_line"), 4) == 0) {
218 if (_tcslen(pair
) > 5 && pair
[5] == _T('1'))
219 tp_info
.is_line
= true;
220 } else if (_tcsncmp(pair
, _T("reduce"), 6) == 0) {
221 if (_tcslen(pair
) > 7 && pair
[7] == _T('1'))
222 tp_info
.reduce
= true;
229 * Parses Options and OZs from See You task file
230 * @param reader. Points to first line of task after task "Waypoint list" line
231 * @param task_info Loads this with CU task options info
232 * @param turnpoint_infos Loads this with CU task tp info
235 ParseCUTaskDetails(FileLineReader
&reader
, SeeYouTaskInformation
*task_info
,
236 SeeYouTurnpointInformation turnpoint_infos
[])
238 // Read options/observation zones
239 TCHAR params_buffer
[1024];
240 const TCHAR
*params
[20];
243 const unsigned int max_params
= ARRAY_SIZE(params
);
244 while ((line
= reader
.ReadLine()) != NULL
&&
245 line
[0] != _T('\"') && line
[0] != _T(',')) {
246 const size_t n_params
= WaypointReaderBase::
247 ExtractParameters(line
, params_buffer
, params
, max_params
, true);
249 if (_tcscmp(params
[0], _T("Options")) == 0) {
250 // Options line found
251 ParseOptions(task_info
, params
, n_params
);
253 } else if (_tcsncmp(params
[0], _T("ObsZone"), 7) == 0) {
254 // Observation zone line found
255 if (_tcslen(params
[0]) <= 8)
258 TPIndex
= ParseOZs(turnpoint_infos
, params
, n_params
);
260 task_info
->max_start_altitude
= turnpoint_infos
[TPIndex
].max_altitude
;
265 static bool isKeyhole(const SeeYouTurnpointInformation
&turnpoint_infos
)
267 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(45)) < fixed(2) &&
268 fabs(turnpoint_infos
.radius1
- fixed(10000)) < fixed(2) &&
269 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
270 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
273 static bool isBGAFixedCourseZone(const SeeYouTurnpointInformation
&turnpoint_infos
)
275 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(45)) < fixed(2) &&
276 fabs(turnpoint_infos
.radius1
- fixed(20000)) < fixed(2) &&
277 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
278 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
281 static bool isBGAEnhancedOptionZone(const SeeYouTurnpointInformation
284 return (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(90)) < fixed(2) &&
285 fabs(turnpoint_infos
.radius1
- fixed(10000)) < fixed(2) &&
286 fabs(turnpoint_infos
.angle2
.Degrees() - fixed(180)) < fixed(2) &&
287 fabs(turnpoint_infos
.radius2
- fixed(500)) < fixed(2));
291 * Creates the correct XCSoar OZ type from the See You OZ options for the point
292 * Note: there are several rules enforced here related to the combinations
293 * and types of Zones supported by XCSoar. When XCSoar adds more zone types,
294 * the logic below will need to be updated.
295 * @param turnpoint_infos Contains the See You turnpoint and OZ info
296 * @param current point position
297 * @param number wps in task
298 * @param array of wps for each point in task
299 * @param factType The XCSoar factory type
300 * @return the XCSoar OZ
302 static ObservationZonePoint
*
303 CreateOZ(const SeeYouTurnpointInformation
&turnpoint_infos
,
304 unsigned pos
, unsigned size
, const Waypoint
*wps
[],
305 TaskFactoryType factType
)
307 ObservationZonePoint
* oz
= NULL
;
308 const bool is_intermediate
= (pos
> 0) && (pos
< (size
- 1));
309 const Waypoint
*wp
= wps
[pos
];
311 if (!turnpoint_infos
.valid
)
314 if (factType
== TaskFactoryType::RACING
&&
315 is_intermediate
&& isKeyhole(turnpoint_infos
))
316 oz
= new KeyholeZone(wp
->location
);
318 else if (factType
== TaskFactoryType::RACING
&&
319 is_intermediate
&& isBGAEnhancedOptionZone(turnpoint_infos
))
320 oz
= new BGAEnhancedOptionZone(wp
->location
);
322 else if (factType
== TaskFactoryType::RACING
&&
323 is_intermediate
&& isBGAFixedCourseZone(turnpoint_infos
))
324 oz
= new BGAFixedCourseZone(wp
->location
);
326 else if (!is_intermediate
&& turnpoint_infos
.is_line
) // special case "is_line"
327 oz
= new LineSectorZone(wp
->location
, turnpoint_infos
.radius1
);
329 // special case "Cylinder"
330 else if (fabs(turnpoint_infos
.angle1
.Degrees() - fixed(180)) < fixed(1) )
331 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
333 else if (factType
== TaskFactoryType::RACING
) {
335 // XCSoar does not support fixed sectors for RT
336 if (turnpoint_infos
.style
== SeeYouTurnpointInformation::FIXED
)
337 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
339 oz
= new FAISectorZone(wp
->location
, is_intermediate
);
341 } else if (is_intermediate
) { //AAT intermediate point
343 assert(wps
[pos
+ 1]);
344 assert(wps
[pos
- 1]);
346 switch (turnpoint_infos
.style
) {
347 case SeeYouTurnpointInformation::FIXED
: {
348 A12adj
= turnpoint_infos
.angle12
.Reciprocal();
351 case SeeYouTurnpointInformation::SYMMETRICAL
: {
352 const Angle ap
= wps
[pos
- 1]->location
.Bearing(wp
->location
);
353 const Angle an
= wps
[pos
+ 1]->location
.Bearing(wp
->location
);
354 A12adj
= ap
.HalfAngle(an
).Reciprocal();
358 case SeeYouTurnpointInformation::TO_NEXT_POINT
: {
359 A12adj
= wps
[pos
+ 1]->location
.Bearing(wp
->location
);
362 case SeeYouTurnpointInformation::TO_PREVIOUS_POINT
: {
363 A12adj
= wps
[pos
- 1]->location
.Bearing(wp
->location
);
366 case SeeYouTurnpointInformation::TO_START_POINT
: {
367 A12adj
= wps
[0]->location
.Bearing(wp
->location
);
372 const Angle RadialStart
= (A12adj
- turnpoint_infos
.angle1
).AsBearing();
373 const Angle RadialEnd
= (A12adj
+ turnpoint_infos
.angle1
).AsBearing();
375 if (turnpoint_infos
.radius2
> fixed(0) &&
376 (turnpoint_infos
.angle2
.AsBearing().Degrees()) < fixed(1)) {
377 oz
= new AnnularSectorZone(wp
->location
, turnpoint_infos
.radius1
,
378 RadialStart
, RadialEnd
, turnpoint_infos
.radius2
);
380 oz
= new SectorZone(wp
->location
, turnpoint_infos
.radius1
,
381 RadialStart
, RadialEnd
);
384 } else { // catch-all
385 oz
= new CylinderZone(wp
->location
, turnpoint_infos
.radius1
);
392 * Creates the XCSoar turnpoint from the See You parameters for the point
393 * @param pos The position of the point in the XCSoar task
394 * @param n_waypoints Number of points in the XCSoar task
395 * @param wp The waypoint
396 * @param fact The XCSoar factory
397 * @param oz The XCSoar OZ for the point
398 * @param factType The XCSoar factory type
401 static OrderedTaskPoint
*
402 CreatePoint(unsigned pos
, unsigned n_waypoints
, const Waypoint
*wp
,
403 AbstractTaskFactory
& fact
, ObservationZonePoint
* oz
,
404 const TaskFactoryType factType
)
406 OrderedTaskPoint
*pt
= NULL
;
409 pt
= (oz
? fact
.CreateStart(oz
, *wp
) : fact
.CreateStart(*wp
));
411 else if (pos
== n_waypoints
- 1)
412 pt
= (oz
? fact
.CreateFinish(oz
, *wp
) : fact
.CreateFinish(*wp
));
414 else if (factType
== TaskFactoryType::RACING
)
415 pt
= (oz
? fact
.CreateASTPoint(oz
, *wp
) : fact
.CreateIntermediate(*wp
));
418 pt
= (oz
? fact
.CreateAATPoint(oz
, *wp
) : fact
.CreateIntermediate(*wp
));
424 AdvanceReaderToTask(FileLineReader
&reader
, const unsigned index
)
426 // Skip lines until n-th task
428 bool in_task_section
= false;
430 for (unsigned i
= 0; (line
= reader
.ReadLine()) != NULL
; i
++) {
431 if (in_task_section
) {
432 if (line
[0] == _T('\"') || line
[0] == _T(',')) {
438 } else if (StringIsEqualIgnoreCase(line
, _T("-----Related Tasks-----"))) {
439 in_task_section
= true;
446 TaskFileSeeYou::GetTask(const TaskBehaviour
&task_behaviour
,
447 const Waypoints
*waypoints
, unsigned index
) const
449 // Create FileReader for reading the task
450 FileLineReader
reader(path
, ConvertLineReader::AUTO
);
454 // Read waypoints from the CUP file
455 Waypoints file_waypoints
;
457 WaypointReaderSeeYou
waypoint_file(0);
458 NullOperationEnvironment operation
;
459 waypoint_file
.Parse(file_waypoints
, reader
, operation
);
461 file_waypoints
.Optimise();
463 if (!reader
.Rewind())
466 TCHAR
*line
= AdvanceReaderToTask(reader
, index
);
470 // Read waypoint list
471 // e.g. "Club day 4 Racing task","085PRI","083BOJ","170D_K","065SKY","0844YY", "0844YY"
472 // TASK NAME , TAKEOFF, START , TP1 , TP2 , FINISH , LANDING
473 TCHAR waypoints_buffer
[1024];
474 const TCHAR
*wps
[30];
475 size_t n_waypoints
= WaypointReaderBase::
476 ExtractParameters(line
, waypoints_buffer
, wps
, 30, true, _T('"')) - 3;
478 SeeYouTaskInformation task_info
;
479 SeeYouTurnpointInformation turnpoint_infos
[30];
480 const Waypoint
*waypoints_in_task
[30];
482 ParseCUTaskDetails(reader
, &task_info
, turnpoint_infos
);
484 OrderedTask
*task
= new OrderedTask(task_behaviour
);
485 task
->SetFactory(task_info
.wp_dis
?
486 TaskFactoryType::RACING
: TaskFactoryType::AAT
);
487 AbstractTaskFactory
& fact
= task
->GetFactory();
488 const TaskFactoryType factType
= task
->GetFactoryType();
490 OrderedTaskBehaviour beh
= task
->GetOrderedTaskBehaviour();
491 if (factType
== TaskFactoryType::AAT
) {
492 beh
.aat_min_time
= task_info
.task_time
;
494 if (factType
== TaskFactoryType::AAT
||
495 factType
== TaskFactoryType::RACING
) {
496 beh
.start_constraints
.max_height
= (unsigned)task_info
.max_start_altitude
;
497 beh
.start_constraints
.max_height_ref
= AltitudeReference::MSL
;
499 task
->SetOrderedTaskBehaviour(beh
);
501 // mark task waypoints. Skip takeoff and landing point
502 for (unsigned i
= 0; i
< n_waypoints
; i
++) {
503 const Waypoint
* file_wp
= file_waypoints
.LookupName(wps
[i
+ 2]);
507 // Try to find waypoint by name
508 const Waypoint
* wp
= waypoints
->LookupName(file_wp
->name
);
510 // If waypoint by name found and closer than 10m to the original
512 wp
->location
.Distance(file_wp
->location
) <= fixed(10)) {
513 // Use this waypoint for the task
514 waypoints_in_task
[i
] = wp
;
518 // Try finding the closest waypoint to the original one
519 wp
= waypoints
->GetNearest(file_wp
->location
, fixed(10));
521 // If closest waypoint found and closer than 10m to the original
523 wp
->location
.Distance(file_wp
->location
) <= fixed(10)) {
524 // Use this waypoint for the task
525 waypoints_in_task
[i
] = wp
;
529 // Use the original waypoint
530 waypoints_in_task
[i
] = file_wp
;
533 //now create TPs and OZs
534 for (unsigned i
= 0; i
< n_waypoints
; i
++) {
536 ObservationZonePoint
* oz
= CreateOZ(turnpoint_infos
[i
], i
, n_waypoints
,
537 waypoints_in_task
, factType
);
538 assert(waypoints_in_task
[i
]);
539 OrderedTaskPoint
*pt
= CreatePoint(i
, n_waypoints
, waypoints_in_task
[i
],
543 fact
.Append(*pt
, false);
551 TaskFileSeeYou::Count()
553 // Reset internal task name memory
554 namesuffixes
.clear();
557 FileLineReader
reader(path
, ConvertLineReader::AUTO
);
562 bool in_task_section
= false;
564 while ((line
= reader
.ReadLine()) != NULL
) {
565 if (in_task_section
) {
566 // If the line starts with a string or "nothing" followed
567 // by a comma it is a new task definition line
568 if (line
[0] == _T('\"') || line
[0] == _T(',')) {
569 // If we still have space in the task name list
570 if (count
< namesuffixes
.capacity()) {
571 // If the task doesn't have a name inside the file
572 if (line
[0] == _T(','))
573 namesuffixes
.append(NULL
);
575 // Ignore starting quote (")
578 // Save pointer to first character
580 // Skip characters until next quote (") or end of string
581 while (line
[0] != _T('\"') && line
[0] != _T('\0'))
584 // Replace quote (") by end of string (null)
587 // Append task name to the list
588 if (_tcslen(name
) > 0)
589 namesuffixes
.append(_tcsdup(name
));
591 namesuffixes
.append(NULL
);
595 // Increase the task counter
598 } else if (StringIsEqualIgnoreCase(line
, _T("-----Related Tasks-----"))) {
599 // Found the marker -> all following lines are task lines
600 in_task_section
= true;
604 // Return number of tasks found in the CUP file