Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
834 - 1
###############################################################################
2
#
3
# libmapdef.pl -  Map definition
4
#
5
## Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
6
#
7
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
8
#
9
# Es ist Ihnen gestattet: 
10
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
11
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
12
# 
13
# Zu den folgenden Bedingungen:
14
#     * Namensnennung.
15
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
16
#     * Keine kommerzielle Nutzung.
17
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
18
#     * Weitergabe unter gleichen Bedingungen.
19
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
20
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
21
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
22
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
23
# 
24
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
25
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
26
# 
27
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
28
# des Rechteinhabers dazu erhalten.
29
# 
30
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
31
# 
32
# Weitere Details zur Lizenzbestimmung gibt es hier:
33
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
34
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
35
#
36
###############################################################################
37
##
38
# 2009-03-06 0.0.1 rw created
39
# 2009-04-01 0.1.0 rw RC1
40
# 2009-04-18 0.1.1 rw Select default map, if configured map does not exist
41
# 2009-07-22 0.1.2 rw Offset_x and Offset_y for adjustment of map calibration
42
# 2009-08-15 0.1.3 rw Tracking Antenne Home position added
43
#                     Player home position added
44
#                     Read map definition from XML file
45
# 2009-09-29 0.1.4 rw Read map definition from KML file (GE import)
46
#                     Allow Config lines in map definition file (like mkcockpit.xml)
47
# 2009-11-08 0.1.5 rw Avoid div/0, if P1=P2
48
# 2010-05-16 0.1.6 rw Check empty Border in XML
49
# 2010-09-09 0.1.7 rw Load JPEG/PNG containig Geo-Information
50
#                     rename map/map.pl --> libmapdef.pl
51
# 2010-09-19 0.1.8 rw Create Map from Google/OSM download
52
# 2010-10-04 0.1.9 rw Google Maps entfernt wg. rechtlicher Bedenken
53
# 2010-10-09 0.1.10 rw Zoom und Reload-Checkbox
54
#
55
###############################################################################
56
 
57
$Version{'libmapdef.pl'} = "0.1.10 - 2010-10-09";
58
 
59
use Math::Trig;
60
use XML::Simple;                  # http://search.cpan.org/dist/XML-Simple-2.18/lib/XML/Simple.pm
61
use Image::ExifTool qw(:Public);  # http://search.cpan.org/~exiftool/Image-ExifTool-8.25/lib/Image/ExifTool.pod
62
use LWP::Simple;                  # http://search.cpan.org/~gaas/libwww-perl-5.836/lib/LWP/Simple.pm
63
use File::Path qw(make_path);
64
 
65
if ( $Cfg->{'map2'}->{'ImageMagickInstalled'} =~ /y/i )
66
    {
67
    require Image::Magick;   # comes with ImageMagick installation
68
 
69
    # requires ImageMagick 6.5 for Activestate Perl 5.10:
70
    #    http://image_magick.veidrodis.com/image_magick/binaries/ImageMagick-6.5.9-9-Q8-windows-dll.exe
71
    #    http://image_magick.veidrodis.com/image_magick/binaries/ImageMagick-6.5.9-9-Q16-windows-dll.exe
72
    # Beim Installieren angeben: Install PerlMagick for Activestate Perl 5.10.1 Buld 1007
73
    #
74
    # Theorie: http://www.imagemagick.org/
75
    #          http://www.imagemagick.org/script/perl-magick.php         
76
    }
77
 
78
use libgemaps;         # Theorie: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
79
 
80
# Load maps from filesystem (XML, KML, JPEG/PNG)
81
sub MapDefLoad()
82
    {
83
    %Maps =
84
        (
85
        Default => {
86
            'Name' => "Default",
87
            'File' => 'default-800.gif',
88
            # 'Size_X' => '800',
89
            # 'Size_Y  => '600',
90
 
91
            'P1_x' => '71',               # calibration P1, P2
92
            'P1_y' => '472',
93
            'P2_x' => '500',
94
            'P2_y' => '103',
95
            'P1_Lat' => '48.856253',
96
            'P1_Lon' => '2.3500000',
97
            'P2_Lat' => '54.090153',
98
            'P2_Lon' => '12.133249',
99
 
100
            # 'Offset_x' =>  5,             # Optional Pixel offset MK to right
101
            # 'Offset_y' =>  5,             # Optional pixel offset MK to top
102
 
103
            # 'Home_Lat' => '54.090153',    # Optional home position for player
104
            # 'Home_Lon' => '12.133249',    # Optional home position for player
105
 
106
            # 'Poi_Lat' => '54.090153',    # Optional POI position for player
107
            # 'Poi_Lon' => '12.133249',    # Optional POI position for player
108
 
109
            # 'Track_Lat' => '49.685333',   # Optional Tracking Antenna pos
110
            # 'Track_Lon' => '10.950134',   # Optional Tracking Antenna pos
111
            # 'Track_Alt' => '500',         # Optional Tracking Antenna altitude
112
            # 'Track_Bearing' =>  10,       # Optional Tracking antenne direction
113
 
114
            #  'Border' => [ 555, 430,       # airfield border
115
            #              516, 555,
116
            #              258, 555,
117
            #              100, 300,
118
            #              580, 260,
119
            #              530, 94,
120
            #              627, 130,
121
            #              735, 300,
122
            #              680, 400,
123
            #              757, 470,
124
            #              720, 515,
125
            #              575, 420,
126
            #            ],
127
            },
128
        );
129
 
130
 
131
    #
132
    # load additional Maps from XML files
133
    #
134
    my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map";
135
    if ( -d $MapDir )
136
        {
137
        opendir DIR, $MapDir;
138
        my @Files = readdir DIR;
139
        @Files = grep /\.xml$/, @Files;
140
        closedir DIR;
141
 
142
        foreach $Xml (@Files)
143
            {
144
            my $MapConfigFile = "$MapDir/$Xml";
145
            if ( -f $MapConfigFile )
146
                {
147
                my $XmlMap = XMLin($MapConfigFile);
148
 
149
                foreach $Location (keys %{$XmlMap})
150
                    {
151
                    foreach $Key (keys %{$XmlMap->{$Location}} )
152
                        {
153
                        my $Value = $XmlMap->{$Location}->{$Key};
154
                        if ( $Key =~ /Border/i )
155
                            {
156
                            $Value =~ s/\s//g;
157
                            if ( $Value ne "" )
158
                                {
159
                                my @Border = split ',', $Value;
160
                                @{$Maps{$Location}->{$Key}}  = @Border;
161
                                }
162
                            }
163
                        else
164
                            {
165
                            $Maps{$Location}->{$Key}  = $Value;
166
                            }
167
                        }
168
                    }
169
                }
170
            }
171
        }
172
 
173
 
174
    #
175
    # load additional Maps from KML files
176
    #
177
    my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map";
178
    if ( -d $MapDir )
179
        {
180
        opendir DIR, $MapDir;
181
        my @Files = readdir DIR;
182
        @Files = grep /\.kml$/, @Files;
183
        closedir DIR;
184
 
185
        foreach $Kml (@Files)
186
            {
187
            my $MapConfigFile = "$MapDir/$Kml";
188
            if ( -f $MapConfigFile )
189
                {
190
                my $KmlMap = XMLin($MapConfigFile);
191
 
192
                my $Name = $KmlMap->{'Document'}->{'Folder'}->{'name'};
193
                my $Desc = $KmlMap->{'Document'}->{'Folder'}->{'description'};
194
 
195
                $Maps{$Name}->{'Name'} = $Name;
196
 
197
                # Airfield Border
198
                my $Border = "";
199
                my $bBorder = 0;
200
 
201
                # parse config lines
202
                @DescLines = split '\n', $Desc;
203
                foreach $Line (@DescLines)
204
                    {
205
                    if ( $bBorder )
206
                        {
207
                        # collect border lines
208
                        if ( $Line =~ /=/i )
209
                            {
210
                            # New keyword found. End of multi-line border config
211
                            $bBorder = 0;
212
                            }
213
                        else
214
                            {
215
                            $Border = "$Border" . "$Line";
216
                            }
217
                        }
218
 
219
                    if ( $Line =~ /\s*(\S*)\s*=\s*(.*)/i)
220
                        {
221
                        my $Key = $1;
222
                        my $Value = $2;
223
                        chomp $Value;
224
 
225
                        # search for border keyword
226
                        if ($Key =~ /border/i )
227
                            {
228
                            $Border = $Value;
229
                            $bBorder = 1;
230
                            }
231
                        else
232
                            {
233
                            $Maps{$Name}->{$Key} = $Value;
234
                            }
235
                        }
236
                    }
237
 
238
                if ( $Border ne "" )
239
                    {
240
                    $Border =~ s/\s//g;
241
                    my @Border = split ',', $Border;
242
                    @{$Maps{$Name}->{'Border'}}  = @Border;
243
                    }
244
 
245
                # P1 calibration point
246
                my $P1 = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P1'}->{'Point'}->{'coordinates'};
247
                my $P1Desc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P1'}->{'description'};
248
                ($Maps{$Name}->{'P1_Lon'}, $Maps{$Name}->{'P1_Lat'}) = split ',', $P1;
249
                if ( $P1Desc =~ /\s*x\s*=\s*(\d*)/i)  { $Maps{$Name}->{'P1_x'} = $1; }  # x=nnn
250
                if ( $P1Desc =~ /\s*y\s*=\s*(\d*)/i)  { $Maps{$Name}->{'P1_y'} = $1; }  # y=nnn
251
 
252
                # P2 calibration point
253
                my $P2 = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P2'}->{'Point'}->{'coordinates'};
254
                my $P2Desc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P2'}->{'description'};
255
                ($Maps{$Name}->{'P2_Lon'}, $Maps{$Name}->{'P2_Lat'}) = split ',', $P2;
256
                if ( $P2Desc =~ /\s*x\s*=\s*(\d*)/i)  { $Maps{$Name}->{'P2_x'} = $1; }  # x=nnn
257
                if ( $P2Desc =~ /\s*y\s*=\s*(\d*)/i)  { $Maps{$Name}->{'P2_y'} = $1; }  # y=nnn
258
 
259
                # Home position
260
                if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'visibility'} ne "0" )
261
                    {
262
                    my $Home = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'Point'}->{'coordinates'};
263
                    my $HomeDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'description'};
264
                    ($Maps{$Name}->{'Home_Lon'}, $Maps{$Name}->{'Home_Lat'}) = split ',', $Home;
265
                    }
266
 
267
                # POI position
268
                if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'visibility'} ne "0" )
269
                    {
270
                    my $Poi = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'Point'}->{'coordinates'};
271
                    my $PoiDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'description'};
272
                    ($Maps{$Name}->{'Poi_Lon'}, $Maps{$Name}->{'Poi_Lat'}) = split ',', $Poi;
273
                    }
274
 
275
                # Antenna tracker position
276
                if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'visibility'} ne "0" )
277
                    {
278
                    my $Track = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'Point'}->{'coordinates'};
279
                    my $TrackDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'description'};
280
                    ($Maps{$Name}->{'Track_Lon'}, $Maps{$Name}->{'Track_Lat'}) = split ',', $Track;
281
                    if ( $TrackDesc =~ /\s*Track_Alt\s*=\s*(\d*)/i)      { $Maps{$Name}->{'Track_Alt'} = $1; }      # Track_Alt=nnn
282
                    if ( $TrackDesc =~ /\s*Track_Bearing\s*=\s*(\d*)/i)  { $Maps{$Name}->{'Track_Bearing'} = $1; }  # Track_Bearing=nnn
283
                    }
284
 
285
                if ( $Maps{$Name}->{'P1_x'} == $Maps{$Name}->{'P2_x'}  and
286
                     $Maps{$Name}->{'P1_y'} == $Maps{$Name}->{'P2_y'} )
287
                    {
288
                    # Avoid div/0 if P1=P2
289
                    $Maps{$Name}->{'P1_x'} += 1;
290
                    }
291
                }
292
            }
293
        }
294
 
295
    #
296
    # load additional Maps from JPEG/PNG containing Geo-Information
297
    #
298
    my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map";
299
    if ( -d $MapDir )
300
        {
301
        opendir DIR, $MapDir;
302
        my @Files = readdir DIR;
303
        closedir DIR;
304
 
305
        my @ImgFiles;
306
        push @ImgFiles, grep /\.jpg$/, @Files;
307
        push @ImgFiles, grep /\.png$/, @Files;
308
 
309
        foreach $Image (@ImgFiles)
310
            {
311
            my $ImgFile = "$MapDir/$Image";
312
            if ( -f $ImgFile )
313
                {
314
                # take only image files which are not already used in %Maps
315
                $bUsed = "";
316
                foreach $Map (keys %Maps)
317
                    {
318
                    if ( $Maps{$Map}->{'File'} =~ /^${Image}$/i )
319
                        {
320
                        $bUsed = "X";
321
                        last;
322
                        }
323
                    }
324
 
325
                if ( $bUsed ne "X" )
326
                    {
327
                    # take image
328
                    my ($Width, $Height, $ImgInfo) = &GetImageInfo($ImgFile);
329
                    my $Comment = $$ImgInfo{'Comment'};
330
                    my $Name = substr ($Image, 0, -4);  # remove extension
331
 
332
                    # Image from Kopter-Tool
333
                    my @Fields = split ",", $Comment;
334
                    if ( $Fields[0] =~ /Geo-Information/i )
335
                        {
336
                        my ($RoLat, $RoLon) = split ":", $Fields[1];
337
                        my ($LoLat, $LoLon) = split ":", $Fields[2];
338
                        my ($RuLat, $RuLon) = split ":", $Fields[3];
339
                        my ($LuLat, $LuLon) = split ":", $Fields[4];
340
 
341
                        $Maps{$Name}->{'Name'} =  $Name;
342
                        $Maps{$Name}->{'File'} =  $Image;
343
                        $Maps{$Name}->{'P1_x'} =  0;
344
                        $Maps{$Name}->{'P1_y'} =  0;
345
                        $Maps{$Name}->{'P2_x'} =  $Width;
346
                        $Maps{$Name}->{'P2_y'} =  $Height;
347
                        $Maps{$Name}->{'P1_Lat'} =  $LoLat;
348
                        $Maps{$Name}->{'P1_Lon'} =  $LoLon;
349
                        $Maps{$Name}->{'P2_Lat'} =  $RuLat;
350
                        $Maps{$Name}->{'P2_Lon'} =  $RuLon;
351
                        }
352
 
353
                    # GeoMapTool Image for Mission Cockpit
354
                    my @Fields = split ";", $Comment;
355
                    foreach $Param (@Fields)
356
                        {
357
                        my ($Key, $Value) = split ":", $Param;
358
                        if ( $Key eq "P" )
359
                            {
360
                            my ($P1_x, $P1_y, $P2_x, $P2_y, $P1_Lat, $P1_Lon, $P2_Lat, $P2_Lon) = split ",", $Value;
361
                            $Maps{$Name}->{'P1_x'} =  $P1_x;
362
                            $Maps{$Name}->{'P1_y'} =  $P1_y;
363
                            $Maps{$Name}->{'P2_x'} =  $P2_x;
364
                            $Maps{$Name}->{'P2_y'} =  $P2_y;
365
                            $Maps{$Name}->{'P1_Lat'} =  $P1_Lat;
366
                            $Maps{$Name}->{'P1_Lon'} =  $P1_Lon;
367
                            $Maps{$Name}->{'P2_Lat'} =  $P2_Lat;
368
                            $Maps{$Name}->{'P2_Lon'} =  $P2_Lon;
369
 
370
                            $Maps{$Name}->{'File'} =  $Image;
371
                            $Maps{$Name}->{'Name'} =  $Name;
372
                            }
373
                        if ( $Key eq "Home" )
374
                            {
375
                            my ($Home_Lat, $Home_Lon) = split ",", $Value;
376
                            $Maps{$Name}->{'Home_Lat'} =  $Home_Lat;
377
                            $Maps{$Name}->{'Home_Lon'} =  $Home_Lon;
378
                            }
379
                        if ( $Key eq "Poi" )
380
                            {
381
                            my ($Poi_Lat, $Poi_Lon) = split ",", $Value;
382
                            $Maps{$Name}->{'Poi_Lat'} =  $Poi_Lat;
383
                            $Maps{$Name}->{'Poi_Lon'} =  $Poi_Lon;
384
                            }
385
                        if ( $Key eq "Border" )
386
                            {
387
                            my @Border = split ',', $Value;
388
                            @{$Maps{$Name}->{'Border'}}  = @Border;
389
                            }
390
                        }
391
                    }
392
                }
393
            }
394
        }
395
 
396
    # Die verwendete Karte
397
    &MapSetCurrentFromCfg();
398
    }
399
 
400
 
401
# Set $Maps{'Current'} from Cfg-Setting
402
sub MapSetCurrentFromCfg()
403
    {
404
 
405
    # Todo: Karte automatisch anhand der aktuellen GPS Position auswählen
406
 
407
    my $MapDefault = $Cfg->{'map'}->{'MapDefault'};
408
    if ( defined $Maps{$MapDefault} )
409
        {
410
        $Maps{'Current'} = $Maps{$MapDefault};
411
        }
412
    else
413
        {
414
        $Maps{'Current'} = $Maps{'Default'};
415
        print "Map \"$MapDefault\" not found in map.pl. Using \"Default\" map\n";
416
        }
417
 
418
    # optional map specific Cfg setup from map definition
419
    # Aktuell gültige Karte
420
    my %Map = %{$Maps{'Current'}};
421
 
422
    foreach $Key (keys %Map)
423
        {
424
        # Cfg:Section:Keyword
425
        if ( $Key =~ /^Cfg:(\S*):(\S*)/i )
426
            {
427
            $Section = $1;
428
            $Keyword = $2;
429
            $Cfg->{$Section}->{$Keyword} = $Map{$Key};
430
            }
431
        }
432
 
433
    # Get size of image
434
    my $ImgFile = "$Cfg->{'map'}->{'MapDir'}/$Map{'File'}";
435
    my ($Width, $Height) = &GetImageInfo($ImgFile);
436
    if ( $Maps{'Current'}->{'Size_X'} eq "" )
437
        {
438
        $Maps{'Current'}->{'Size_X'} = $Width;
439
        }
440
    if ( $Maps{'Current'}->{'Size_Y'} eq "" )
441
        {
442
        $Maps{'Current'}->{'Size_Y'} = $Height;
443
        }
444
 
445
    # Option list from map config dialog
446
    @{ $CfgOpt{MapDefault} } = sort keys %Maps
447
    }
448
 
449
 
450
# Get size and EXIF data of Image
451
sub GetImageInfo()
452
    {
453
    my ($File) = @_;
454
 
455
    my $ExifTool = new Image::ExifTool;
456
    my $ImgInfo = $ExifTool->ImageInfo($File);
457
 
458
    my $Width;
459
    my $Height;
460
 
461
    my $ImageSize  = $$ImgInfo{'ImageSize'};
462
    ($Width, $Height) = split "x", $ImageSize;
463
 
464
    if ( $Width eq "" )
465
        {
466
        $Width  = $$ImgInfo{'ImageWidth'};
467
        }
468
    if ( $Height eq "" )
469
        {
470
        $Height  = $$ImgInfo{'ImageHeight'};
471
        }
472
 
473
    if ( $Width eq "" )
474
        {
475
        $Width  = $$ImgInfo{'ExifImageWidth'};
476
        }
477
    if ( $Height eq "" )
478
        {
479
        $Height  = $$ImgInfo{'ExifImageHeight'};
480
        }
481
 
482
    return ($Width, $Height, $ImgInfo);
483
    }
484
 
485
 
486
#
487
# Compose a JPEG map for given Position and Bearing. Download Image-Tiles from OSM
488
#
489
 
490
# Create Map
491
sub MapCompose()
492
    {
493
    # Directory for tile cache
494
    my $TileDir = $Cfg->{'map2'}->{'MapTileCache'} || "map/_tile_cache";
495
 
496
    # JPEG filename
497
    my $MapFile = $Cfg->{'map'}->{'MapDir'} || "map";
498
    $MapFile = $MapFile . "/AutoMap.jpg";
499
 
500
    my $Zoom = $Cfg->{'map2'}->{'Zoom'}            || 18;
501
    my $OsmUrl = $Cfg->{'map2'}->{'OsmUrl'}       || "http://tile.openstreetmap.org/%z/%x/%y.png";
502
 
503
    my $Lat = $MkOsd{'CurPos_Lat'};
504
    my $Lon = $MkOsd{'CurPos_Lon'};
505
    my $Radius = $MkOsd{'OperatingRadius'} || 250;
506
    my $Bearing = $MkOsd{'CompassHeading'} || 0;
507
 
508
    my $OverscanTop   = $Cfg->{'map2'}->{'OverscanTop'}   || 100;
509
    my $OverscanBot   = $Cfg->{'map2'}->{'OverscanBot'}   || 23;
510
    my $OverscanLeft  = $Cfg->{'map2'}->{'OverscanLeft'}  || 110;
511
    my $OverscanRight = $Cfg->{'map2'}->{'OverscanRight'} || 110;
512
    my $ImageHeight   = $Cfg->{'map2'}->{'ImageHeight'}   || int($main->screenheight * 0.8);
513
    my $TileReload = "";
514
 
515
    #
516
    # GUI Popup - check and confirm parameter
517
    #
518
    my $popup = $main->Toplevel();
519
    $popup->title("Map Composer");
520
 
521
    my @Fields = ( # Label                    Variable
522
                   $Translate{MapLat},        \$Lat,
523
                   $Translate{MapLon},        \$Lon,
524
                   $Translate{MapRadius},     \$Radius,
525
                   $Translate{MapBearing},    \$Bearing,
526
                   $Translate{OverscanTop},   \$OverscanTop,
527
                   $Translate{OverscanBot},   \$OverscanBot,
528
                   $Translate{OverscanLeft},  \$OverscanLeft,
529
                   $Translate{OverscanRight}, \$OverscanRight,
530
                   $Translate{MapZoom},       \$Zoom,
531
                   $Translate{MapImageHight}, \$ImageHeight,
532
                  );
533
    my $Row = 0;
534
    my $i = 0;
535
    for $Field (0 .. $#Fields/2)
536
        {
537
        my $Label = $Fields[$i++];
538
        my $Var   = $Fields[$i++];
539
 
540
        $popup->Label (-text => $Label,
541
                       -width => 25,
542
                       -anchor => 'w',
543
                       )->grid (-row    => $Row,
544
                                -column => 0,
545
                                -sticky => 'w',
546
                                -padx   => 5,
547
                                -pady   => 1,
548
                               );
549
        $popup->Entry ( -textvariable => $Var,
550
                        -exportselection => '1',
551
                        -width => 40,
552
                        -relief => 'sunken',
553
                       )->grid (-row    => $Row,
554
                                -column => 1,
555
                                -sticky => 'w',
556
                                -padx   => 5,
557
                                -pady   => 1,
558
                                );
559
        $Row ++;
560
        }
561
 
562
    $popup->Checkbutton( -text => $Translate{'MapTileReload'},
563
                         -offvalue  => "",
564
                         -onvalue   => "X",
565
                         -variable  => \$TileReload,
566
                     )->grid (-row    => $Row++,
567
                              -column => 1,
568
                              -columnspan => 2,
569
                              -sticky => 'w',
570
                             );
571
 
572
    $MapComposeMessage = "";
573
    $popup->Label (-textvariable => \$MapComposeMessage,
574
                   -font => '-*-Arial-Bold-R-Normal--*-175-*',
575
                   -width => 40,
576
                   -anchor => 'w',
577
                   )->grid (-row    => $Row ++,
578
                            -column => 0,
579
                            -columnspan => 2,
580
                            -sticky => 'w',
581
                            -padx   => 5,
582
                            -pady   => 5,
583
                           );
584
 
585
    # Button: OK
586
    $popup->Button('-text'    => 'OK - Create Map',
587
                   '-width' => '20',
588
                   '-command' => sub
589
        {
590
        # check values
591
        if ( $Radius > 500 )        { $Radius = 500; }
592
        if ( $Zoom > 19 )           { $Zoom = 19; }
593
        if ( $OverscanTop > 125 )   { $OverscanTop = 125; }
594
        if ( $OverscanBot > 125 )   { $OverscanBot = 125; }
595
        if ( $OverscanLeft > 125 )  { $OverscanLeft = 125; }
596
        if ( $OverscanRight > 125 ) { $OverscanRight = 125; }
597
 
598
        if ( $Lat ne ""  and  $Lon ne "")
599
            {
600
            # Compose map - will block Mainloop while running
601
            &MapComposeOsm ($Lat, $Lon, $Radius, $Bearing, $Zoom, $OsmUrl, $TileDir, $MapFile, $TileReload,
602
                            $OverscanTop, $OverscanBot, $OverscanLeft, $OverscanRight, $ImageHeight);
603
 
604
            # Save new MapDefault
605
            my $CfgEdit = &CopyHash($Cfg);
606
            $CfgEdit->{'map'}->{'MapDefault'} = "AutoMap";
607
            &ConfigureSave( $XmlConfigFile, $Cfg, $CfgEdit);
608
 
609
            # Reload Map
610
            &MapDefLoad();
611
            &CanvasRedraw();
612
 
613
            $popup->destroy();
614
            }
615
        else
616
            {
617
            $MapComposeMessage = "Lat/Lon is invalid";
618
            $main->update;
619
            }
620
        } )->grid(-row    => $Row,
621
                  -column => 0,
622
                  -sticky => 'w',
623
                  -padx   => 15,
624
                  -pady   => 5,
625
                 );
626
 
627
    # Button: Abort
628
    $popup->Button('-text'    => $Translate{'Abort'},
629
                   '-width' => '10',
630
                   '-command' => sub { $popup->destroy() },
631
                  )->grid(-row    => $Row,
632
                          -column => 1,
633
                          -sticky => 'w',
634
                          -padx   => 15,
635
                          -pady   => 5,
636
                          );
637
    }
638
 
639
# Create JPEG Map from OSM using ImageMagick
640
sub MapComposeOsm()
641
    {
642
    my ($Lat, $Lon, $Radius, $Bearing, $Zoom, $UrlDesc, $TileDir, $MapFile, $TileReload,
643
        $OverscanTop, $OverscanBot, $OverscanLeft, $OverscanRight, $ImageHeight) = @_;
644
 
645
    # Calculate required tiles
646
    my ($LatN, $LonN) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 0);    # Factor 1.4 wg. rotation
647
    my ($LatE, $LonE) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 90);
648
    my ($LatS, $LonS) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 180);
649
    my ($LatW, $LonW) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 270);
650
    my @Tiles = &Google_Tiles($LatS, $LonW, $LatN, $LonE, $Zoom, 0, 'Partial');
651
 
652
    # download tiles and compose Image
653
    my $Columns = 0;
654
    my $LastRow = $Tiles[0]{'NAMEY'};
655
    my $ImgTiles = new Image::Magick;
656
    for ( $Tile = 0; $Tile <= $#Tiles; $Tile++ )
657
        {
658
        my $NameX = $Tiles[$Tile]{'NAMEX'};
659
        my $NameY = $Tiles[$Tile]{'NAMEY'};
660
 
661
        my $Dir = "$TileDir/$Zoom/$NameX";
662
        if ( ! -d $Dir )
663
            {
664
            make_path("$Dir");
665
            }
666
        my $File = sprintf ("%s/%d_%d_%d.jpg", $Dir, $Zoom, $NameX, $NameY);
667
        if ( ! -f $File  or  $TileReload eq "X" )
668
            {
669
            if ( defined $main)
670
                {
671
                $MapComposeMessage = sprintf "Download Tile %d/%d: %d %d %d", $Tile+1, $#Tiles+1, $NameX, $NameY, $Zoom;
672
                $main->update;
673
                }
674
 
675
            my $Url = $UrlDesc;
676
            $Url =~ s/%x/$NameX/i;
677
            $Url =~ s/%y/$NameY/i;
678
            $Url =~ s/%z/$Zoom/i;
679
 
680
            my $Status = getstore($Url, $File );   # download using LWP
681
            if ( $Status != RC_OK )
682
                {
683
                # use error tile
684
                $File = "icon/Tile256x256.jpg";
685
                }
686
            }
687
 
688
        $ImgTiles->Read("$File");
689
 
690
        # count tile Columns
691
        $Columns ++;
692
        if ( $NameY != $LastRow )
693
            {
694
            $Columns = 1;
695
            $LastRow = $NameY;
696
            }
697
        }
698
 
699
    if ( defined $main)
700
        {
701
        $MapComposeMessage = "Compose image ...";
702
        $main->update;
703
        }
704
 
705
    # Montage Tiles
706
    my $Width  = $ImgTiles->[0]->Get('columns');
707
    my $Height = $ImgTiles->[0]->Get('rows');
708
    my $Img = $ImgTiles->Montage( geometry => "$Width" . 'x' . "$Height" . '+0+0',
709
                                  tile     => $Columns,
710
                                );
711
    undef $ImgTiles;  # Tiles not needed any more
712
 
713
    #
714
    # Rotate around lat/lon, translate lat/lon to image center
715
    #
716
 
717
    my $Value = &Google_Tile_Factors($Zoom, "0") ; # Calculate Tile Factor
718
    my ($C_gy, $C_gx) = &Google_Coord_to_Pix( $Value, $Lat, $Lon );
719
 
720
    # OSM raw image pixel size
721
    my $Left_gx  = $Tiles[0]{'PXW'};
722
    my $Right_gx = $Tiles[$#Tiles]{'PXE'};
723
    my $Top_gy   = $Tiles[0]{'PYN'};
724
    my $Bot_gy   = $Tiles[$#Tiles]{'PYS'};
725
 
726
    # Center: Image pixel coord    
727
    my $C_x = $C_gx - $Left_gx;
728
    my $C_y = $C_gy - $Top_gy;
729
 
730
    # Translation to image center
731
    my $Width  = $Img->[0]->Get('columns');
732
    my $Height = $Img->[0]->Get('rows');
733
    my $T_x = $Width  /2;
734
    my $T_y = $Height /2;
735
 
736
    my $Degree = (360 - $Bearing) % 360;
737
    my $Scale = 1.0;
738
    $Status = $Img->Distort (method=>ScaleRotateTranslate,
739
                             points=>[ $C_x, $C_y, $Scale, $Degree, $T_x, $T_y ],
740
                             );
741
    #
742
    # Clip used area around Radius
743
    #
744
    my ($LatN, $LonN) = &MapGpsAt ($Lat, $Lon, $Radius, 0);
745
    my ($N_gy, $N_gx) = &Google_Coord_to_Pix( $Value, $LatN, $LonN );
746
    my $RadiusPix = $C_gy - $N_gy;  # $Radius in image pixel
747
 
748
    my $Left_x  = $RadiusPix * $OverscanLeft  / 100;
749
    my $Right_x = $RadiusPix * $OverscanRight / 100;
750
    my $Top_y   = $RadiusPix * $OverscanTop   / 100;
751
    my $Bot_y   = $RadiusPix * $OverscanBot   / 100;
752
    my $SizeX = $Left_x + $Right_x;
753
    my $SizeY = $Top_y  + $Bot_y;
754
 
755
    my $Width  = $Img->[0]->Get('columns');
756
    my $Height = $Img->[0]->Get('rows');
757
    my $OffsetX = $Width /2 - $Left_x;
758
    my $OffsetY = $Height/2 - $Top_y;
759
 
760
    $Status = $Img->Crop( geometry => $SizeX . 'x' . $SizeY . '+' . $OffsetX . '+' . $OffsetY, );
761
 
762
    #
763
    # Scale Image to Screen Height
764
    #
765
    my $Scale = $ImageHeight / $Img->[0]->Get('rows');
766
    $Status = $Img->AffineTransform( scale => "$Scale , $Scale" );
767
 
768
    #
769
    # Transparent Rectangel for better OSD contrast
770
    #
771
    my $Width  = $Img->[0]->Get('columns');
772
    my $Height = 100;
773
    $Img->Draw( primitive => "rectangle",
774
                points => "0,0 $Width,$Height",
775
                fill => 'rgba(0, 0, 0, 0.4)',
776
               );
777
 
778
    #
779
    # Copyright / Logo
780
    #
781
    if ( $UrlDesc =~ /openstreetmap/i )
782
        {
783
        my $Height = $Img->[0]->Get('rows');
784
        $Img->Annotate( text=> 'Daten von OpenStreetMap - Veröffentlicht unter CC-BY-SA 2.0',
785
                        pointsize => 12,
786
                        x => 10 + $Img->[0]->Get('page.x'),
787
                        y => $Height -4 + $Img->[0]->Get('page.y'),
788
                        fill => 'black',
789
                      );
790
        }
791
    elsif ( $UrlDesc =~ /google/i )
792
        {
793
        my $Logo = new Image::Magick;
794
        $Logo->Read("icon/PoweredByGoogle.png");
795
        $Img->Composite( image => $Logo,
796
                         compose => "Atop",
797
                         gravity => "SouthWest",
798
                         geometry => "+10-2",
799
                       );
800
        }
801
 
802
 
803
    #
804
    # get map calibration for Top/Left (P1), Bottom/Right (P2)
805
    #
806
    my $P1_x = 0;
807
    my $P1_y = 0;
808
    my $P2_x = $Img->[0]->Get('columns') -1;
809
    my $P2_y = $Img->[0]->Get('rows') -1;
810
 
811
    my $P_x = - $Left_x;
812
    my $P_y =   $Top_y;
813
    my $R1_x = $P_x * cos (deg2rad $Degree) - $P_y * sin (deg2rad $Degree);
814
    my $R1_y = $P_y * cos (deg2rad $Degree) + $P_x * sin (deg2rad $Degree);
815
    my $R1_gx = $C_gx + $R1_x;
816
    my $R1_gy = $C_gy - $R1_y;
817
 
818
    my $P_x =   $Right_x;
819
    my $P_y = - $Bot_y;
820
    my $R2_x = $P_x * cos (deg2rad $Degree) - $P_y * sin (deg2rad $Degree);
821
    my $R2_y = $P_y * cos (deg2rad $Degree) + $P_x * sin (deg2rad $Degree);
822
    my $R2_gx = $C_gx + $R2_x;
823
    my $R2_gy = $C_gy - $R2_y;
824
 
825
    ($P1_Lat, $P1_Lon) = &Google_Pix_to_Coordinate($Value, $R1_gy, $R1_gx);
826
    ($P2_Lat, $P2_Lon) = &Google_Pix_to_Coordinate($Value, $R2_gy, $R2_gx);
827
 
828
    #
829
    # Set EXIF Geo-Information Comment - Mission Cockpit format for rotated image
830
    #
831
    my $Comment = sprintf ("P:%d,%d,%d,%d,%f,%f,%f,%f", $P1_x, $P1_y, $P2_x, $P2_y, $P1_Lat, $P1_Lon, $P2_Lat, $P2_Lon);
832
    $Img->Comment("$Comment");
833
 
834
    # Write file
835
    $Img->Write("$MapFile");
836
    undef $ImgClip;
837
    }
838
 
839
1;
840
 
841
__END__