Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
827 | - | 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__ |