0,0 → 1,841 |
############################################################################### |
# |
# libmapdef.pl - Map definition |
# |
## Copyright (C) 2009 Rainer Walther (rainerwalther-mail@web.de) |
# |
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa) |
# |
# Es ist Ihnen gestattet: |
# * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen |
# * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen |
# |
# Zu den folgenden Bedingungen: |
# * Namensnennung. |
# Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen. |
# * Keine kommerzielle Nutzung. |
# Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden. |
# * Weitergabe unter gleichen Bedingungen. |
# Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten, |
# verändern oder als Grundlage für einen anderen Inhalt verwenden, |
# dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen |
# weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind. |
# |
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses |
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden. |
# |
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung |
# des Rechteinhabers dazu erhalten. |
# |
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt. |
# |
# Weitere Details zur Lizenzbestimmung gibt es hier: |
# Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/ |
# Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode |
# |
############################################################################### |
## |
# 2009-03-06 0.0.1 rw created |
# 2009-04-01 0.1.0 rw RC1 |
# 2009-04-18 0.1.1 rw Select default map, if configured map does not exist |
# 2009-07-22 0.1.2 rw Offset_x and Offset_y for adjustment of map calibration |
# 2009-08-15 0.1.3 rw Tracking Antenne Home position added |
# Player home position added |
# Read map definition from XML file |
# 2009-09-29 0.1.4 rw Read map definition from KML file (GE import) |
# Allow Config lines in map definition file (like mkcockpit.xml) |
# 2009-11-08 0.1.5 rw Avoid div/0, if P1=P2 |
# 2010-05-16 0.1.6 rw Check empty Border in XML |
# 2010-09-09 0.1.7 rw Load JPEG/PNG containig Geo-Information |
# rename map/map.pl --> libmapdef.pl |
# 2010-09-19 0.1.8 rw Create Map from Google/OSM download |
# 2010-10-04 0.1.9 rw Google Maps entfernt wg. rechtlicher Bedenken |
# 2010-10-09 0.1.10 rw Zoom und Reload-Checkbox |
# |
############################################################################### |
|
$Version{'libmapdef.pl'} = "0.1.10 - 2010-10-09"; |
|
use Math::Trig; |
use XML::Simple; # http://search.cpan.org/dist/XML-Simple-2.18/lib/XML/Simple.pm |
use Image::ExifTool qw(:Public); # http://search.cpan.org/~exiftool/Image-ExifTool-8.25/lib/Image/ExifTool.pod |
use LWP::Simple; # http://search.cpan.org/~gaas/libwww-perl-5.836/lib/LWP/Simple.pm |
use File::Path qw(make_path); |
|
if ( $Cfg->{'map2'}->{'ImageMagickInstalled'} =~ /y/i ) |
{ |
require Image::Magick; # comes with ImageMagick installation |
|
# requires ImageMagick 6.5 for Activestate Perl 5.10: |
# http://image_magick.veidrodis.com/image_magick/binaries/ImageMagick-6.5.9-9-Q8-windows-dll.exe |
# http://image_magick.veidrodis.com/image_magick/binaries/ImageMagick-6.5.9-9-Q16-windows-dll.exe |
# Beim Installieren angeben: Install PerlMagick for Activestate Perl 5.10.1 Buld 1007 |
# |
# Theorie: http://www.imagemagick.org/ |
# http://www.imagemagick.org/script/perl-magick.php |
} |
|
use libgemaps; # Theorie: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames |
|
# Load maps from filesystem (XML, KML, JPEG/PNG) |
sub MapDefLoad() |
{ |
%Maps = |
( |
Default => { |
'Name' => "Default", |
'File' => 'default-800.gif', |
# 'Size_X' => '800', |
# 'Size_Y => '600', |
|
'P1_x' => '71', # calibration P1, P2 |
'P1_y' => '472', |
'P2_x' => '500', |
'P2_y' => '103', |
'P1_Lat' => '48.856253', |
'P1_Lon' => '2.3500000', |
'P2_Lat' => '54.090153', |
'P2_Lon' => '12.133249', |
|
# 'Offset_x' => 5, # Optional Pixel offset MK to right |
# 'Offset_y' => 5, # Optional pixel offset MK to top |
|
# 'Home_Lat' => '54.090153', # Optional home position for player |
# 'Home_Lon' => '12.133249', # Optional home position for player |
|
# 'Poi_Lat' => '54.090153', # Optional POI position for player |
# 'Poi_Lon' => '12.133249', # Optional POI position for player |
|
# 'Track_Lat' => '49.685333', # Optional Tracking Antenna pos |
# 'Track_Lon' => '10.950134', # Optional Tracking Antenna pos |
# 'Track_Alt' => '500', # Optional Tracking Antenna altitude |
# 'Track_Bearing' => 10, # Optional Tracking antenne direction |
|
# 'Border' => [ 555, 430, # airfield border |
# 516, 555, |
# 258, 555, |
# 100, 300, |
# 580, 260, |
# 530, 94, |
# 627, 130, |
# 735, 300, |
# 680, 400, |
# 757, 470, |
# 720, 515, |
# 575, 420, |
# ], |
}, |
); |
|
|
# |
# load additional Maps from XML files |
# |
my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map"; |
if ( -d $MapDir ) |
{ |
opendir DIR, $MapDir; |
my @Files = readdir DIR; |
@Files = grep /\.xml$/, @Files; |
closedir DIR; |
|
foreach $Xml (@Files) |
{ |
my $MapConfigFile = "$MapDir/$Xml"; |
if ( -f $MapConfigFile ) |
{ |
my $XmlMap = XMLin($MapConfigFile); |
|
foreach $Location (keys %{$XmlMap}) |
{ |
foreach $Key (keys %{$XmlMap->{$Location}} ) |
{ |
my $Value = $XmlMap->{$Location}->{$Key}; |
if ( $Key =~ /Border/i ) |
{ |
$Value =~ s/\s//g; |
if ( $Value ne "" ) |
{ |
my @Border = split ',', $Value; |
@{$Maps{$Location}->{$Key}} = @Border; |
} |
} |
else |
{ |
$Maps{$Location}->{$Key} = $Value; |
} |
} |
} |
} |
} |
} |
|
|
# |
# load additional Maps from KML files |
# |
my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map"; |
if ( -d $MapDir ) |
{ |
opendir DIR, $MapDir; |
my @Files = readdir DIR; |
@Files = grep /\.kml$/, @Files; |
closedir DIR; |
|
foreach $Kml (@Files) |
{ |
my $MapConfigFile = "$MapDir/$Kml"; |
if ( -f $MapConfigFile ) |
{ |
my $KmlMap = XMLin($MapConfigFile); |
|
my $Name = $KmlMap->{'Document'}->{'Folder'}->{'name'}; |
my $Desc = $KmlMap->{'Document'}->{'Folder'}->{'description'}; |
|
$Maps{$Name}->{'Name'} = $Name; |
|
# Airfield Border |
my $Border = ""; |
my $bBorder = 0; |
|
# parse config lines |
@DescLines = split '\n', $Desc; |
foreach $Line (@DescLines) |
{ |
if ( $bBorder ) |
{ |
# collect border lines |
if ( $Line =~ /=/i ) |
{ |
# New keyword found. End of multi-line border config |
$bBorder = 0; |
} |
else |
{ |
$Border = "$Border" . "$Line"; |
} |
} |
|
if ( $Line =~ /\s*(\S*)\s*=\s*(.*)/i) |
{ |
my $Key = $1; |
my $Value = $2; |
chomp $Value; |
|
# search for border keyword |
if ($Key =~ /border/i ) |
{ |
$Border = $Value; |
$bBorder = 1; |
} |
else |
{ |
$Maps{$Name}->{$Key} = $Value; |
} |
} |
} |
|
if ( $Border ne "" ) |
{ |
$Border =~ s/\s//g; |
my @Border = split ',', $Border; |
@{$Maps{$Name}->{'Border'}} = @Border; |
} |
|
# P1 calibration point |
my $P1 = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P1'}->{'Point'}->{'coordinates'}; |
my $P1Desc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P1'}->{'description'}; |
($Maps{$Name}->{'P1_Lon'}, $Maps{$Name}->{'P1_Lat'}) = split ',', $P1; |
if ( $P1Desc =~ /\s*x\s*=\s*(\d*)/i) { $Maps{$Name}->{'P1_x'} = $1; } # x=nnn |
if ( $P1Desc =~ /\s*y\s*=\s*(\d*)/i) { $Maps{$Name}->{'P1_y'} = $1; } # y=nnn |
|
# P2 calibration point |
my $P2 = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P2'}->{'Point'}->{'coordinates'}; |
my $P2Desc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'P2'}->{'description'}; |
($Maps{$Name}->{'P2_Lon'}, $Maps{$Name}->{'P2_Lat'}) = split ',', $P2; |
if ( $P2Desc =~ /\s*x\s*=\s*(\d*)/i) { $Maps{$Name}->{'P2_x'} = $1; } # x=nnn |
if ( $P2Desc =~ /\s*y\s*=\s*(\d*)/i) { $Maps{$Name}->{'P2_y'} = $1; } # y=nnn |
|
# Home position |
if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'visibility'} ne "0" ) |
{ |
my $Home = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'Point'}->{'coordinates'}; |
my $HomeDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Home'}->{'description'}; |
($Maps{$Name}->{'Home_Lon'}, $Maps{$Name}->{'Home_Lat'}) = split ',', $Home; |
} |
|
# POI position |
if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'visibility'} ne "0" ) |
{ |
my $Poi = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'Point'}->{'coordinates'}; |
my $PoiDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'POI'}->{'description'}; |
($Maps{$Name}->{'Poi_Lon'}, $Maps{$Name}->{'Poi_Lat'}) = split ',', $Poi; |
} |
|
# Antenna tracker position |
if ( $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'visibility'} ne "0" ) |
{ |
my $Track = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'Point'}->{'coordinates'}; |
my $TrackDesc = $KmlMap->{'Document'}->{'Folder'}->{'Placemark'}->{'Antenna'}->{'description'}; |
($Maps{$Name}->{'Track_Lon'}, $Maps{$Name}->{'Track_Lat'}) = split ',', $Track; |
if ( $TrackDesc =~ /\s*Track_Alt\s*=\s*(\d*)/i) { $Maps{$Name}->{'Track_Alt'} = $1; } # Track_Alt=nnn |
if ( $TrackDesc =~ /\s*Track_Bearing\s*=\s*(\d*)/i) { $Maps{$Name}->{'Track_Bearing'} = $1; } # Track_Bearing=nnn |
} |
|
if ( $Maps{$Name}->{'P1_x'} == $Maps{$Name}->{'P2_x'} and |
$Maps{$Name}->{'P1_y'} == $Maps{$Name}->{'P2_y'} ) |
{ |
# Avoid div/0 if P1=P2 |
$Maps{$Name}->{'P1_x'} += 1; |
} |
} |
} |
} |
|
# |
# load additional Maps from JPEG/PNG containing Geo-Information |
# |
my $MapDir = $Cfg->{'map'}->{'MapDir'} || "map"; |
if ( -d $MapDir ) |
{ |
opendir DIR, $MapDir; |
my @Files = readdir DIR; |
closedir DIR; |
|
my @ImgFiles; |
push @ImgFiles, grep /\.jpg$/, @Files; |
push @ImgFiles, grep /\.png$/, @Files; |
|
foreach $Image (@ImgFiles) |
{ |
my $ImgFile = "$MapDir/$Image"; |
if ( -f $ImgFile ) |
{ |
# take only image files which are not already used in %Maps |
$bUsed = ""; |
foreach $Map (keys %Maps) |
{ |
if ( $Maps{$Map}->{'File'} =~ /^${Image}$/i ) |
{ |
$bUsed = "X"; |
last; |
} |
} |
|
if ( $bUsed ne "X" ) |
{ |
# take image |
my ($Width, $Height, $ImgInfo) = &GetImageInfo($ImgFile); |
my $Comment = $$ImgInfo{'Comment'}; |
my $Name = substr ($Image, 0, -4); # remove extension |
|
# Image from Kopter-Tool |
my @Fields = split ",", $Comment; |
if ( $Fields[0] =~ /Geo-Information/i ) |
{ |
my ($RoLat, $RoLon) = split ":", $Fields[1]; |
my ($LoLat, $LoLon) = split ":", $Fields[2]; |
my ($RuLat, $RuLon) = split ":", $Fields[3]; |
my ($LuLat, $LuLon) = split ":", $Fields[4]; |
|
$Maps{$Name}->{'Name'} = $Name; |
$Maps{$Name}->{'File'} = $Image; |
$Maps{$Name}->{'P1_x'} = 0; |
$Maps{$Name}->{'P1_y'} = 0; |
$Maps{$Name}->{'P2_x'} = $Width; |
$Maps{$Name}->{'P2_y'} = $Height; |
$Maps{$Name}->{'P1_Lat'} = $LoLat; |
$Maps{$Name}->{'P1_Lon'} = $LoLon; |
$Maps{$Name}->{'P2_Lat'} = $RuLat; |
$Maps{$Name}->{'P2_Lon'} = $RuLon; |
} |
|
# GeoMapTool Image for Mission Cockpit |
my @Fields = split ";", $Comment; |
foreach $Param (@Fields) |
{ |
my ($Key, $Value) = split ":", $Param; |
if ( $Key eq "P" ) |
{ |
my ($P1_x, $P1_y, $P2_x, $P2_y, $P1_Lat, $P1_Lon, $P2_Lat, $P2_Lon) = split ",", $Value; |
$Maps{$Name}->{'P1_x'} = $P1_x; |
$Maps{$Name}->{'P1_y'} = $P1_y; |
$Maps{$Name}->{'P2_x'} = $P2_x; |
$Maps{$Name}->{'P2_y'} = $P2_y; |
$Maps{$Name}->{'P1_Lat'} = $P1_Lat; |
$Maps{$Name}->{'P1_Lon'} = $P1_Lon; |
$Maps{$Name}->{'P2_Lat'} = $P2_Lat; |
$Maps{$Name}->{'P2_Lon'} = $P2_Lon; |
|
$Maps{$Name}->{'File'} = $Image; |
$Maps{$Name}->{'Name'} = $Name; |
} |
if ( $Key eq "Home" ) |
{ |
my ($Home_Lat, $Home_Lon) = split ",", $Value; |
$Maps{$Name}->{'Home_Lat'} = $Home_Lat; |
$Maps{$Name}->{'Home_Lon'} = $Home_Lon; |
} |
if ( $Key eq "Poi" ) |
{ |
my ($Poi_Lat, $Poi_Lon) = split ",", $Value; |
$Maps{$Name}->{'Poi_Lat'} = $Poi_Lat; |
$Maps{$Name}->{'Poi_Lon'} = $Poi_Lon; |
} |
if ( $Key eq "Border" ) |
{ |
my @Border = split ',', $Value; |
@{$Maps{$Name}->{'Border'}} = @Border; |
} |
} |
} |
} |
} |
} |
|
# Die verwendete Karte |
&MapSetCurrentFromCfg(); |
} |
|
|
# Set $Maps{'Current'} from Cfg-Setting |
sub MapSetCurrentFromCfg() |
{ |
|
# Todo: Karte automatisch anhand der aktuellen GPS Position auswählen |
|
my $MapDefault = $Cfg->{'map'}->{'MapDefault'}; |
if ( defined $Maps{$MapDefault} ) |
{ |
$Maps{'Current'} = $Maps{$MapDefault}; |
} |
else |
{ |
$Maps{'Current'} = $Maps{'Default'}; |
print "Map \"$MapDefault\" not found in map.pl. Using \"Default\" map\n"; |
} |
|
# optional map specific Cfg setup from map definition |
# Aktuell gültige Karte |
my %Map = %{$Maps{'Current'}}; |
|
foreach $Key (keys %Map) |
{ |
# Cfg:Section:Keyword |
if ( $Key =~ /^Cfg:(\S*):(\S*)/i ) |
{ |
$Section = $1; |
$Keyword = $2; |
$Cfg->{$Section}->{$Keyword} = $Map{$Key}; |
} |
} |
|
# Get size of image |
my $ImgFile = "$Cfg->{'map'}->{'MapDir'}/$Map{'File'}"; |
my ($Width, $Height) = &GetImageInfo($ImgFile); |
if ( $Maps{'Current'}->{'Size_X'} eq "" ) |
{ |
$Maps{'Current'}->{'Size_X'} = $Width; |
} |
if ( $Maps{'Current'}->{'Size_Y'} eq "" ) |
{ |
$Maps{'Current'}->{'Size_Y'} = $Height; |
} |
|
# Option list from map config dialog |
@{ $CfgOpt{MapDefault} } = sort keys %Maps |
} |
|
|
# Get size and EXIF data of Image |
sub GetImageInfo() |
{ |
my ($File) = @_; |
|
my $ExifTool = new Image::ExifTool; |
my $ImgInfo = $ExifTool->ImageInfo($File); |
|
my $Width; |
my $Height; |
|
my $ImageSize = $$ImgInfo{'ImageSize'}; |
($Width, $Height) = split "x", $ImageSize; |
|
if ( $Width eq "" ) |
{ |
$Width = $$ImgInfo{'ImageWidth'}; |
} |
if ( $Height eq "" ) |
{ |
$Height = $$ImgInfo{'ImageHeight'}; |
} |
|
if ( $Width eq "" ) |
{ |
$Width = $$ImgInfo{'ExifImageWidth'}; |
} |
if ( $Height eq "" ) |
{ |
$Height = $$ImgInfo{'ExifImageHeight'}; |
} |
|
return ($Width, $Height, $ImgInfo); |
} |
|
|
# |
# Compose a JPEG map for given Position and Bearing. Download Image-Tiles from OSM |
# |
|
# Create Map |
sub MapCompose() |
{ |
# Directory for tile cache |
my $TileDir = $Cfg->{'map2'}->{'MapTileCache'} || "map/_tile_cache"; |
|
# JPEG filename |
my $MapFile = $Cfg->{'map'}->{'MapDir'} || "map"; |
$MapFile = $MapFile . "/AutoMap.jpg"; |
|
my $Zoom = $Cfg->{'map2'}->{'Zoom'} || 18; |
my $OsmUrl = $Cfg->{'map2'}->{'OsmUrl'} || "http://tile.openstreetmap.org/%z/%x/%y.png"; |
|
my $Lat = $MkOsd{'CurPos_Lat'}; |
my $Lon = $MkOsd{'CurPos_Lon'}; |
my $Radius = $MkOsd{'OperatingRadius'} || 250; |
my $Bearing = $MkOsd{'CompassHeading'} || 0; |
|
my $OverscanTop = $Cfg->{'map2'}->{'OverscanTop'} || 100; |
my $OverscanBot = $Cfg->{'map2'}->{'OverscanBot'} || 23; |
my $OverscanLeft = $Cfg->{'map2'}->{'OverscanLeft'} || 110; |
my $OverscanRight = $Cfg->{'map2'}->{'OverscanRight'} || 110; |
my $ImageHeight = $Cfg->{'map2'}->{'ImageHeight'} || int($main->screenheight * 0.8); |
my $TileReload = ""; |
|
# |
# GUI Popup - check and confirm parameter |
# |
my $popup = $main->Toplevel(); |
$popup->title("Map Composer"); |
|
my @Fields = ( # Label Variable |
$Translate{MapLat}, \$Lat, |
$Translate{MapLon}, \$Lon, |
$Translate{MapRadius}, \$Radius, |
$Translate{MapBearing}, \$Bearing, |
$Translate{OverscanTop}, \$OverscanTop, |
$Translate{OverscanBot}, \$OverscanBot, |
$Translate{OverscanLeft}, \$OverscanLeft, |
$Translate{OverscanRight}, \$OverscanRight, |
$Translate{MapZoom}, \$Zoom, |
$Translate{MapImageHight}, \$ImageHeight, |
); |
my $Row = 0; |
my $i = 0; |
for $Field (0 .. $#Fields/2) |
{ |
my $Label = $Fields[$i++]; |
my $Var = $Fields[$i++]; |
|
$popup->Label (-text => $Label, |
-width => 25, |
-anchor => 'w', |
)->grid (-row => $Row, |
-column => 0, |
-sticky => 'w', |
-padx => 5, |
-pady => 1, |
); |
$popup->Entry ( -textvariable => $Var, |
-exportselection => '1', |
-width => 40, |
-relief => 'sunken', |
)->grid (-row => $Row, |
-column => 1, |
-sticky => 'w', |
-padx => 5, |
-pady => 1, |
); |
$Row ++; |
} |
|
$popup->Checkbutton( -text => $Translate{'MapTileReload'}, |
-offvalue => "", |
-onvalue => "X", |
-variable => \$TileReload, |
)->grid (-row => $Row++, |
-column => 1, |
-columnspan => 2, |
-sticky => 'w', |
); |
|
$MapComposeMessage = ""; |
$popup->Label (-textvariable => \$MapComposeMessage, |
-font => '-*-Arial-Bold-R-Normal--*-175-*', |
-width => 40, |
-anchor => 'w', |
)->grid (-row => $Row ++, |
-column => 0, |
-columnspan => 2, |
-sticky => 'w', |
-padx => 5, |
-pady => 5, |
); |
|
# Button: OK |
$popup->Button('-text' => 'OK - Create Map', |
'-width' => '20', |
'-command' => sub |
{ |
# check values |
if ( $Radius > 500 ) { $Radius = 500; } |
if ( $Zoom > 19 ) { $Zoom = 19; } |
if ( $OverscanTop > 125 ) { $OverscanTop = 125; } |
if ( $OverscanBot > 125 ) { $OverscanBot = 125; } |
if ( $OverscanLeft > 125 ) { $OverscanLeft = 125; } |
if ( $OverscanRight > 125 ) { $OverscanRight = 125; } |
|
if ( $Lat ne "" and $Lon ne "") |
{ |
# Compose map - will block Mainloop while running |
&MapComposeOsm ($Lat, $Lon, $Radius, $Bearing, $Zoom, $OsmUrl, $TileDir, $MapFile, $TileReload, |
$OverscanTop, $OverscanBot, $OverscanLeft, $OverscanRight, $ImageHeight); |
|
# Save new MapDefault |
my $CfgEdit = &CopyHash($Cfg); |
$CfgEdit->{'map'}->{'MapDefault'} = "AutoMap"; |
&ConfigureSave( $XmlConfigFile, $Cfg, $CfgEdit); |
|
# Reload Map |
&MapDefLoad(); |
&CanvasRedraw(); |
|
$popup->destroy(); |
} |
else |
{ |
$MapComposeMessage = "Lat/Lon is invalid"; |
$main->update; |
} |
} )->grid(-row => $Row, |
-column => 0, |
-sticky => 'w', |
-padx => 15, |
-pady => 5, |
); |
|
# Button: Abort |
$popup->Button('-text' => $Translate{'Abort'}, |
'-width' => '10', |
'-command' => sub { $popup->destroy() }, |
)->grid(-row => $Row, |
-column => 1, |
-sticky => 'w', |
-padx => 15, |
-pady => 5, |
); |
} |
|
# Create JPEG Map from OSM using ImageMagick |
sub MapComposeOsm() |
{ |
my ($Lat, $Lon, $Radius, $Bearing, $Zoom, $UrlDesc, $TileDir, $MapFile, $TileReload, |
$OverscanTop, $OverscanBot, $OverscanLeft, $OverscanRight, $ImageHeight) = @_; |
|
# Calculate required tiles |
my ($LatN, $LonN) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 0); # Factor 1.4 wg. rotation |
my ($LatE, $LonE) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 90); |
my ($LatS, $LonS) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 180); |
my ($LatW, $LonW) = &MapGpsAt ($Lat, $Lon, $Radius * 1.4, 270); |
my @Tiles = &Google_Tiles($LatS, $LonW, $LatN, $LonE, $Zoom, 0, 'Partial'); |
|
# download tiles and compose Image |
my $Columns = 0; |
my $LastRow = $Tiles[0]{'NAMEY'}; |
my $ImgTiles = new Image::Magick; |
for ( $Tile = 0; $Tile <= $#Tiles; $Tile++ ) |
{ |
my $NameX = $Tiles[$Tile]{'NAMEX'}; |
my $NameY = $Tiles[$Tile]{'NAMEY'}; |
|
my $Dir = "$TileDir/$Zoom/$NameX"; |
if ( ! -d $Dir ) |
{ |
make_path("$Dir"); |
} |
my $File = sprintf ("%s/%d_%d_%d.jpg", $Dir, $Zoom, $NameX, $NameY); |
if ( ! -f $File or $TileReload eq "X" ) |
{ |
if ( defined $main) |
{ |
$MapComposeMessage = sprintf "Download Tile %d/%d: %d %d %d", $Tile+1, $#Tiles+1, $NameX, $NameY, $Zoom; |
$main->update; |
} |
|
my $Url = $UrlDesc; |
$Url =~ s/%x/$NameX/i; |
$Url =~ s/%y/$NameY/i; |
$Url =~ s/%z/$Zoom/i; |
|
my $Status = getstore($Url, $File ); # download using LWP |
if ( $Status != RC_OK ) |
{ |
# use error tile |
$File = "icon/Tile256x256.jpg"; |
} |
} |
|
$ImgTiles->Read("$File"); |
|
# count tile Columns |
$Columns ++; |
if ( $NameY != $LastRow ) |
{ |
$Columns = 1; |
$LastRow = $NameY; |
} |
} |
|
if ( defined $main) |
{ |
$MapComposeMessage = "Compose image ..."; |
$main->update; |
} |
|
# Montage Tiles |
my $Width = $ImgTiles->[0]->Get('columns'); |
my $Height = $ImgTiles->[0]->Get('rows'); |
my $Img = $ImgTiles->Montage( geometry => "$Width" . 'x' . "$Height" . '+0+0', |
tile => $Columns, |
); |
undef $ImgTiles; # Tiles not needed any more |
|
# |
# Rotate around lat/lon, translate lat/lon to image center |
# |
|
my $Value = &Google_Tile_Factors($Zoom, "0") ; # Calculate Tile Factor |
my ($C_gy, $C_gx) = &Google_Coord_to_Pix( $Value, $Lat, $Lon ); |
|
# OSM raw image pixel size |
my $Left_gx = $Tiles[0]{'PXW'}; |
my $Right_gx = $Tiles[$#Tiles]{'PXE'}; |
my $Top_gy = $Tiles[0]{'PYN'}; |
my $Bot_gy = $Tiles[$#Tiles]{'PYS'}; |
|
# Center: Image pixel coord |
my $C_x = $C_gx - $Left_gx; |
my $C_y = $C_gy - $Top_gy; |
|
# Translation to image center |
my $Width = $Img->[0]->Get('columns'); |
my $Height = $Img->[0]->Get('rows'); |
my $T_x = $Width /2; |
my $T_y = $Height /2; |
|
my $Degree = (360 - $Bearing) % 360; |
my $Scale = 1.0; |
$Status = $Img->Distort (method=>ScaleRotateTranslate, |
points=>[ $C_x, $C_y, $Scale, $Degree, $T_x, $T_y ], |
); |
# |
# Clip used area around Radius |
# |
my ($LatN, $LonN) = &MapGpsAt ($Lat, $Lon, $Radius, 0); |
my ($N_gy, $N_gx) = &Google_Coord_to_Pix( $Value, $LatN, $LonN ); |
my $RadiusPix = $C_gy - $N_gy; # $Radius in image pixel |
|
my $Left_x = $RadiusPix * $OverscanLeft / 100; |
my $Right_x = $RadiusPix * $OverscanRight / 100; |
my $Top_y = $RadiusPix * $OverscanTop / 100; |
my $Bot_y = $RadiusPix * $OverscanBot / 100; |
my $SizeX = $Left_x + $Right_x; |
my $SizeY = $Top_y + $Bot_y; |
|
my $Width = $Img->[0]->Get('columns'); |
my $Height = $Img->[0]->Get('rows'); |
my $OffsetX = $Width /2 - $Left_x; |
my $OffsetY = $Height/2 - $Top_y; |
|
$Status = $Img->Crop( geometry => $SizeX . 'x' . $SizeY . '+' . $OffsetX . '+' . $OffsetY, ); |
|
# |
# Scale Image to Screen Height |
# |
my $Scale = $ImageHeight / $Img->[0]->Get('rows'); |
$Status = $Img->AffineTransform( scale => "$Scale , $Scale" ); |
|
# |
# Transparent Rectangel for better OSD contrast |
# |
my $Width = $Img->[0]->Get('columns'); |
my $Height = 100; |
$Img->Draw( primitive => "rectangle", |
points => "0,0 $Width,$Height", |
fill => 'rgba(0, 0, 0, 0.4)', |
); |
|
# |
# Copyright / Logo |
# |
if ( $UrlDesc =~ /openstreetmap/i ) |
{ |
my $Height = $Img->[0]->Get('rows'); |
$Img->Annotate( text=> 'Daten von OpenStreetMap - Veröffentlicht unter CC-BY-SA 2.0', |
pointsize => 12, |
x => 10 + $Img->[0]->Get('page.x'), |
y => $Height -4 + $Img->[0]->Get('page.y'), |
fill => 'black', |
); |
} |
elsif ( $UrlDesc =~ /google/i ) |
{ |
my $Logo = new Image::Magick; |
$Logo->Read("icon/PoweredByGoogle.png"); |
$Img->Composite( image => $Logo, |
compose => "Atop", |
gravity => "SouthWest", |
geometry => "+10-2", |
); |
} |
|
|
# |
# get map calibration for Top/Left (P1), Bottom/Right (P2) |
# |
my $P1_x = 0; |
my $P1_y = 0; |
my $P2_x = $Img->[0]->Get('columns') -1; |
my $P2_y = $Img->[0]->Get('rows') -1; |
|
my $P_x = - $Left_x; |
my $P_y = $Top_y; |
my $R1_x = $P_x * cos (deg2rad $Degree) - $P_y * sin (deg2rad $Degree); |
my $R1_y = $P_y * cos (deg2rad $Degree) + $P_x * sin (deg2rad $Degree); |
my $R1_gx = $C_gx + $R1_x; |
my $R1_gy = $C_gy - $R1_y; |
|
my $P_x = $Right_x; |
my $P_y = - $Bot_y; |
my $R2_x = $P_x * cos (deg2rad $Degree) - $P_y * sin (deg2rad $Degree); |
my $R2_y = $P_y * cos (deg2rad $Degree) + $P_x * sin (deg2rad $Degree); |
my $R2_gx = $C_gx + $R2_x; |
my $R2_gy = $C_gy - $R2_y; |
|
($P1_Lat, $P1_Lon) = &Google_Pix_to_Coordinate($Value, $R1_gy, $R1_gx); |
($P2_Lat, $P2_Lon) = &Google_Pix_to_Coordinate($Value, $R2_gy, $R2_gx); |
|
# |
# Set EXIF Geo-Information Comment - Mission Cockpit format for rotated image |
# |
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); |
$Img->Comment("$Comment"); |
|
# Write file |
$Img->Write("$MapFile"); |
undef $ImgClip; |
} |
|
1; |
|
__END__ |