読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

Open Layers ~点の描画とcluster~

写真やコメントなどの位置を表すための点の描画と、clusterを使って近い場所の点をまとめて丸を大きくする方法。

点の描画

Open Street Map上の点を経緯度で指定して、集合に対応できる点を描画する。

ページ初期化時

  • Open Street Mapを、projectionにEPSG900913を設定して生成する。
  • 点を描画する先のOpenLayers.Layer.Vector()を生成して、地図に追加する。
// 写真の撮影ポイント
strategy_photopnt = new OpenLayers.Strategy.Cluster();
vecPhotoLayer = new OpenLayers.Layer.Vector("Photo Points", {});

// マップにVectorを追加
olMap.addLayer(vecPhotoLayer);

点をまとめて追加する

点をまとめて追加するには、点の配列を作成して、まとめて登録する。
imagesという配列のlonに経度、latに緯度が入っているものとする。

var features = [];
for(var i=0 ; i<images.length ; i++) {
    features.push(
        new OpenLayers.Feature.Vector(
                new OpenLayers.Geometry.Point(
                    images[i].lon,images[i].lat).transform(projLonLat,projSpMerc),
                {x: images[i].lon, y: images[i].lat, file: images[i].file}
        )
    );
}

// 写真の点を追加する
vecPhotoLayer.removeFeatures(vecPhotoLayer.features);
vecPhotoLayer.addFeatures(features);
  • OpenLayers.Feature.Vector型は、Vector型のレイヤーに追加するオブジェクトを表す。生成時にGeometry、属性、スタイルを渡す。
    • Featureの形状は、OpenLayers.Geometryで表す。点はPointで、x,yの順に引数を受けとる。今回はSpherical Mercator座標系のOSMを利用するので、transformを使って経緯度をSpherical Mercator座標系に変換している。
    • Featureに追加するプロパティを属性に設定。この例ではx,y,fileを渡している。これは自由に追加してよい。Featureから後で取り出したいデータを追加しておく。
    • スタイルは省略している
  • 点リストの作成が完了したら、一度レイヤーに追加済みのfeatureを削除してから、新しいfeatureを追加する。

点を個別に追加する

点を一つずつ追加したい場合、OpenLayers.Layer.drawFeature()が使えそうだが、Zoomした際に点がずれてしまった。そこで、今回はOpenLayers.Feature.Vector型の配列を用意して、そこに点の追加をして、レイヤーからFeatureの削除と追加を行う方法で実装した。

// featureに追加描画
ftPhotoPoints.push(
    new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.Point(lon,lat).transform(projLonLat,projSpMerc),
            {x: lon, y: lat, file: fl}
    )
);

// 写真の点を追加する
vecPhotoLayer.removeFeatures(vecPhotoLayer.features);
vecPhotoLayer.addFeatures(ftPhotoPoints);

Clusterによる点の集約化

OpenLayers.Strategy.Cluster()クラスをVectorレイヤーに追加すると、指定の距離内に、指定の数以上の点があった場合、1つの大きな円に集約化してくれる。以下、使い方。

スタイルの定義

//// 写真点のスタイル
var style_photopnt = new OpenLayers.Style({
    pointRadius: "${radius}",
    fillColor: "#ffcc66",
    fillOpacity: 0.8,
    strokeColor: "#cc6633",
    strokeWidth: "${width}",
    strokeOpacity: 0.8
},{
    context: {
        width: function(feature) {
            return (feature.cluster) ? 2 : 1;
        },
        radius: function(feature) {
            var pix = 16;
            if(feature.cluster) {
                pix = Math.min(feature.attributes.count, 8)*2 + 4;
            }
            return pix;
        }
    }
});

初期化

// 写真の撮影ポイント
vecPhotoLayer = new OpenLayers.Layer.Vector("Photo Points", {
    strategies: [new OpenLayers.Strategy.Cluster()],
    styleMap: new OpenLayers.StyleMap({
        "default": style_photopnt,
        "select": {
            fillColor: "#8aeeef",
            strokeColor: "#32a8a9"
        }
    }),
    distance: 20,
    threshold: 2
});
  • 予め、OpenLayers.Strategyオブジェクトを生成する。
  • OpenLayers.Layer.Vector(ベクトル名,パラメータ)として初期化。
    • strategiesに、作成したStrategyオブジェクトを指定。
    • styleMapに、通常時のスタイルと、選択時の水色のスタイルを設定。
    • distanceに、集約化する時の距離を指定。
    • thresholdに、集約化するための最低個数を指定。

Featureの選択

追加したFeatureをマウスカーソルなどで選択するために、Openlayers.Control.SelectFeature()オブジェクトを利用する。

初期化

// selectFeatureを設定する
var select = new OpenLayers.Control.SelectFeature(
    vecPhotoLayer,{hover:true}
);
olMap.addControl(select);
select.activate();
vecPhotoLayer.events.on({"featureselected": display});
  • 追加先のレイヤーと、発生させるイベント(今回はFeature上にマウスカーソルが来たときのイベント)を指定して、OpenLayers.Control.SelectFeature()オブジェクトを生成。
  • 作成したSelectFeatureを地図に追加。
  • SelectFeatureをactivate()して有効化。
  • 追加先のレイヤーに、イベントハンドラを指定。featureselectedで選択時で、display関数を呼び出すようにする。

イベント

// ホバーしたfeatureの情報を表示
function display(event) {
    var f = event.feature;
    if (f.cluster) {
        for (var i=0 ; i<f.cluster.length ; i++) {
            // ホバーした場所の情報を取り出す。
            console.log(
                "photo["+i+"]="+f.cluster[i].attributes.x+"/"
                +f.cluster[i].attributes.y+"/"
                +f.cluster[i].attributes.file.name);
        }
    }
    else {
        console.log("no cluster");
    }
}

設定したイベントの例。

  • event.featureに、選択されたFeatureのデータが渡される。
  • event.feature.clusterに、集約されたデータが配列で入っている。
  • attributes.countに、Featureの数。
  • cluster[n].attributes.パラメータ名で、各featureの属性に設定した値が取得できる