2010年02月07日

iPhone/iPod touch向けトラックアップ地図の機能拡充

年始に作成したトラックアップ地図を少しずつ機能を追加してきた。
デモモードを実装したので、iPhone/iPod touchを持っている人は是非見て欲しいと思う。
デモモードは現在地を内部的に変更し、トラックアップ地図が移動時にどのような挙動を示すのかを確認する事ができる。
デモムービーを撮っては見たけどボケててみにくい。
実際に使用するには次のリンク先を使って欲しい。
主な機能と技術的なことについて書いてみる。特出して目新しい機能はないが、注目して欲しいのはブラウザ上で動いているということ。Webアプリでもここまでできる。
地図の拡大縮小
Safariの独自イベントであるgesture系のものを使い、ピンチアウト/ピンチイン(二本指で画面に触れ、指を広げる/狭める操作)で「地図の拡大縮小」ができる。拡大縮小にはGoogle MapsのAPIを使用している。ズームの比率が表示されないのは既知の不具合。ロジック的には問題はないはずだけど原因は不明。
地図の回転
常に北を上に表示する「ノーズアップ機能」、常に進行方向を上に表示する「トラックアップ機能」(ヘディングアップ機能と言われることもある)がある。この両方の機能をオフにすると、ピンチ(二本指で画面に触れ、回転させる操作)で視点の方向を自由に変更できるようになる。位置情報取得に使っているgeolocationというAPIでは、位置情報だけでなくheadingプロパティで向いている方向もわかるはずだが、Safari mobile for iPhoneOSでは対応していないようだ。このため、トラックアップ機能は、現在地と直近の位置から向いている方向を推定している。ピンチの検出にはSafari mobileのgesture系のイベントを利用し、地図の回転にはCSSのTransformのrotateを使用している。
バーズアイビュー機能
「バーズアイビュー機能」カーナビのように、進行方向後方上空から、現在地を見下ろすように表示する機能。この機能がオフの場合、現在地の上空から真下を見下ろすような視点となる。バーズアイビューはCSSのTransform 3Dで実現している。
地図の表示位置変更
「現在地の自動追尾機能」をオンにすると、現在地を常に画面の中心(バーズアイビュー機能では画面下方)に表示する。自動追尾をオフにすると、ドラッグ操作(もしくは画面を指で弾くフリック操作)で表示位置を変更できる。ドラッグ操作はSafari独自イベントのtouch系のイベントで検出し、表示位置の変更にはGoogle MapsのAPIを使用している。
問題点等
地図の回転等のスクラッチで実装した部分は操作が機敏だけど、地図の表示位置変更と拡大縮小は動きが遅い。これは、Google MapsのAPIを想定されていない使い方をしているためだと思う。特に、地図の中心位置を変更するsetCenterやpanBy,panToはパンのアニメーションが入ってしまう(正確には、setCenterは移動距離が短い場合のみパンとなる)。
以前のバージョンの一番の問題は著作権表示で、地図を回転させる事で著作権表示が画面外に出てしまっていた。表示領域に外接するように地図のサイズを変更していたので、回転角が90度の倍数に近い場合は表示されていたが、逆にいうとそれ以外では表示されていなかった。今のバージョンでは地図を二枚重ねにしてなんとか表示させている。ただし、まだ問題が残っていて、ユーザの操作をイベントとして受け取るDIV要素が全画面を覆っているため、著作権表示部分のリンクをクリックできない。これもなんとかしたい。
位置情報をこちらに送信したくない場合は、Githubに公開しているソースを自分のサーバに設置するか、iPhone/iPod touch内に保存して利用して欲しい。(といっても、コードを見てもらうとわかると思うけど、こちらに位置情報を送信するような処理は入っていない。)

2010年01月30日

【添削求む】Javaで多次元を扱えるArrayListっぽいクラスを作ってみた

Javaは配列なら多次元を扱えるけど、ArrayListを使った場合は多次元を扱う方法がない模様。
定番のライブラリがありそうだけど自作してみた。
package com.kanasansoft.MultidimensionalArrayLists;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MultidimensionalArrayLists<E>{

    private static final long serialVersionUID=1L;
    private int[] indexes_=null;
    private E data_=null;
    private ArrayList<MultidimensionalArrayLists<E>> children_=new ArrayList<MultidimensionalArrayLists<E>>(); 

    public MultidimensionalArrayLists(int... indexes){
        if(indexes.length==0){
            return;
        }
        if(indexes[0]<0){
            throw new IllegalArgumentException();
        }
        int[] childIndexes=Arrays.copyOfRange(indexes,1,indexes.length);
        ArrayList<MultidimensionalArrayLists<E>> children = new ArrayList<MultidimensionalArrayLists<E>>();
        for(int i=0;i<indexes[0];i++){
            children.add(new MultidimensionalArrayLists<E>(childIndexes));
        }
        this.indexes_=indexes;
        this.children_=children;
    }

    public MultidimensionalArrayLists(List<E> arrayList,int... indexes){
        if(indexes.length==0){
            this.data_=arrayList.get(0);
            return;
        }
        if(indexes[0]<0){
            throw new IllegalArgumentException();
        }
        int length=1;
        for(int i=0;i<indexes.length;i++){
            length*=indexes[i];
        }
        if(arrayList.size()!=length){
            throw new IllegalArgumentException();
        }
        int[] childIndexes=Arrays.copyOfRange(indexes,1,indexes.length);
        ArrayList<MultidimensionalArrayLists<E>> children = new ArrayList<MultidimensionalArrayLists<E>>();
        for(int i=0;i<indexes[0];i++){
            List<E> childrenArrayList = arrayList.subList(length/indexes[0]*i,length/indexes[0]*(i+1));
            children.add(new MultidimensionalArrayLists<E>(childrenArrayList,childIndexes));
        }
        this.indexes_=indexes;
        this.children_=children;
    }

    public void put(E value,int... indexes){
        if(this.indexes_==null&&indexes.length==0){
            this.data_=value;
            return;
        }
        if(this.indexes_.length!=indexes.length){
            throw new IllegalArgumentException();
        }
        int length=this.indexes_.length;
        for(int i=0;i<length;i++){
            if(this.indexes_[i]<=indexes[i]){
                throw new IndexOutOfBoundsException();
            }
        }
        int[] childIndexes=Arrays.copyOfRange(indexes,1,indexes.length);
        this.children_.get(indexes[0]).put(value,childIndexes);
    }

    public E get(int... indexes){
        if(this.indexes_==null&&indexes.length==0){
            return this.data_;
        }
        if(this.indexes_.length!=indexes.length){
            throw new IllegalArgumentException();
        }
        int length=this.indexes_.length;
        for(int i=0;i<length;i++){
            if(this.indexes_[i]<=indexes[i]){
                throw new IndexOutOfBoundsException();
            }
        }
        int[] childIndexes=Arrays.copyOfRange(indexes,1,indexes.length);
        return this.children_.get(indexes[0]).get(childIndexes);
    }

    public int[] getIndexes(){
        int length=this.indexes_.length;
        int[] indexes=new int[length];
        for(int i=0;i<length;i++){
            indexes[i]=this.indexes_[i];
        }
        return indexes;
    }

    public ArrayList<E> getFlatten(){
        ArrayList<E> arrayList=new ArrayList<E>();
        if(this.indexes_.length==1){
            for(int i=0;i<this.indexes_[0];i++){
                arrayList.add(this.children_.get(i).get());
            }
        }else{
            for(int i=0;i<this.indexes_[0];i++){
                arrayList.addAll(this.children_.get(i).getFlatten());
            }
        }
        return arrayList;
    }

}
使い方は以下の通り。
package com.kanasansoft.MultidimensionalArrayLists;

import java.util.ArrayList;

public class Sample {

    public static void main(String[] args) {
        new Sample();
    }
    public Sample(){
        body();
    }
    public void body(){

        MultidimensionalArrayLists<String> mlists = null;
        mlists=new MultidimensionalArrayLists<String>(10,10,10);
        System.out.println(mlists.get(0,0,0));          //=>null
        System.out.println(mlists.getIndexes().length); //=>3
        System.out.println(mlists.getFlatten().size()); //=>1000

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("x");
        arrayList.add("y");
        arrayList.add("z");
        mlists=new MultidimensionalArrayLists<String>(arrayList,2,3);
        System.out.println(mlists.get(0,0)); //=>"a"
        System.out.println(mlists.get(0,2)); //=>"c"
        System.out.println(mlists.get(1,2)); //=>"z"
        mlists.put("foo", 1,2);
        System.out.println(mlists.get(1,2)); //=>"foo"

    }
}
Java初心者が作ったわりには使い物になるんじゃないかと思う。
変なところや改善案があれば指摘してい欲しい。

コードはGistsにもUploadしている。

2010年01月28日

選択範囲をGistに保存するBookmarklet(再掲)

2010/01/30 追記
このブックマークレットを公開したあと、CSRFではないかと指摘を頂きました。
仕様と考えていたのですが、確かにセキュリティホールにも見えたため、一旦記事を削除し、GitHub社に問い合わせました。
回答は、「コマンドラインやサードパーティ製のアプリケーションからもGistを作成できるようにするための仕様」との事なので再掲載します。
ちなみにこのAPIは将来的に削除する可能性もあるとの事です。
追記ここまで
以前こんなブックマークレットを書きました。
これの応用とGist APIを使ってこんなものを作りました。
/*
================================================================================
    Name        :   選択範囲をGistに保存するBookmarklet Ver1.0.0
    In          :   [none]      
    Out         :   [none]      
    Note        :   選択範囲の文字列(utf-8のみ)をGistに保存します。
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2010/01/28  |   新規作成
--------------------------------------------------------------------------------
    License     :   MIT license
    URL         :   www.kanasansoft.com
================================================================================
*/

(
    function(w,d,s,c,a,g,p,n){
        var
             t=d[c]("textarea")
            ,f=d[c]("form")
            ,i=d[c]("iframe")
            ;
        if(d[p]){
            s=d[p].createRange().text
        }else if(d[g]){
            s=d[g]()
        }else if(w[g]){
            s=w[g]()
        }
        i.name=n;
        t.value=s;
        t.name="files["+n+".txt]";
        f.method="post";
        f.action="http://gist.github.com/api/v1/plaintext/new";
        f.target=n;
        f.style.displey="none";
        f[a](i);
        f[a](t);
        d.body[a](f);
        f.submit();
    }
)
(window,document,"","createElement","appendChild","getSelection","selection",(location+"_gist_"+Math.random()).replace(/[^\d\w]+/g,"_"))
GitHubへログイン中でないと使えません。
残念な点は日本語だとUTF-8しか動作しなさそうな点。
少なくともShift JISは文字化けしました。
確認はしていないのですが文字数的にはIEでも使えるはずです。
2010/01/28 追記
Gist API+JavaScriptを使うと他人のGistのレポジトリに勝手にソースを突っ込めるって事だよなぁ。
色々試してみたところ、Gist APIはCreateはできてもUpdateは不可能みたいだけど...。

iPadとiPodの誤読/空目を防ぐ効果的な方法

次のグリモンをいれる。
以上。

2010年01月10日

Jyazo0.1.0をリリースしました

GyazoクローンのJyazoの最新バージョンをリリースしました。
0.0.6からほとんど変更はありませんが、安定して動作しているのでバージョン番号を0.1.0に変更しました。
画像を送信するGyazoサーバを簡単に変更できたり、複数のGyazoサーバに同時に送信することができます。
詳細はアーカイブファイルに含まれているREADME.txtをご覧ください。
Public Only
Public Only
Localhost Only
Localhost Only
Public and Localhost
Public and Localhost

2010年01月03日

iPhone向けトラックアップ地図を作った

位置情報取得にはiPhoneのSafari mobileのGeolocationを、地図表示にはGoogle Maps API V3を、トラックアップ機能(ヘディングアップ機能)等の地図の回転にはCSSのtransformのrotateを使用した。
地図の拡大/縮小はピンチアウト/ピンチインで、地図の回転はピンチの回転で、地図のスクロールはフリックで可能となっている。
画面上部には、「地図の種類を変更」のプルダウンメニューと、「現在地を自動追尾」「ノースアップ機能」「トラックアップ機能(ヘディングアップ機能)」のボタンが表示されている。
説明するよりも、次のURLにiPhoneかiPod touchでアクセスしてもらった方が早いと思う。
たまに自動追尾がうまくいかないことがあるけど原因不明。
問題は、Google Mapsの利用規約に違反している可能性があること。
Google Mapsは地図の下の方にGoogleのロゴと地図データの著作者名(日本の場合はZENRIN等)、利用規約へのリンクが表示されているが、地図を回転させた場合、これらが画面外にはみ出てしまう。
意図的に違反する気は全くないのだが、解決方法が思いつかない。
自分で著作権表示等を別の場所に移動しても良いのだろうか...。
誰か解決策知らない?
2010/01/03 追記
codeをGitHubにUpした。
2010/01/09
ヘディングアップという言葉はどうも和製英語らしいのでトラックアップと名前を変更。