Subversion Repositories Projects

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
670 rain-er 1
#!/usr/bin/perl
2
#!/usr/bin/perl -d:ptkdb
3
 
4
###############################################################################
5
#
6
# libmkcockpit.pl -  MK Mission Cockpit - Subroutined for GUI Frontend
7
#
8
# Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
9
#
10
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
11
#
12
# Es ist Ihnen gestattet: 
13
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
14
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
15
# 
16
# Zu den folgenden Bedingungen:
17
#     * Namensnennung.
18
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
19
#     * Keine kommerzielle Nutzung.
20
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
21
#     * Weitergabe unter gleichen Bedingungen.
22
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
23
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
24
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
25
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
26
# 
27
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
28
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
29
# 
30
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
31
# des Rechteinhabers dazu erhalten.
32
# 
33
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
34
# 
35
# Weitere Details zur Lizenzbestimmung gibt es hier:
36
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
37
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
38
#
39
###############################################################################
40
# 2009-08-09 0.2.5 rw subroutines moved from mkcockpit.pl
41
# 2009-09-05 0.2.6 rw POI heading control added
42
# 2009-10-10 0.2.7 rw Layout Config-dialog
43
#                     Fix Message-Balloon in KML-Mode
44
# 2009-10-25 0.3.0 rw NC 0.17
45
#                     Read/Write KopterTool WPL Waypoint list
46
#                     configuration Combo Box
47
#
48
###############################################################################
49
 
50
$Version{'libmkcockpit.pl'} = "0.3.0 - 2009-10-25";
51
 
52
 
53
# check, if %MkOsd is valid
54
sub MkOsdIsValid()
55
    {
56
    return ( $MkOsd{'_Timestamp'} >= time-2 );
57
    }
58
 
59
# check, if current GPS position is valid
60
sub CurPosIsValid()
61
    {
62
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 );
63
    }
64
 
65
# check, if home GPS position is valid
66
sub HomePosIsValid()
67
    {
68
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'HomePos_Stat'} == 1 );
69
    }
70
 
71
# check, if target GPS position is valid
72
sub TargetIsValid()
73
    {
74
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'TargetPos_Stat'} == 1  );
75
    }
76
 
77
# check, if motor are on
78
sub MkIsMotorOn()
79
    {
80
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x01  );
81
    }
82
 
83
# check, if MK is flying
84
sub MkIsFlying()
85
    {
86
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x02  );
87
    }
88
 
89
# check, if MK is calibrating
90
sub MkIsCalibrating()
91
    {
92
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x04  );
93
    }
94
# check, if Motor is starting
95
sub MkIsMotorStarting()
96
    {
97
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x08  );
98
    }
99
 
100
# check, Emergency Landing
101
sub MkEmergencyLanding()
102
    {
103
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x10  );
104
    }
105
 
106
# check, if MK is FREE Mode
107
sub MkIsFreeMode()
108
    {
109
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x01  );
110
    }
111
 
112
# check, if MK is in PH Mode
113
sub MkIsPhMode()
114
    {
115
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x02  );
116
    }
117
 
118
# check, if MK is in WPT Mode
119
sub MkIsWptMode()
120
    {
121
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x04  );
122
    }
123
 
124
# check, Range Limit
125
sub MkRangeLimit()
126
    {
127
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x08  );
128
    }
129
 
130
# check, Serial Link
131
sub MkSerialLink()
132
    {
133
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x10  );
134
    }
135
 
136
# check, Target reached
137
sub MkTargetReached()
138
    {
139
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x20  );
140
    }
141
 
142
# check, Manual Control
143
sub MkManualControl()
144
    {
145
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x40  );
146
    }
147
 
148
 
149
# get battery capacity in %
150
sub BatCapacity()
151
    {
152
    my ($UBat) = @_;
153
 
154
    my $CfgVal = $Cfg->{'mkcockpit'}->{'BatCharacteristics'};
155
    my @Voltage = split ' ', $CfgVal;
156
 
157
    my $Capacity = 0;
158
    if ( $UBat >= $Voltage[0] )
159
        {
160
        $Capacity = 100;
161
        }
162
 
163
    $Cnt = $#Voltage;
164
    for ($i=0; $i < $Cnt; $i++)
165
        {
166
        my $V1 = $Voltage[$i];
167
        my $V2 = $Voltage[$i+1];
168
 
169
        if ( $UBat >= $V1  and  $UBat < $V2  or
170
             $UBat <= $V1  and  $UBat > $V2 )
171
            {
172
            # linear interpolation
173
            my $x = $i + ($UBat - $V1 ) / ($V2 - $V1);
174
            $Capacity = 100 - $x * 100 / $Cnt;
175
            last;
176
            }
177
        }
178
 
179
    return $Capacity;
180
    }
181
 
182
 
183
#
184
# Waypoint handling
185
#
186
 
187
# Add a Waypoint to @Waypoints List
188
sub WpAdd()
189
    {
190
    my ($Wp_x, $Wp_y) = @_;
191
 
192
    # save Wp-Hash in Waypoint-Array
193
    my $Wp = {};        
194
    my $Tag = sprintf "Waypoint-%d.%d", time, int (rand(9)) ;   # kind of unique Tag for this Wp
195
    ($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
196
    $Wp->{'Tag'} = $Tag;
197
    $Wp->{'MapX'} = $Wp_x;
198
    $Wp->{'MapY'} = $Wp_y;
199
    $Wp->{'Pos_Lat'} = $Lat;
200
    $Wp->{'Pos_Lon'} = $Lon;
201
    $Wp->{'Pos_Alt'} = $MkOsd{'CurPos_Alt'};
202
    $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
203
    $Wp->{'ToleranceRadius'} = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
204
    $Wp->{'Holdtime'}        = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
205
    $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
206
    push @Waypoints, $Wp;
207
    }
208
 
209
 
210
# Delete Waypoint from @Waypoints List
211
sub WpDelete ()
212
    {
213
    my ($WpIndex) = @_;
214
 
215
    # delete Wp in Waypoint-Array
216
    splice @Waypoints, $WpIndex, 1;
217
    }
218
 
219
 
220
# Load @Waypoints from file
221
sub WpLoadFile ()
222
    {
223
    my ($WpFile) = @_;
224
 
225
    if ( $WpFile =~ /.wpl$/i )
226
        {
227
        # load Mikrokopter Tool WP List *.wpl
228
 
229
        my $WpCnt = 0;
230
        my $WpIndex = 0;
231
        my @WpWpl;
232
 
233
        open WPL, "<$WpFile";
234
        my @Wpl = <WPL>;
235
        close WPL;
236
        foreach my $Line (@Wpl)
237
            {
238
            chomp $Line;
239
            if ( $Line =~ /NumberOfWaypoints\s*=\s*(\d*)/i )
240
                {
241
                $WpCnt = $1;
242
                }
243
            elsif ( $Line =~ /\[Waypoint(\d*)\]/i )
244
                {
245
                $WpIndex = $1;
246
                }
247
            elsif ( $Line =~ /(\S*)\s*=\s*(\S*)/i )
248
                {
249
                my $Key = $1;
250
                my $Value = $2;
251
                $WpWpl[$WpIndex]{$Key} = $Value;
252
                }
253
            }
254
 
255
        # WPL Array in Waypoints-Array umkopieren
256
        undef @Waypoints;
257
 
258
        for ( $Index=0; $Index < $WpCnt; $Index++)
259
            {
260
            my $Wp = {};        
261
            my $Tag = sprintf "Waypoint-%d.%d", time, $Index + 1;   # kind of unique Tag for this Wp
262
 
263
            my $Lat = $WpWpl[$Index]{'Latitude'};
264
            my $Lon = $WpWpl[$Index]{'Longitude'};
265
 
266
            ($MapX, $MapY) = &MapGps2XY($Lat, $Lon);
267
            $Wp->{'Tag'}  = $Tag;
268
            $Wp->{'MapX'} = $MapX;
269
            $Wp->{'MapY'} = $MapY;
270
            $Wp->{'Pos_Lat'} = $Lat;
271
            $Wp->{'Pos_Lon'} = $Lon;
272
            $Wp->{'Pos_Alt'} = $MkOsd{'CurPos_Alt'};
273
            $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
274
            $Wp->{'ToleranceRadius'} = $WpWpl[$Index]{'Radius'};
275
            $Wp->{'Holdtime'}        = $WpWpl[$Index]{'DelayTime'};
276
            $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
277
            push @Waypoints, $Wp;
278
            }
279
        }
280
    else
281
        {
282
        # load Mission Cockpit XML
283
 
284
        # XML in Hash-Ref lesen
285
        my $Wp = XMLin($WpFile, ForceArray => 1);
286
 
287
       # XML Hash-Ref in Wp-Array umkopieren
288
        undef @Waypoints;
289
 
290
        foreach $key (sort keys %$Wp)
291
            {
292
            my $Point = $Wp->{$key}->[0];
293
 
294
            # relative Pixelkoordinaten auf Bildgroesse umrechnen
295
            if ( $Point->{'MapX'} <= 1  and  $Point->{'MapY'} <= 1 )
296
                {
297
                $Point->{'MapX'} = int ( $Point->{'MapX'} * $MapSizeX + 0.5 );
298
                $Point->{'MapY'} = int ( $Point->{'MapY'} * $MapSizeY + 0.5 );
299
                }
300
 
301
            # GPS Koordinaten für die aktuelle Karte neu aus Map x/y berechnen
302
            my ($Lat, $Lon) = &MapXY2Gps($Point->{'MapX'}, $Point->{'MapY'});
303
            $Point->{'Pos_Lat'} = $Lat;
304
            $Point->{'Pos_Lon'} = $Lon;
305
            push @Waypoints, $Point;
306
            }
307
        }
308
    }
309
 
310
 
311
# Save @Waypoints to file
312
sub WpSaveFile()
313
    {
314
    my ($WpFile) = @_;
315
 
316
    if ( $WpFile =~ /.wpl$/i )
317
        {
318
        # save Mikrokopter Tool WP List *.wpl
319
 
320
        open WPL, ">$WpFile";
321
 
322
        my $WpCnt = scalar @Waypoints;
323
 
324
        print WPL "[General\]\n";
325
        print WPL "FileVersion=1\n";
326
        print WPL "NumberOfWaypoints=$WpCnt\n";
327
 
328
        for  $i ( 0 .. $#Waypoints )
329
            {
330
            print WPL "\[Waypoint${i}\]\n";
331
            print WPL "Latitude=$Waypoints[$i]{'Pos_Lat'}\n";
332
            print WPL "Longitude=$Waypoints[$i]{'Pos_Lon'}\n";
333
            print WPL "Radius=$Waypoints[$i]{'ToleranceRadius'}\n";
334
            print WPL "DelayTime=$Waypoints[$i]{'Holdtime'}\n";
335
            }
336
        close WPL;
337
        }
338
    else
339
        {
340
        # save Mission Cockpit XML
341
 
342
        # Waypoint-Array in Hash umkopieren
343
        for  $i ( 0 .. $#Waypoints )
344
            {
345
            my $key = sprintf ("WP-%04d", $i);
346
            my $Wp = {%{$Waypoints[$i]}};        # copy of Hash-content
347
            $WpOut{$key} = $Wp;
348
 
349
            # Pixelkoordinaten relativ zur Bildgroesse speichern
350
            $WpOut{$key}{'MapX_Pixel'} = $WpOut{$key}{'MapX'};
351
            $WpOut{$key}{'MapY_Pixel'} = $WpOut{$key}{'MapY'};
352
            $WpOut{$key}{'MapX'} /= $MapSizeX;
353
            $WpOut{$key}{'MapY'} /= $MapSizeY;
354
            }
355
 
356
        # WP-Hash als XML speichern
357
        &XMLout (\%WpOut,
358
                 'OutputFile' => $WpFile,
359
                 'AttrIndent' => '1',
360
                 'RootName' => 'Waypoints',
361
                );
362
        }
363
    }
364
 
365
 
366
# Get Wp Index from Canvas Id
367
sub WpGetIndexFromId()
368
    {
369
    my ($id) = @_;
370
 
371
    my @Tags = $map_canvas->gettags($id);
372
    my $WpTag = $Tags[1];
373
 
374
    for $i (0 .. $#Waypoints)
375
        {
376
        my $Wp = $Waypoints[$i];
377
        if ( $Wp->{'Tag'} eq $WpTag )
378
            {
379
            # got it
380
            return $i;
381
            }
382
        }
383
    return -1;
384
    }
385
 
386
 
387
# Resend all Waypoints to MK
388
sub WpSendAll()
389
    {
390
    # OSD/Debug Abfragefrequenz verringern, sonst kommen nicht alle Wp im MK an
391
    # Sicherheitshalber doppelt senden
392
    $MkSendWp = 1;       # verhindert ueberschreiben im Timer
393
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
394
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
395
    usleep (200000);
396
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
397
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
398
    usleep (200000);
399
 
400
    # Alte WP-Liste im MK löschen
401
    my $Wp = $Waypoints[0];
402
    &MkFlyTo ( '-lat'  => $Wp->{'Pos_Lat'},
403
               '-lon'  => $Wp->{'Pos_Lon'},
404
               '-mode' => "Waypoint Delete"
405
             );
406
 
407
    for $i (0 .. $#Waypoints)
408
        {
409
        my $Wp = $Waypoints[$i];
410
        &MkFlyTo ( '-lat'             => $Wp->{'Pos_Lat'},
411
                   '-lon'             => $Wp->{'Pos_Lon'},
412
                   '-alt'             => $Wp->{'Pos_Alt'},
413
                   '-heading'         => $Wp->{'Heading'},
414
                   '-toleranceradius' => $Wp->{'ToleranceRadius'},
415
                   '-holdtime'        => $Wp->{'Holdtime'},
416
                   '-eventflag'       => $Wp->{'Event_Flag'},
417
                   '-mode'            => "Waypoint",
418
                   '-index'           => $i,
419
                 );
420
 
421
        usleep (150000)  # NC Zeit zum Verarbeiten geben
422
        }
423
 
424
    $MkSendWp = 0;  # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt
425
 
426
    # gray connectors: Wp are sent to MK
427
    $map_canvas->itemconfigure('Waypoint-Connector',
428
                               '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpConnector'},
429
                              );
430
 
431
    # MK ist nun synchron mit @Waypoints
432
    $WaypointsModified = 0;
433
    }          
434
 
435
 
436
# Redraw Waypoint Icons
437
sub WpRedrawIcons()
438
    {
439
    if ( $PlayerWptKmlMode =~ /WPT/i )
440
        {
441
 
442
        # delete old icons and Wp-Number from canvas
443
        $map_canvas->delete('Waypoint');
444
        $map_canvas->delete('WaypointNumber');
445
 
446
        # create new icons
447
        for $i (0 .. $#Waypoints)
448
           {
449
            my $Wp = $Waypoints[$i];
450
            my $x = $Wp->{'MapX'};
451
            my $y = $Wp->{'MapY'};
452
            my $Tag = $Wp->{'Tag'};
453
 
454
            # Waypoint Icon
455
            my $IconHeight = 48;
456
            my $IconWidth = 48;
457
            $map_canvas->createImage($x-$IconWidth/2, $y-$IconHeight,
458
                                     '-tags' => ['Waypoint', $Tag],
459
                                     '-anchor' => 'nw',
460
                                     '-image'  => 'Waypoint-Photo',
461
                                    );
462
            # Waypoint Number
463
            my $WpNumber = $i + 1;
464
            $map_canvas->createText ( $x+3, $y-$IconHeight/2+12,
465
                                      '-tags' => ['WaypointNumber', $Tag],
466
                                      '-text' => $WpNumber,
467
                                      '-font' => '-*-Arial-Bold-R-Normal--*-100-*',
468
                                      '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpNumber'},
469
                                      '-anchor' => 'w',
470
                                     );
471
 
472
            }  
473
        $map_canvas->lower('Waypoint', 'Target');           # waypoint below Target
474
        $map_canvas->lower('WaypointNumber', 'Waypoint');   # waypoint-number below waypoint
475
        }
476
    }
477
 
478
 
479
# Redraw Waypoint connectors
480
sub WpRedrawLines()
481
    {
482
    if ( $PlayerWptKmlMode eq 'WPT'  and  $PlayerRandomMode eq 'STD' )
483
        {
484
        # delete old connectors from canvas
485
        $map_canvas->delete('Waypoint-Connector');  
486
 
487
        my $Color = $Cfg->{'mkcockpit'}->{'ColorWpConnector'};
488
        if ( $WaypointsModified )
489
            {
490
            $Color = $Cfg->{'mkcockpit'}->{'ColorWpResend'};
491
            }
492
 
493
        my $Wp = $Waypoints[0];
494
        my $x_last = $Wp->{'MapX'};
495
        my $y_last = $Wp->{'MapY'};
496
        for $i (1 .. $#Waypoints)
497
            {
498
            my $Wp = $Waypoints[$i];
499
            my $x = $Wp->{'MapX'};
500
            my $y = $Wp->{'MapY'};
501
 
502
            $map_canvas->createLine ( $x_last, $y_last, $x, $y,
503
                                      '-tags' => 'Waypoint-Connector',
504
                                      '-arrow' => 'last',
505
                                      '-arrowshape' => [10, 10, 3 ],
506
                                      '-fill' => $Color,
507
                                      '-width' => 1,
508
                                    );                                           
509
            $x_last = $x;
510
            $y_last = $y;
511
            }
512
 
513
        $map_canvas->lower('Waypoint-Connector', 'Waypoint');   # connector below waypoint
514
        }
515
    }
516
 
517
 
518
# Hide Waypoints and connectors on Canvas
519
sub WpHide()
520
   {
521
   $map_canvas->delete('Waypoint');
522
   $map_canvas->delete('WaypointNumber');
523
   $map_canvas->delete('Waypoint-Connector');
524
   }
525
 
526
 
527
# Hide Kml-Track on Canvas
528
sub KmlHide()
529
   {
530
   $map_canvas->delete('KML-Track');
531
   }
532
 
533
 
534
# Load @KmlTargets from file
535
sub KmlLoadFile()
536
    {
537
    my ($File) = @_;
538
 
539
    # XML in Hash-Ref lesen
540
    my $Kml = XMLin($File);
541
 
542
    # init state maschine
543
    undef @KmlTargets;
544
    $KmlPlayerIndex = 0;
545
 
546
    my $Coordinates = $Kml->{Document}->{Placemark}->{LineString}->{coordinates};
547
    foreach $Line (split "\n", $Coordinates)
548
        {
549
        chomp $Line;
550
        $Line =~ s/\s//g;       # remove white space
551
        if ( $Line ne "" )
552
            {
553
            my ($Lon, $Lat, $Alt) = split ",", $Line;
554
            $Lon = sprintf ("%f", $Lon);
555
            $Lat = sprintf ("%f", $Lat);
556
            $Alt = sprintf ("%f", $Alt);
557
 
558
            push @KmlTargets, {'Lat' => $Lat,
559
                               'Lon' => $Lon,
560
                               'Alt' => $Alt,
561
                              };
562
            }
563
        }
564
    }
565
 
566
# Redraw KML track
567
sub KmlRedraw()
568
    {
569
 
570
    # delete old Track from canvas
571
    $map_canvas->delete('KML-Track');
572
 
573
    my @Track;
574
 
575
    foreach $Target ( @KmlTargets )
576
        {
577
        my $Lat = $Target->{'Lat'};
578
        my $Lon = $Target->{'Lon'};
579
        my $Alt = $Target->{'Alt'};
580
        my ($x, $y) = &MapGps2XY($Lat, $Lon);
581
        push @Track, $x, $y;
582
        }
583
 
584
    if ( scalar @Track >= 4 )  # at least 2 Koordinaten-Paare
585
        {
586
        $map_canvas->createLine ( @Track,
587
                                  '-tags' => 'KML-Track',
588
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorKmlTrack'},
589
                                  '-width' => 1,
590
                                );       
591
 
592
        $map_canvas->lower('KML-Track', 'Target');        # Track below Target
593
        }
594
    }
595
 
596
 
597
# Redraw Footprint
598
sub FootprintRedraw()
599
    {
600
    # delete old Footprint from canvas
601
    $map_canvas->delete('Footprint');  
602
 
603
    if ( scalar @Footprint >= 4 )  # at least 2 Koordinaten-Paare
604
        {
605
        $map_canvas->createLine ( @Footprint,
606
                                  '-tags' => 'Footprint',
607
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorFootprint'},
608
                                  '-width' => 1,
609
                                );       
610
        }
611
 
612
    $map_canvas->lower('Footprint', 'Target');
613
    }
614
 
615
 
616
# Waypoint Player: Set Waypoint - sequence or random
617
sub WpTargetSet()
618
    {
619
    my ($Index) = @_;
620
 
621
    my $WpCnt = scalar @Waypoints;
622
    if ( $Index < 0  or  $Index >= $WpCnt )
623
        {
624
        # invalid WP number
625
        return 1;
626
        }
627
 
628
    my $Wp = $Waypoints[$Index];
629
    my $Wp_x = $Wp->{'MapX'};
630
    my $Wp_y = $Wp->{'MapY'};
631
 
632
    # is Wp reachable?
633
    if ( ! &IsTargetReachable($Wp_x, $Wp_y) )
634
        {
635
        # new Wp-Target is not reachable
636
        return 1;
637
        }
638
 
639
    # set new Wp-Target 
640
    $WpPlayerIndex = $Index;
641
    $WpPlayerHoldtime = -1;
642
 
643
    return 0;
644
    }
645
 
646
 
647
# Waypoint Player: Goto next Waypoint - sequence or random
648
sub WpTargetNext()
649
    {
650
    my ($ParIndex) = @_;
651
 
652
    my $WpCnt = scalar @Waypoints;
653
 
654
    # Std- or Random Waypoint sequence
655
    if ( $PlayerRandomMode =~ /STD/i  or
656
         $PlayerRandomMode =~ /RND/i )
657
        {
658
        $NewIndex = $WpPlayerIndex;
659
 
660
        # get next Wp
661
        for ( $i=0; $i<5; $i++)        # avoid deadlock, if no WP reachable
662
            {
663
            for ( $j=0; $j<5; $j++ )   # avoid deadlock, if only 1 WP
664
                {
665
 
666
                if ( $PlayerRandomMode =~ /STD/i )
667
                    {
668
                    $NewIndex ++;
669
                    if ( $NewIndex >= $WpCnt )
670
                        {
671
                        # Restart with 1st Wp
672
                        $NewIndex = 0;
673
                        }
674
                    }
675
 
676
                if ( $PlayerRandomMode =~ /RND/i )
677
                    {
678
                    $NewIndex = int (rand($WpCnt));
679
                    }
680
 
681
                # want to have different Wp 
682
                if ( $NewIndex ne $WpPlayerIndex )
683
                    {
684
                    last;
685
                    }
686
                }
687
 
688
            # Set new Target 
689
            if ( &WpTargetSet ($NewIndex) == 0 )
690
                {
691
                # new Wp-Target set
692
                last;
693
                }
694
            }
695
        }
696
 
697
    # Random Map sequence
698
    if ( $PlayerRandomMode =~ /MAP/i )
699
        {
700
        $RandomTarget_x = $MkPos_x;
701
        $RandomTarget_y = $MkPos_y;
702
 
703
        for ( $i=0; $i<50; $i++)        # avoid deadlock, if target not reachable
704
            {
705
            # don't use 10% around the map
706
            my $New_x = int (rand($MapSizeX - 2 * $MapSizeX/10));
707
            my $New_y = int (rand($MapSizeY - 2 * $MapSizeY/10));
708
            $New_x += $MapSizeX/10;
709
            $New_y += $MapSizeY/10;
710
 
711
            # is Target reachable?
712
            if ( &IsTargetReachable($New_x, $New_y) )
713
                {
714
                # new Target found
715
                $RandomTarget_x = $New_x;
716
                $RandomTarget_y = $New_y;
717
                last;
718
                }
719
            }
720
        }
721
 
722
    &TtsSpeak ('MEDIUM', $Translate{'TtsNextTarget'});
723
 
724
    $WpPlayerHoldtime = -1;
725
    }
726
 
727
 
728
# Waypoint Player: Goto previous Waypoint
729
sub WpTargetPrev()
730
    {
731
    if ( $PlayerRandomMode =~ /STD/i )
732
        {
733
        $WpPlayerIndex --;
734
        if ( $WpPlayerIndex < 0 )
735
            {
736
            # Restart with last Wp
737
            $WpPlayerIndex = $#Waypoints;
738
            }
739
        }
740
    else
741
        {
742
        # Next Random Target
743
        &WpTargetNext();
744
        }
745
 
746
    $WpPlayerHoldtime = -1;
747
    }
748
 
749
 
750
# Waypoint Player: Goto first Waypoint
751
sub WpTargetFirst()
752
    {
753
    $WpPlayerIndex = 0;
754
    $WpPlayerHoldtime = -1;
755
    }
756
 
757
# Waypoint Player: Goto last Waypoint
758
sub WpTargetLast()
759
    {
760
    $WpPlayerIndex = $#Waypoints;
761
    $WpPlayerHoldtime = -1;
762
    }
763
 
764
 
765
# Waypoint Player: Waypoint Target reached?
766
sub WpCheckTargetReached()
767
    {
768
    if ( $WpPlayerHoldtime == -1 )
769
        {
770
        lock (%MkOsd);              # until end of block
771
 
772
        if ( &CurPosIsValid() and  &HomePosIsValid() and  &MkIsWptMode() )
773
            {
774
            # Gueltige SAT Daten
775
 
776
            # for Wp mode
777
            my $Wp = $Waypoints[$WpPlayerIndex];
778
            my $WpTarget_Lat = $Wp->{'Pos_Lat'};
779
            my $WpTarget_Lon = $Wp->{'Pos_Lon'};
780
            my $WpTolerance  = $Wp->{'ToleranceRadius'};
781
            my $WpHoldtime   = $Wp->{'Holdtime'};
782
 
783
            # Random-Map Mode
784
            if ( $PlayerRandomMode =~ /MAP/i )
785
                {
786
                ($WpTarget_Lat, $WpTarget_Lon) = &MapXY2Gps ($RandomTarget_x, $RandomTarget_y);
787
                $WpTolerance = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
788
                $WpHoldtime  = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
789
                }
790
 
791
            # Operation Radius pruefen
792
            my ($HomeDist, $HomeBearing) = &MapGpsTo($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
793
            if ( $HomeDist > $MkOsd{'OperatingRadius'} )
794
                {
795
                # Target entsprechend Operation Radius neu berechnen
796
                $HomeDist = $MkOsd{'OperatingRadius'};
797
                ($WpTarget_Lat, $WpTarget_Lon) = &MapGpsAt($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $HomeDist, $HomeBearing);
798
                }
799
 
800
            # Abstand zum Ziel pruefen
801
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
802
            $Dist = int ($Dist + 0.5);
803
            if ( $Dist <= $WpTolerance )
804
                {
805
                # Target reached - count down Holdtime
806
                $WpPlayerHoldtime = 2 * $WpHoldtime;      # 0..2n - decrement im 0.5s timer
807
 
808
                &TtsSpeak ('MEDIUM', $Translate{'TtsTargetReached'});
809
                }
810
            }
811
        }
812
 
813
    if ( $WpPlayerHoldtime == 0 )  # wird im 0.5s timer runtergezaehlt
814
        {
815
        # Target reached - Holdtime is over
816
        $WpPlayerHoldtime = -1;
817
 
818
        return 1;
819
        }
820
 
821
    # Target NOT reached
822
    return 0;
823
    }
824
 
825
 
826
# KML Player: 10s forward
827
sub KmlTargetNext()
828
    {
829
    $KmlPlayerIndex += int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
830
    if ( $KmlPlayerIndex > $#KmlTargets )
831
        {
832
        # Next loop
833
        $KmlPlayerIndex -= $#KmlTargets;
834
        }
835
    }
836
 
837
# KML Player: 10s backward
838
sub KmlTargetPrev()
839
    {
840
    $KmlPlayerIndex -= int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
841
    if ( $KmlPlayerIndex < 0 )
842
        {
843
        # Next loop
844
        $KmlPlayerIndex += $#KmlTargets;
845
        }
846
    }
847
 
848
# KML Player: Goto first Target
849
sub KmlTargetFirst()
850
    {
851
    $KmlPlayerIndex = 0;
852
    }
853
 
854
# KML Player: Goto last Target
855
sub KmlTargetLast()
856
    {
857
    $KmlPlayerIndex = $#KmlTargets;
858
    }
859
 
860
 
861
#
862
# Set Player modes
863
#
864
 
865
# set player to "Play" mode
866
sub PlayerPlay()
867
    {
868
    $PlayerMode = 'Play';
869
    $WpPlayerHoldtime = -1;
870
 
871
    # Play/Pause-Icon loeschen und neu anzeigen
872
    $map_canvas->delete('Wp-PlayPause');
873
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
874
                             '-tags' => 'Wp-PlayPause',
875
                             '-anchor' => 'nw',
876
                             '-image'  => 'WpPause-Foto',
877
                             );
878
    &FoxHide();
879
    }
880
 
881
 
882
# set player to "Pause" mode
883
sub PlayerPause()
884
    {
885
    $PlayerMode = 'Pause';
886
    $WpPlayerHoldtime = -1;
887
 
888
    # Play/Pause-Icon loeschen und neu anzeigen
889
    $map_canvas->delete('Wp-PlayPause');
890
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
891
                             '-tags' => 'Wp-PlayPause',
892
                             '-anchor' => 'nw',
893
                             '-image'  => 'WpPlay-Foto',
894
                             );
895
 
896
    # momentane Position merken und im Player-Timer senden
897
    $PlayerPause_Lon = "";
898
    $PlayerPause_Lat = "";
899
 
900
    lock (%MkOsd);              # until end of block
901
    if ( &CurPosIsValid() )
902
        {
903
        $PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
904
        $PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
905
        }
906
 
907
    &FoxShow();
908
    }
909
 
910
 
911
# set player to "Home" mode
912
sub PlayerHome()
913
    {
914
 
915
    $PlayerMode = 'Home';
916
    &WpTargetFirst();
917
 
918
    # Play/Pause-Icon loeschen und neu anzeigen
919
    $map_canvas->delete('Wp-PlayPause');
920
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
921
                             '-tags'   => 'Wp-PlayPause',
922
                             '-anchor' => 'nw',
923
                             '-image'  => 'WpPlay-Foto',
924
                             );
925
    &FoxHide();
926
    }
927
 
928
 
929
# set player to "Stop" mode
930
sub PlayerStop()
931
    {
932
    $PlayerMode = 'Stop';
933
    &WpTargetFirst();
934
 
935
    # set Play/Pause Icon to "Play
936
    $map_canvas->delete('Wp-PlayPause');
937
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
938
                             '-tags'   => 'Wp-PlayPause',
939
                             '-anchor' => 'nw',
940
                             '-image'  => 'WpPlay-Foto',
941
                             );
942
 
943
    # switch player to Wp Mode
944
    &PlayerWpt();
945
 
946
    &FoxHide();
947
    }
948
 
949
 
950
# set player Random Mode to "STD"
951
sub PlayerRandomStd()
952
    {
953
    $PlayerRandomMode = "STD";
954
 
955
    # Set Icon
956
    $map_canvas->delete('Wp-WptRandom');
957
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
958
                             '-tags' => 'Wp-WptRandom',
959
                             '-anchor' => 'nw',
960
                             '-image'  => 'WpRandomOn-Foto',
961
                            );
962
 
963
    # redraw connectors and Icons on canvas
964
    &WpRedrawLines();
965
    &WpRedrawIcons();
966
    }
967
 
968
 
969
# set player Random Mode to "RND"
970
sub PlayerRandomRnd()
971
    {
972
    $PlayerRandomMode = "RND";
973
 
974
    # Set Icon
975
    $map_canvas->delete('Wp-WptRandom');
976
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
977
                             '-tags' => 'Wp-WptRandom',
978
                             '-anchor' => 'nw',
979
                             '-image'  => 'WpRandomMap-Foto',
980
                            );
981
 
982
    # delete Wp-connectors from canvas
983
    $map_canvas->delete('Waypoint-Connector');  
984
    }
985
 
986
 
987
# set player Random Mode to "MAP"
988
sub PlayerRandomMap()
989
    {
990
    $PlayerRandomMode = "MAP";
991
 
992
    # Set Icon
993
    $map_canvas->delete('Wp-WptRandom');
994
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
995
                             '-tags' => 'Wp-WptRandom',
996
                             '-anchor' => 'nw',
997
                             '-image'  => 'WpRandomOff-Foto',
998
                            );
999
 
1000
    # Get 1st Target
1001
    &WpTargetNext();
1002
 
1003
    # hide WP and connectors on canvas
1004
    &WpHide();
1005
    }
1006
 
1007
 
1008
# set player to KML mode
1009
sub PlayerKml()
1010
    {
1011
    $PlayerWptKmlMode = 'KML';
1012
 
1013
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1014
    $map_canvas->delete('Wp-WptKml');
1015
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1016
                             '-tags' => 'Wp-WptKml',
1017
                             '-anchor' => 'nw',
1018
                             '-image'  => 'WpKml-Foto',
1019
                             );
1020
 
1021
    # delete Waypoints from canvas
1022
    &WpHide();
1023
 
1024
    # show KML Track
1025
    &KmlRedraw();
1026
    }
1027
 
1028
 
1029
# set player to WPT mode
1030
sub PlayerWpt()
1031
    {
1032
    $PlayerWptKmlMode = 'WPT';
1033
 
1034
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1035
    $map_canvas->delete('Wp-WptKml');
1036
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1037
                             '-tags' => 'Wp-WptKml',
1038
                             '-anchor' => 'nw',
1039
                             '-image'  => 'WpWpt-Foto',
1040
                             );
1041
 
1042
    # delete Kml-Track from canvas
1043
    &KmlHide();
1044
 
1045
    # Show waypoints, WP resend required
1046
    $WaypointsModified = 1;
1047
 
1048
    if ( $PlayerRandomMode ne 'MAP' )
1049
        {
1050
        &WpRedrawIcons()
1051
        }
1052
    if ( $PlayerRandomMode eq 'STD' )
1053
        {
1054
        &WpRedrawLines()
1055
        }
1056
 
1057
    }
1058
 
1059
 
1060
# Activate Recording mode
1061
sub PlayerRecordOn
1062
    {
1063
    $PlayerRecordMode = "REC";
1064
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "Recording" );
1065
 
1066
    # Record new KML-Track
1067
    undef @KmlTargets;
1068
    $KmlPlayerIndex = 0;
1069
 
1070
    # delete Kml-Track from canvas
1071
    &KmlHide();
1072
    }
1073
 
1074
# Deactivate Recording mode
1075
sub PlayerRecordOff
1076
    {
1077
    $PlayerRecordMode = "";
1078
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "" );
1079
    }
1080
 
1081
 
1082
# Hide Fox icon on canvas
1083
sub FoxHide()
1084
    {
1085
    $map_canvas->lower('Fox', 'Map');
1086
    }
1087
 
1088
# Show Fox icon on canvas
1089
sub FoxShow()
1090
    {
1091
    $map_canvas->raise('Fox', 'Target');
1092
    }
1093
 
1094
# Hide POI icon on canvas
1095
sub PoiHide()
1096
    {
1097
    $map_canvas->lower('POI', 'Map');
1098
    }
1099
 
1100
# Show POI icon on canvas
1101
sub PoiShow()
1102
    {
1103
    $map_canvas->raise('POI', 'Track-Antenna');
1104
    }
1105
 
1106
 
1107
#
1108
# System Messages
1109
#
1110
 
1111
# Init Messages for a Subsystem/timer
1112
sub MkMessageInit ()
1113
    {
1114
    my ($Id) = @_;
1115
 
1116
    $MkMessages{$Id} = [];
1117
    }
1118
 
1119
 
1120
# Register message
1121
sub MkMessage ()
1122
    {
1123
    my ($Message, $Id) = @_;
1124
 
1125
    push @{$MkMessages{$Id}}, $Message;
1126
    }
1127
 
1128
 
1129
# show registered messages
1130
sub MkMessageShow()
1131
    {
1132
    my @Messages;
1133
    my $MsgLines = 0;
1134
    my $MaxMsgLen = 0;
1135
 
1136
    # Collect Messages of each category
1137
    foreach my $Id (keys %MkMessages)
1138
        {
1139
        foreach $i ( 0 .. $#{$MkMessages{$Id}} )
1140
            {
1141
            my $Msg = $MkMessages{$Id}[$i];
1142
            push @Messages, $Msg;
1143
 
1144
            $MsgLines ++;
1145
 
1146
            my $Len = length $Msg;
1147
            if ( $Len > $MaxMsgLen )
1148
                {
1149
                $MaxMsgLen = $Len;
1150
                }
1151
            }
1152
        }
1153
 
1154
    $map_canvas->delete('Message-Balloon');  # delete old Balloon
1155
 
1156
    if ( $MsgLines > 0 )
1157
        {
1158
        # draw Balloon
1159
        my @MsgBalloon = ( $MkPos_x ,                       $MkPos_y,
1160
                           $MkPos_x + 30 ,                  $MkPos_y + 40,
1161
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 40,
1162
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 44 + $MsgLines * 20,
1163
                           $MkPos_x + 20,                   $MkPos_y + 44 + $MsgLines * 20,
1164
                           $MkPos_x + 20,                   $MkPos_y + 40,
1165
                           $MkPos_x,                        $MkPos_y,
1166
                          );
1167
 
1168
        $map_canvas->createPolygon( @MsgBalloon,
1169
                                    '-tags' => ['Message-Balloon', 'Message-BalloonBubble'],
1170
                                    '-fill' => 'yellow',
1171
                                    '-outline' => 'yellow',
1172
                                    '-width' => 1,
1173
                                  );
1174
        # draw Messages
1175
        my $MsgLine = 1;
1176
        foreach my $Msg (@Messages)
1177
            {
1178
            $map_canvas->createText ( $MkPos_x + 25, $MkPos_y + 32 + $MsgLine * 20 ,
1179
                                      '-tags' => ['Message-Balloon', 'Message-BalloonText'],
1180
                                      '-text' => $Msg,
1181
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1182
                                      '-fill' => 'blue',
1183
                                      '-anchor' => 'w',
1184
                                        );
1185
            $MsgLine ++;
1186
            }
1187
 
1188
 
1189
        $map_canvas->lower('Message-Balloon', 'MK-Arrow');
1190
        }
1191
 
1192
    }
1193
 
1194
 
1195
# Show Balloon, when arproaching Target
1196
sub TargetMessageShow ()
1197
    {
1198
    $map_canvas->delete('Target-Balloon');  # delete old Balloon
1199
 
1200
    if ( $OperationMode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1  and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
1201
        {
1202
        my $BalloonLines = 0;
1203
        $ColorBalloon = "blue";
1204
        my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
1205
        my $Wp = $Waypoints[$MkOsd{'WaypointIndex'}];
1206
 
1207
        # Holdtime Wp-Player Mode
1208
        if ( $WpPlayerHoldtime >= 0  and  $PlayerWptKmlMode  eq "WPT" )
1209
            {
1210
            # Holdtime
1211
            $ColorBalloon = 'red';
1212
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($WpPlayerHoldtime / 2  + 0.5) );
1213
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
1214
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
1215
                                      '-text' => $HoldTime,
1216
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1217
                                      '-fill' => $ColorBalloon,
1218
                                      '-anchor' => 'w',
1219
                                    );
1220
            $BalloonLines ++;
1221
            }
1222
 
1223
        # Holdtime WPT-Mode
1224
        if ( &MkTargetReached()  and  $OperationMode eq "WPT" )
1225
            {
1226
            # Holdtime from MK
1227
            $ColorBalloon = 'red';
1228
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($MkOsd{'TargetHoldTime'} + 0.5) );
1229
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
1230
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
1231
                                      '-text' => $HoldTime,
1232
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1233
                                      '-fill' => $ColorBalloon,
1234
                                      '-anchor' => 'w',
1235
                                    );
1236
            $BalloonLines ++;
1237
            }
1238
 
1239
        # Tolerance Radius Player Mode
1240
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" and $PlayerWptKmlMode eq "WPT" )
1241
            {
1242
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'});
1243
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
1244
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
1245
                                      '-text' => $WpTolerance,
1246
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1247
                                      '-fill' => $ColorBalloon,
1248
                                      '-anchor' => 'w',
1249
                                    );
1250
            $BalloonLines ++;
1251
            }
1252
 
1253
        # Tolerance WPT-Mode
1254
        if ( &MkIsWptMode  and  $OperationMode eq "WPT" )
1255
            {
1256
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'} );
1257
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
1258
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
1259
                                      '-text' => $WpTolerance,
1260
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1261
                                      '-fill' => $ColorBalloon,
1262
                                      '-anchor' => 'w',
1263
                                    );
1264
            $BalloonLines ++;
1265
            }
1266
 
1267
        # Distance to Target
1268
        my $Dist = int ($MkOsd{'TargetPosDev_Dist'} /10 + 0.5);
1269
        $map_canvas->createText ( $T_x + 25, $T_y - 80,
1270
                                  '-tags' => ['Target-Balloon', 'Target-BalloonText'],
1271
                                  '-text' => sprintf ("%5s %3d m", "DST:", $Dist) ,
1272
                                  '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
1273
                                  '-fill' => $ColorBalloon,
1274
                                  '-anchor' => 'w',
1275
                                );
1276
        $BalloonLines ++;
1277
 
1278
        if ( $BalloonLines >= 1 )
1279
            {
1280
            # draw Balloon
1281
            my @TargetBalloon = ( $T_x ,      $T_y,
1282
                                  $T_x + 30,  $T_y - (3 - $BalloonLines) * 20 -27,
1283
                                  $T_x + 150, $T_y - (3 - $BalloonLines) * 20 -27 ,
1284
                                  $T_x + 150, $T_y - 93,
1285
                                  $T_x + 20,  $T_y - 93,
1286
                                  $T_x + 20,  $T_y - (3 - $BalloonLines) * 20 -27,
1287
                                  $T_x,       $T_y,
1288
                                );
1289
 
1290
            $map_canvas->createPolygon( @TargetBalloon,
1291
                                        '-tags' => ['Target-Balloon', 'Target-BalloonBubble'],
1292
                                        '-fill' => 'lightgray',
1293
                                        '-outline' => 'yellow',
1294
                                        '-width' => 1,
1295
                                       );
1296
            }
1297
 
1298
 
1299
        $map_canvas->lower('Target-Balloon', 'MK-Home-Line');
1300
        $map_canvas->lower('Target-BalloonBubble', 'Target-BalloonText');
1301
        }
1302
    }
1303
 
1304
 
1305
#
1306
# Airfield border
1307
#
1308
 
1309
# Are two segments A(a1/a2), B(b1/b2) and C(c1/c2), D(d1/d2) crossing ?
1310
sub SegmentCross()
1311
    {
1312
    my ( $a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) = @_;
1313
 
1314
    # segment C/D ist vertical, avoid div/0
1315
    if ( $c1 == $d1 )
1316
        {
1317
        $d1 += 0.00001;
1318
        }
1319
 
1320
    my $n = ($b1 - $a1) * ($d2 - $c2) - ($b2 - $a2) * ($d1 - $c1);
1321
    if ( $n == 0.0 )
1322
        {
1323
        # AB und CD sind parallel
1324
        return 0;
1325
        }
1326
 
1327
    my $s = ( ($c1 - $a1) * ($d2 - $c2) - ($c2 - $a2) * ($d1 - $c1) ) / $n;
1328
    my $t = ( $a1 - $c1 + $s * ($b1 - $a1) ) / ( $d1 - $c1 );
1329
    if ( $s >= 0.0  and  $s <= 1.0  and  $t >= 0.0  and  $t <= 1.0 )
1330
        {
1331
        # beide Strecken kreuzen sich
1332
 
1333
        # Schnittpunkt: s_x, s_y
1334
        my $s_x = $a1 + $s * ( $b1 - $a1 );
1335
        my $s_y = $a2 + $s * ( $b2 - $a2 );
1336
 
1337
        return 1;
1338
        }
1339
 
1340
    # beide Strecken kreuzen sich nicht
1341
    return 0;
1342
    }
1343
 
1344
 
1345
# How often does a segment A(a1,a2), B(b1,b2) cross the polygon?
1346
sub SegmentPolygonCross()
1347
    {
1348
    my ( $a1, $a2, $b1, $b2, $Polygon) = @_;
1349
 
1350
    my $Cross = 0;
1351
    my $PolyCnt = scalar @{$Polygon};
1352
    my $PolyPointCnt = $PolyCnt / 2;
1353
 
1354
    my $i = 0;
1355
    for ( $p=0; $p < $PolyPointCnt; $p++ )
1356
        {
1357
        my $c1 = ${$Polygon}[$i++];
1358
        my $c2 = ${$Polygon}[$i++];
1359
 
1360
        if ( $i >= $PolyCnt ) { $i = 0; }
1361
 
1362
        my $d1 = ${$Polygon}[$i];
1363
        my $d2 = ${$Polygon}[$i+1];
1364
 
1365
        # map calibration offsets
1366
        $c1 -= $Map{'Offset_x'};
1367
        $c2 += $Map{'Offset_y'};
1368
        $d1 -= $Map{'Offset_x'};
1369
        $d2 += $Map{'Offset_y'};    
1370
 
1371
        if ( &SegmentCross($a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) )
1372
            {
1373
            $Cross ++;
1374
            }
1375
        }
1376
 
1377
    return $Cross;
1378
    }
1379
 
1380
 
1381
# Is point A inside airfield border?
1382
sub IsInsideBorder()
1383
    {
1384
    my ($a1, $a2) = @_;
1385
 
1386
    if ( scalar @Map{'Border'} == 0 )
1387
        {
1388
        # no border defined, always inside
1389
        return 1;
1390
        }
1391
 
1392
    my $Cross = &SegmentPolygonCross (-10, -10, $a1, $a2, @Map{'Border'} );
1393
 
1394
    # Ungerade Anzahl Kreuzungen: Inside
1395
    return ( $Cross % 2 );
1396
    }
1397
 
1398
 
1399
 
1400
# Is segment A, B crossing the airfield border?
1401
sub IsCrossingBorder()
1402
    {
1403
    my ($a1, $a2, $b1, $b2) = @_;
1404
 
1405
    if ( scalar @Map{'Border'} == 0 )
1406
        {
1407
        # no border defined, always not crossing
1408
        return 0;
1409
        }
1410
 
1411
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
1412
 
1413
    return ( $Cross > 0 );
1414
    }
1415
 
1416
 
1417
# How often is segment A, B crossing the airfield border?
1418
sub CrossingBorderCount()
1419
    {
1420
    my ($a1, $a2, $b1, $b2) = @_;
1421
 
1422
    if ( scalar @Map{'Border'} == 0 )
1423
        {
1424
        # no border defined, not crossing
1425
        return 0;
1426
        }
1427
 
1428
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
1429
 
1430
    return ( $Cross );
1431
    }
1432
 
1433
 
1434
# check, if Target is reachable my MK
1435
sub IsTargetReachable()
1436
    {
1437
    my ($T_x, $T_y) = @_;
1438
 
1439
    my $MkIsInside = &IsInsideBorder($MkPos_x, $MkPos_y);
1440
    my $TargetIsInside = &IsInsideBorder($T_x, $T_y);
1441
    my $MkTargetCrossingCount = &CrossingBorderCount($MkPos_x, $MkPos_y, $T_x, $T_y);
1442
 
1443
    if ( ($MkIsInside  and  $MkTargetCrossingCount == 0 )  or
1444
         (! $MkIsInside  and  $TargetIsInside  and  $MkTargetCrossingCount == 1) )
1445
        {
1446
        # Target is reachable
1447
        return 1;
1448
        }
1449
 
1450
    # Target is not reachable
1451
    return 0;
1452
    }
1453
 
1454
 
1455
 
1456
#
1457
# Configuration and data-visualisation
1458
#
1459
 
1460
# Display or Modify Hash
1461
sub DisplayHash()
1462
    {
1463
    my ($hrefData, $Titel, $Mode) = @_;
1464
 
1465
    # $Mode: Display, Edit, Waypoint, Refresh
1466
 
1467
    my %Id;
1468
    my $Label;
1469
    my $Value;
1470
 
1471
    # Neues Fenster aufmachen
1472
    my $popup = $main->Toplevel();
1473
    $popup->title($Titel);
1474
 
1475
    # Buttons
1476
    my $popup_button = $popup->Frame() -> pack('-side' => 'bottom',
1477
                                               '-expand' => 'y',
1478
                                               '-anchor' => 's',
1479
                                               '-padx' => 5,
1480
                                               '-pady' => 5,
1481
                                               );
1482
    $popup_button->Button('-text'    => 'Schließen',
1483
                          '-command' => sub
1484
        {
1485
        if ( $Mode =~ /edit/i  and  $Mode =~ /waypoint/i )
1486
            {
1487
            $WaypointsModified = 1;            
1488
            &WpRedrawLines();
1489
            &WpRedrawIcons();
1490
            }
1491
 
1492
        $popup->destroy()
1493
        })->pack;
1494
 
1495
    # Frame mit den Labels
1496
    my $popup_label = $popup->Frame() -> pack('-side' => 'left',
1497
                                              '-expand' => 'y',
1498
                                              '-anchor' => 'w',
1499
                                              '-padx' => 10,
1500
                                              '-pady' => 10,
1501
                                              );
1502
    # Labels anzeigen                    
1503
    foreach $Label ( sort keys %{$hrefData})
1504
        {
1505
        if ( $Translate{$Label} ne "" )
1506
            {
1507
            $Label = $Translate{$Label};
1508
            }
1509
 
1510
        $popup_label->Label ('-text' => $Label,
1511
                             '-width' => 25,
1512
                             '-anchor' => 'w',
1513
                             ) -> pack();
1514
        }
1515
 
1516
    # Frame mit den Daten
1517
    my $popup_values = $popup->Frame() -> pack('-side' => 'left',
1518
                                               '-expand' => 'y',
1519
                                               '-anchor' => 'w',
1520
                                               '-padx' => 10,
1521
                                               '-pady' => 10,
1522
                                               );
1523
    # Daten anzeigen
1524
    foreach $Value ( sort keys %{$hrefData})
1525
        {                              
1526
        if ( $Mode =~ /display/i )
1527
            {
1528
            # Display
1529
            $Id{$Value} = $popup_values->Label ('-text' => ${$hrefData}{$Value},
1530
                                                '-width' => 20,
1531
                                                '-anchor' => 'e',
1532
                                                '-relief' => 'sunken',
1533
                                                ) -> pack();
1534
            }
1535
        if ( $Mode =~ /edit/i )
1536
            {
1537
            # Edit
1538
            $Id{$Value} = $popup_values->Entry ('-textvariable' => \${$hrefData}{$Value},
1539
                                                '-exportselection' => '1',
1540
                                                '-width' => 20,
1541
                                                '-relief' => 'sunken',
1542
                                               ) -> pack();
1543
            if ( $Mode =~ /waypoint/i )
1544
                {
1545
                # einige Waypoint-Felder nicht aenderbar einstellen
1546
                if ( "MapX MapY Pos_Lat Pos_Lon Tag" =~ /$Value/i )
1547
                    {
1548
                    $Id{$Value}->configure('-state' => 'disabled', );
1549
                    }
1550
                }      
1551
            }
1552
        }
1553
 
1554
    if ( $Mode =~ /refresh/i )
1555
        {
1556
        # Timer: 0.1s
1557
        $popup_values->repeat (100, sub
1558
            {
1559
            # Datenfelder alle 100ms aktualisieren
1560
 
1561
            my $BgColor = 'white';
1562
            if ( $Mode =~ /heartbeat/i )
1563
                {
1564
                $BgColor = 'red';
1565
                if ( &MkOsdIsValid() )
1566
                    {
1567
                    # gültige daten vom MK
1568
                    $BgColor = 'white';
1569
                    }
1570
                }
1571
 
1572
            foreach $Value ( sort keys %{$hrefData} )
1573
                {
1574
                # Eingebbare Waypoint-Felder nicht aktualisieren
1575
                if ( ! ($Mode =~ /waypoint/i  and
1576
                        "Event_Flag Heading ToleranceRadius HoldTime Pos_Alt" =~ /$Value/i) )
1577
                    {                
1578
                    $Id{$Value}->configure('-text' => ${$hrefData}{$Value},
1579
                                           '-background' => "$BgColor",
1580
                                          );
1581
                    }
1582
                }
1583
            });
1584
        }
1585
 
1586
    return 0;
1587
    }
1588
 
1589
 
1590
 
1591
# Konfigurationsdatei mkcockpit.xml im Popup-Fenster editieren
1592
sub Configure()
1593
    {
1594
 
1595
    # Copy Cfg-Hash for editing
1596
    my $CfgEdit = {%{$Cfg}};
1597
    foreach $key (keys %{$Cfg})
1598
        {
1599
        if ( ref $Cfg->{$key} )
1600
            {
1601
            $CfgEdit->{$key} = {%{$Cfg->{$key}}};
1602
            }
1603
        }
1604
 
1605
    # Neues Fenster aufmachen
1606
    my $popup = $main->Toplevel();
1607
    $popup->title("Einstellungen - $XmlConfigFile");
1608
 
1609
    # jede Sektion in einem Tab anzeigen
1610
    my $book = $popup->NoteBook()->pack( -fill=>'both', -expand=>1 );
1611
    foreach $key (sort keys %{$CfgEdit})
1612
        {    
1613
        if ( ! ref $CfgEdit->{$key} )
1614
            {
1615
            next;
1616
            }
1617
 
1618
        my $TabLabel = "$key";
1619
        if ( $Translate{$key} ne "" )
1620
                {
1621
                $TabLabel = $Translate{$key};
1622
                }
1623
 
1624
        my $Tab = $book->add( "$key", -label=>"$TabLabel", );
1625
 
1626
        # Frame fuer Buttons
1627
        my $book_button = $Tab->Frame() -> pack('-side' => 'bottom',
1628
                                                '-expand' => 'y',
1629
                                                '-anchor' => 's',
1630
                                                '-padx' => 5,
1631
                                                '-pady' => 5,
1632
                                                );
1633
 
1634
        $book_button->Button('-text'    => 'OK',
1635
                             '-width' => '10',
1636
                             '-command' => sub
1637
            {
1638
            # Copy back CfgEdit-Hash
1639
            $Cfg = {%{$CfgEdit}};
1640
            foreach $key (keys %{$CfgEdit})
1641
                {
1642
                if ( ref $CfgEdit->{$key} )
1643
                    {
1644
                    $Cfg->{$key} = {%{$CfgEdit->{$key}}};
1645
                    }
1646
                }
1647
 
1648
            # set new timestamp
1649
            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
1650
            my $TimeStamp = sprintf ("%04d%02d%02d-%02d%02d%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
1651
            $Cfg->{'CreationDate'} = $TimeStamp;
1652
 
1653
            # set MkCockpit Version
1654
            $Cfg->{'Version'} =  $Version{'mkcockpit.pl'};
1655
 
1656
            # Cfg in mkcockpit.xml speichern
1657
            &XMLout ($Cfg,
1658
                     'OutputFile' => $XmlConfigFile,
1659
                     'AttrIndent' => '1',
1660
                     'RootName' => 'mkcockpit-Config',
1661
                    );
1662
 
1663
            $popup->destroy();
1664
            } )->pack ('-side' => 'left',
1665
                       '-expand' => 'y',
1666
                       '-anchor' => 's',
1667
                       '-padx' => 5,
1668
                       '-pady' => 5,
1669
                      );
1670
 
1671
        $book_button->Button('-text'    => $Translate{'Abort'},
1672
                             '-width' => '10',
1673
                             '-command' => sub { $popup->destroy() },
1674
                            )->pack ('-side' => 'left',
1675
                                     '-expand' => 'y',
1676
                                     '-anchor' => 's',
1677
                                     '-padx' => 5,
1678
                                     '-pady' => 5,
1679
                                     );
1680
        $book_button->Label ('-text' => $Translate{'RestartRequired'},
1681
                             '-anchor' => 'w',
1682
                             '-foreground' => 'red',
1683
                            ) ->pack ('-side' => 'left',
1684
                                      '-expand' => 'y',
1685
                                      '-anchor' => 's',
1686
                                      '-padx' => 10,
1687
                                      '-pady' => 5,
1688
                                      );
1689
 
1690
        # Frame for label and data
1691
        my $popup_cfg = $Tab->Frame() -> pack('-side' => 'left',
1692
                                              '-expand' => 'y',
1693
                                              '-anchor' => 'w',
1694
                                              '-padx' => 10,
1695
                                              '-pady' => 10,
1696
                                              );
1697
 
1698
        # Eingabefelder/Optionmenu mit Daten anzeigen
1699
        $Row = 0;
1700
        foreach $Entry ( sort keys %{$CfgEdit->{$key}})
1701
            {                          
1702
            # Label
1703
            my $Label = $Entry;
1704
            if ( $Translate{$Label} ne "" )
1705
                {
1706
                $Label = $Translate{$Label};
1707
                }
1708
 
1709
            $popup_cfg->Label (-text => $Label,
1710
                               -width => 35,
1711
                               -anchor => 'w',
1712
                               )->grid (-row    => $Row,
1713
                                        -column => 0,
1714
                                       );
1715
            # Values
1716
            if ( defined $CfgOpt{$Entry}[0] )
1717
                {
1718
                # Combo Box with optiones defined in libcfgopt.pl
1719
 
1720
                my $cbo = $popup_cfg->BrowseEntry( -label => "",
1721
                                                   -variable => \$CfgEdit->{$key}->{$Entry},
1722
                                                   -width => 27,
1723
                                                   -relief => 'sunken'
1724
                                                 )->grid (-row    => $Row,
1725
                                                          -column => 1,
1726
                                                          -sticky => 'w',
1727
                                                         );
1728
                # add options
1729
                $cbo->insert("end", @{ $CfgOpt{$Entry} });
1730
                }
1731
            else
1732
                {
1733
                # Text entry
1734
 
1735
                $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
1736
                                    -exportselection => '1',
1737
                                    -width => 30,
1738
                                    -relief => 'sunken',
1739
                                   )->grid (-row    => $Row,
1740
                                            -column => 1,
1741
                                            -sticky => 'e',
1742
                                           );
1743
                }
1744
            $Row ++;
1745
            }
1746
        }
1747
    }
1748
 
1749
 
1750
1;
1751
 
1752
__END__