Friday, June 20, 2008

Maps API, GTileLayer

要放大圖才是男子漢~~

當瀏覽 Google Maps API 的設計時,會覺得功能相當複雜細緻,當然最簡單的功能按照範例是很容易的,但除了最簡單的功能以外就完全的不容易了。也不知道是不是因為 JavaScript 的文法太自由了,所以文件也就跟著難寫,光看 Google 官方的文件會看得出來一些進階的功能才有鬼。你會想反正是 JavaScript 寫的嘛,大不了就去看 Code 總行了吧,但無奈小氣的 Google 把他們的 API 都進行混淆了,雖然認真看還是可以看出一些頭緒來,但實在是很費力。所以,當Maps API 最好的方法就是去看看別人是怎麼寫的,照著抄就是了。

但學了半天 API,總覺得只能看 Google 提供的地圖不夠過癮,像是如果能用來看一些很不像地圖的地圖…
《清明上河圖》的原畫長528公分,高24.8公分,最早的版本屬於北宋畫家張擇端(1085-1145)的作品,現今典藏在北京的故宮博物院。該圖描繪了清明時節[1]北宋京城汴梁及汴河兩岸的繁華和熱鬧的景象和優美的自然風光。作品以長捲形式,採用散點透視的構圖法,將繁雜的景物納入統一而富於變化的畫捲中,畫中主要分開兩部份,一部份是農村,另一部是市集。畫中有814人,牲畜60多匹,船隻28艘,房屋樓宇30多棟,車20輛,轎8頂,樹木170多棵,往來衣著不同,神情各異,栩栩如生,其間還穿插各種活動,註重情節,構圖疏密有致,富有節奏感和韻律的變化,筆墨章法都很巧妙,頗見功底。這幅畫作對於各種形態的幾何正確描繪性使其負有盛名,也因此被稱之為中國的蒙那麗莎。 ---- Wikipedia
雖然我怎麼看都覺得這應該是地圖,不過既然有人要說這是中國的蒙娜麗莎,那讓我們先來看看真正的蒙娜麗莎長什麼樣,根據阿宅集中地的說法,是男子漢就要放大圖 (握拳...),而且最好是大得一眼看不到全部的大圖,雖然如此,可以是我們還是要一眼就能看到全部:
科科~~

雖然大家不明說,不過其實阿宅都知道看這張圖時,視線的集中點在哪裡…
科科~~

不過藝術品或明星距離阿宅都太遙遠了,還是 cosplay 正妹比較容易讓人親近…
科科科~~


============== 解說分隔線 ==============
程式簡化後的範例請參考(1,2,3,4)

基本上,這個效果是為 Maps 加上自己的 GTileLayer,Tile Layer 可以讓你用一定規則的 url 來形成地圖層,例如 'http://wctang-info.googlecode.com/svn/trunk/imgs/01/{Z}_{Y}_{X}.jpg' 這樣的指定方式 (也可以用 callback 的方式來指定,如果你的 url 比較沒有規則的話)。

要加入 Tile Layer 的方式有兩種,比較單純的就是用 GTileLayerOverlay 的方式加入,這種方式沒辦法沒辦法指定定位的方式,例如這個官方的無趣例子。另一種就是組成 GMapType,這種方式除了 TileLayer 外,還需要 GProjection ,這個 Projection 的用處其實就是要指定把你的 Layer 投影到地圖上的方式。要知道,Maps API 終究還是個看地圖的工具,雖然外表看起來可能是個讓阿宅科科笑的大圖,但程式內部還是認為他是個有經緯度的地圖,所以 Project 就是在做圖片位置與經緯度轉換的工作。不過 Google 只提供了 GMercatorProjection ,這是適合地球經緯度的映射方式,不適合用來放平鋪圖片,所以就要自己寫,把圖片投影成 經度 -180~180, 緯度 -90~90。
  function EuclideanProjection(s){ this.s=s; }
  EuclideanProjection.prototype=new google.maps.Projection();
  EuclideanProjection.prototype.fromLatLngToPixel=function(latlng,zoom){
    return new google.maps.Point( Math.ceil(this.s[zoom].w*(latlng.lng()+180)/360), Math.ceil(this.s[zoom].h*(90-latlng.lat())/180) );
  };
  EuclideanProjection.prototype.fromPixelToLatLng=function(pixel,zoom,unbounded){
    return new google.maps.LatLng( (0.5-(pixel.y/this.s[zoom].h))*180, ((pixel.x/this.s[zoom].w)-0.5)*360, unbounded);
  };
  EuclideanProjection.prototype.tileCheckRange=function(tile,zoom,size){
    if(zoom >= tile.length || tile.x < 0 || tile.y < 0) return false;
    if(tile.x >= Math.ceil(this.s[zoom].w/256)) return false;
    if(tile.y >= Math.ceil(this.s[zoom].h/256)) return false;
    return true;
  }
  EuclideanProjection.prototype.getWrapWidth=function(zoom) {
    return this.s[zoom].w;
  }

  function ImageMapType(name, layerdef, pathpattern) {
    var myCopyright = new google.maps.CopyrightCollection("");
    myCopyright.addCopyright(new google.maps.Copyright('', new google.maps.LatLngBounds(new google.maps.LatLng(-90,-180), new google.maps.LatLng(90,180)), 0, ''));
    var tilelayers = [new google.maps.TileLayer(myCopyright, 0, layerdef.length-1, {opacity:1, isPng:!!/\.png$/.exec(pathpattern), tileUrlTemplate:pathpattern})];
    return new google.maps.MapType(tilelayers, new EuclideanProjection(layerdef), name);
  }


除了程式以外,另一個重點就是圖片來源。
基本上就是要找張夠大的圖,然後分幾個層次縮圖,再把每張縮圖切成 256x256 的小圖組。這工作想必不會是手動來做的,所以一定要找圖片處理工具來做,這方面很出名的工具就是 ImageMagick。下面的 Perl script 是可以指定縮小的比例或尺寸,再自動切成 256x256 的小圖,用到了 ImageMagick 的二個工具:identify, convert 。
#!/usr/bin/perl

use POSIX qw(ceil floor);


my $name = shift;
my $out = shift;
my $resiz = shift;
my $quality = shift;

my $unit = 256;

if(! defined $name || ! defined $out) { die('croppic.pl   ()'); }

$name =~ /(\.\w*)$/;
my $type = $1;

if($resiz) {
  `convert $name -resize $resiz temp$type`;
  $name = "temp$type";
}

`identify $name` =~ /(\S*)\s*(\S*)\s*(\d*)\D*(\d*)/;

print "$3 x $4 ---> ";

my $nw = ceil($3/$unit);
my $w = $nw*$unit;
my $nh = ceil($4/$unit);
my $h = $nh*$unit;
my $siz = $w.'x'.$h;

print "$siz ($nw x $nh) \n";

if($quality) {
  `convert $name -background \#E5E3DF -gravity NorthWest -extent $siz -quality $quality $out$type`;
} else {
  `convert $name -background \#E5E3DF -gravity NorthWest -extent $siz $out$type`;
}

my $ss = $w.'x256';
my $so = $out."_%d$type";
`convert $out$type -crop $ss +repage $so`;

for($i=0;$i<$nh;$i++) {
  $ss = $out.'_'.$i.$type;
  $so = $out.'_'.$i."_%d$type";
  $tt = $unit.'x'.$unit;
  `convert $ss -crop $tt +repage $so`;
  unlink($ss);
}
unlink("$out$type");
unlink("temp$type");
執行完下面這一串後,就產生出一堆小圖了。
croppic.pl monalisa.jpg 3
croppic.pl monalisa.jpg 2 75%
croppic.pl monalisa.jpg 1 50%
croppic.pl monalisa.jpg 0 25%
大圖合體:

0 comments:

Post a Comment