Cyber EarthのPerl版

ちょいと2chにて作業してましたが、まぁ最低限動いたのでちゃんと公開します。
Cyber EarthPerlで動くように移植して、地図の透過をサポートしたものです。

http://localhost/cyberearth/cyber-earth.cgi?MODE=0&ALPHA=220 の様に指定してください。
MODE=1とするとキャッシュモード、MODE=0あるいは省略するとダイレクトモードになります。
ALPHAは1〜255を指定でき、255で不透明、1でほとんど透明となります。省略すると220になります。
ここらへんは id:kazkun:20090515 で書いたhackと同じです。

#!/usr/bin/perl

# Cyber Earth - The CyberJapan Map layer CGI for Google Earth.
# 
# Gauche version is written by YOKOTA Hiroshi 2008-07-28
#       http://www.netlab.is.tsukuba.ac.jp/~yokota/izumi/cyber-earth/
# Perl version is ported by Kazkun 2011-07-11
# 
# Copyright pending because there is no response from Original auther YOKOTA to date.
#
# 20110711 Kaz : Initial release on Perl
#                ADD ALPHA parameter, MODE parameter
#                MOD fixed for new format of CyberJapan site

use POSIX;
use CGI;
use List::Util;

$MYURLBASE = "http://localhost/cyberearth/";

sub toSecond100 {
	my ($val) = @_;
	return $val * (60 * 60 * 100.0);
}

sub toDegree {
	my ($val) = @_;
	return $val / (60 * 60 * 100.0);
}

sub isAvailableArea {
	my ($lat, $lon, $level) = @_;
	return 0 if $lon < toSecond100(121.0) - $level || $lon > toSecond100(154.0);
	return 0 if $lat < toSecond100( 20.2) - $level || $lat > toSecond100( 46.5);
	return 1;
}

sub makePosList {
	my ($start, $limit, $step) = @_;
	my ($i, @list);
	for($i = $start; $i < $limit; $i += $step) {
		unshift(@list, $i);
	}
	return @list;
}

sub adjustLevel {
	my ($lv) = @_;
	# avoid range bug. GoogleEarth puts wrong range value when the eye is very far from the earth. (Ver.4.1.7076.4458/Linux)
	return 3072000 if $lv <       0;
	return     750 if $lv <     750;
	return    1500 if $lv <    1500;
	return    3000 if $lv <    3000;
	return    6000 if $lv <    6000;
	return   12000 if $lv <   12000;
	return   24000 if $lv <   24000;
	return   48000 if $lv <   48000;
	return   96000 if $lv <   96000;
	return  192000 if $lv <  192000;
	return  384000 if $lv <  384000;
	return  768000 if $lv <  768000;
	return 1536000 if $lv < 1536000;
	return 3072000;
	
}

sub levToLevID {
	my($lv) = @_;
	return "7.5fgd"       if $lv <=     750;
	return "15nti"        if $lv <=    1500;
	return "30nti"        if $lv <=    3000;
	return "60nti"        if $lv <=    6000;
	return "120bafd"      if $lv <=   12000;
	return "240bafd"      if $lv <=   24000;
	return "480bafd"      if $lv <=   48000;
	return "960bafd"      if $lv <=   96000;
	return "1920bafd"     if $lv <=  192000;
	return "raster/3840"  if $lv <=  384000;
	return "raster/7680"  if $lv <=  768000;
	return "raster/15360" if $lv <= 1536000;
	return "raster/30720";
}


# main logic starts here

$query = new CGI;
if ($query->param('MODE') == '1') {
	$mode = 1;
} else {
	$mode = 0;
}
$alpha = $query->param('ALPHA') + 0;
$alpha = 220 if ($alpha == 0);
($wdeg, $sdeg, $edeg, $ndeg) = split(/,/, $query->param('BBOX'));
$wsec = toSecond100($wdeg + 0);
$ssec = toSecond100($sdeg + 0);
$esec = toSecond100($edeg + 0);
$nsec = toSecond100($ndeg + 0);
$lonsec = toSecond100($query->param('lookatLon') + 0);
$latsec = toSecond100($query->param('lookatLat') + 0);
$range = $query->param('lookatRange') + 0;
$tilt = $query->param('lookatTilt') + 0;

# decide map level
if ($tilt < 20) {
	$level = adjustLevel(List::Util::max(abs($esec - $wsec), abs ($nsec - $ssec)) / 6);
} else {
	$level = adjustLevel($range / 4 * 3);		# FIXME : use $tilt
}

# reshape BoundBox
$wsec = List::Util::max((floor($wsec/$level) - 1) * $level, (floor($lonsec/$level) - 3) * $level, toSecond100(0)),
$ssec = List::Util::max((floor($ssec/$level) - 1) * $level, (floor($latsec/$level) - 3) * $level, toSecond100(-90)),
$esec = List::Util::min((floor($esec/$level) + 1) * $level, (floor($lonsec/$level) + 4) * $level, toSecond100(360)),
$nsec = List::Util::min((floor($nsec/$level) + 1) * $level, (floor($latsec/$level) + 4) * $level, toSecond100(90)), 

# build overlay position list
@latlist = makePosList($ssec, $nsec, $level);
@lonlist = makePosList($wsec, $esec, $level);
@gridlist = ();
foreach $lat (@latlist) {
	foreach $lon (@lonlist) {
		push(@gridlist, [$lat, $lon]) if isAvailableArea($lat, $lon, $level);
	}
}

# setup misc data
$color = sprintf("%02x", $alpha)."ffffff";
$levstr = levToLevID($level);

# output XML data
print "Content-type: application/vnd.google-earth.kml+xml\n";
print "\n";
print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
print "<kml xmlns=\"http://earth.google.com/kml/2.0\">\n";
print "  <Document>\n";
print "    <Folder>\n";
print "      <name>layers</name>\n";
print "      <open>0</open>\n";
print "      <description>";
	print "MODE:" . ($mode == 1 ? "CACHED" : "DIRECT");
	print " ALPHA:$alpha";
	print " N:$ndeg E:$edeg W:$wdeg S:$sdeg Lev:$level";
	print " Nbox:" . toDegree($nsec);
	print " Ebox:" . toDegree($esec);
	print " Wbox:" . toDegree($wsec);
	print " Sbox:" . toDegree($ssec);
	print " lr:" . $query->param('lookatRange');
	print " llat:" . $query->param('lookatLat');
	print " llon:" . $query->param('lookatLon');
	print " lt:" . $query->param('lookatTilt');
print "</description>\n";

# output Overlay elements
foreach $pos (@gridlist) {
	$lat = $pos->[0];
	$lon = $pos->[1];
	$path = "/data/$levstr/new/$lon/$lon-$lat-img.png";
	if ($mode < 1) {
		$url = "http://cyberjapandata.gsi.go.jp$path";
	} else {
		$url = "${MYURLBASE}cyber-earth-cache.cgi?filename=$path";
	}
	$n = toDegree($lat + $level);
	$s = toDegree($lat);
	$e = toDegree($lon + $level);
	$w = toDegree($lon);
	print "      <GroundOverlay>\n";
	print "        <name>$level:$lat:$lon</name>\n";
	print "        <color>$color</color>\n";
	print "        <Icon>\n";
	print "          <href>$url</href>\n";
	print "        </Icon>\n";
	print "        <LatLonBox>\n";
	print "          <north>$n</north>\n";
	print "          <south>$s</south>\n";
	print "          <east>$e</east>\n";
	print "          <west>$w</west>\n";
	print "        </LatLonBox>\n";
	print "      </GroundOverlay>\n";
}
print "    </Folder>\n";
print "  </Document>\n";
print "</kml>\n";
exit 0;