POIデータの拡張

POIデータ拡張の一連のサンプルでは、特定のロケーションにマーカーを表示する方法を示します。

特定のロケーションへのマーカー表示

対応するサンプルタイトル名「Poi At Location」

最初のパートでは、画像を特定のロケーションに表示します。そのために、AR.context.onLocationChanged()コールバックを使用して現在のロケーションを取得します。ロケーションを取得した後、そこにAR.ImageDrawableを配置します。

JavaScriptコードはすべてpoiatlocation.jsファイルに含まれています。

画像をロードしてARchitect Worldに表示する方法は、クライアント認識サンプルですでに説明しました。このサンプルでは、World変数を定義するときにAR.ImageResourceをロードします。ロードした画像リソースは、その後作成するすべてのマーカーで再利用します。

poiatlocation.jsの最後の行は、AR.context.onLocationChangedコールバックにをカスタム関数を設定する方法を示します。

AR.context.onLocationChanged = World.onLocationChanged;
ソースコードをGitHubで見る

カスタム関数のWorld.onLocationChangedは、World.initiallyLoadedDataフラグを使用してこの関数がすでに呼び出されたかどうかをチェックします。また、AR.context.onLocationChangedをnullに設定する可能性もあります。その場合、この関数はそれ以上呼び出されず、位置の更新は取得されません。

World.onLocationChangedの初回の呼び出しでロケーションを含むオブジェクトを作成し、さらにWorld.loadPoisFromJsonData関数でそのオブジェクトを使用してマーカーを作成します。

locationChanged: function locationChangedFn(lat, lon, alt, acc) {
    // request data if not already present
    if (!World.initiallyLoadedData) {
        var poiData = {
            "id": 1,
            "longitude": (lon + (Math.random() / 5 - 0.1)),
            "latitude": (lat + (Math.random() / 5 - 0.1)),
            "altitude": 100.0
        };
        World.loadPoisFromJsonData(poiData);
        World.initiallyLoadedData = true;
    }
}
ソースコードをGitHubで見る

loadPoisFromJsonData関数では、マーカーの画像として使用するAR.ImageResourceを作成します。

// start loading marker assets
World.markerDrawable_idle = new AR.ImageResource("assets/marker_idle.png");
ソースコードをGitHubで見る

マーカーを作成するため、指定されたロケーションに新しいAR.GeoObjectオブジェクトを作成します。AR.GeoObjectには、複数のAR.GeoLocationを設定できます。AR.Drawableは、カメラビュー、レーダー、または方向インジケーターに設定できます。レーダーと方向インジケーターについては、後のサンプルで詳しく取り上げます。

    // create the marker
    var markerLocation = new AR.GeoLocation(poiData.latitude, poiData.longitude, poiData.altitude);
    var markerImageDrawable_idle = new AR.ImageDrawable(markerDrawable_idle, 2.5, {
        zOrder: 0,
        opacity: 1.0
    });
    // create GeoObject
    var markerObject = new AR.GeoObject(markerLocation, {
        drawables: {
            cam: [markerImageDrawable_idle]
        }
    });
ソースコードをGitHubで見る

最後に、すべてのマーカーが正常にロードされたことをユーザーに知らせるために、ステータスメッセージを更新します。

World.updateStatusMessage('1 place loaded');
ソースコードをGitHubで見る

特定のロケーションへのラベル付きマーカー表示

対応するサンプルタイトル名「Poi With Label」

2番目のパートではタイトルと説明ラベルをマーカーに追加し、さらにその他のオプションパラメータについても説明します。

JavaScriptに対するすべての変更はpoiwithlabel.jsに加えます。このファイルは単にファイル名を変更しただけであり、内容はpoiatlocation.jsとほとんど同じです。

locationChanged関数で、説明とタイトルをマーカーに追加します。

var poiData = {
    "id": 1,
    "longitude": (lon + (Math.random() / 5 - 0.1)),
    "latitude": (lat + (Math.random() / 5 - 0.1)),
    "altitude": 100.0,
    "description": "This is the description of POI#1",
    "title": "POI#1"
};
ソースコードをGitHubで見る

この他にもマーカーに関する変更があるので、関連するコードを別のMarkerクラスに抽出しました(marker.jsを参照)。loadPoisFromJsonDataからMarkerクラスに移動したのは、AR.GeoLocationの作成、AR.ImageDrawableの作成、およびAR.GeoObjectの作成の部分です。そして、loadPoisFromJsonData関数でMarkerをインスタンス化します。

    // create the marker
    var marker = new Marker(poiData);
ソースコードをGitHubで見る

複数のAR.Drawableを同じロケーションに描画するとき、注意しなければならない点が2つあります。それは、どちらの拡張オブジェクトを前面に描画するかと、配置のオフセットが必要かどうかです。Wikitude SDKには、どちらのシナリオにも対応できるように、拡張オブジェクトを調整する機能が用意されています。

AR.Labelを前面に表示するため、背面の(AR.ImageDrawable2D)のzOrderを0に設定します。ラベルのzOrderは両方とも1にします。こうすることで、ラベルが確実に背面の拡張オブジェクトよりも前面に描画されます。

2つのラベルをAR.GeoObjectに設定された同じロケーションに描画すると、両者が重なります。ラベルの配置を調整するには、translate.xプロパティとtranslate.yプロパティを変更します。オフセットの単位はSDUです。SDUの詳細については、このセクションを参照してください。

以下のコードでは、両方のAR.Labelを初期化し、配置を設定しています。どちらのラベルも、AR.ImageDrawableと同じようにAR.GeoObjectのcamプロパティに追加されていることに注意してください。

function Marker(poiData) {
    this.poiData = poiData;
    var markerLocation = new AR.GeoLocation(poiData.latitude, poiData.longitude, poiData.altitude);
    this.markerDrawable_idle = new AR.ImageDrawable(World.markerDrawable_idle, 2.5, {
        zOrder: 0,
        opacity: 1.0
    });
    this.titleLabel = new AR.Label(poiData.title.trunc(10), 1, {
        zOrder: 1,
        translate: {
            y: 0.55
        },
        style: {
            textColor: '#FFFFFF',
            fontStyle: AR.CONST.FONT_STYLE.BOLD
        }
    });
    this.descriptionLabel = new AR.Label(poiData.description.trunc(15), 0.8, {
        zOrder: 1,
        translate: {
            y: -0.55
        },
        style: {
            textColor: '#FFFFFF'
        }
    });
    // Changed: 
    this.markerObject = new AR.GeoObject(markerLocation, {
        drawables: {
            cam: [this.markerDrawable_idle, this.titleLabel, this.descriptionLabel]
        }
    });
    return this;
}
ソースコードをGitHubで見る

また、指定した長さより長いテキスト文字列の末尾を切り捨てる関数も追加します。この関数を使用して、タイトルと説明を短くします。

String.prototype.trunc = function(n) {
       return this.substr(0, n - 1) + (this.length > n ? '...' : '');
};
ソースコードをGitHubで見る

複数のロケーションへのマーカー表示とマーカーへのイベント追加

対応するサンプルタイトル名「Multiple Pois」

3番目のサンプルは2つのパートで構成されています。最初のパートでは複数のマーカーの作成方法を示し、2番目のパートではマーカーへのイベント追加について示します。

複数のマーカーを作成するため、Worldクラスを変更します。緯度と経度をパラメーターとして受け取るrequestDataFromLocal関数を追加し、これを使用してユーザーの近辺のランダムなロケーションに異なるマーカーを作成します。この新しい関数はlocationChangedから(前のサンプルでのloadPoisFromJsonDataの代わりに)呼び出します。

World.requestDataFromLocal(lat, lon);
ソースコードをGitHubで見る

loadPoisFromJsonData関数は、新しいrequestDataFromLocal関数の中で、POIデータが作成された後に呼び出します。


    // request POI data
    requestDataFromLocal: function requestDataFromLocalFn(centerPointLatitude, centerPointLongitude) {
        var poisToCreate = 20;
        var poiData = [];
        for (var i = 0; i < poisToCreate; i++) {
            poiData.push({
                "id": (i + 1),
                "longitude": (centerPointLongitude + (Math.random() / 5 - 0.1)),
                "latitude": (centerPointLatitude + (Math.random() / 5 - 0.1)),
                "description": ("This is the description of POI#" + (i + 1)),
                "altitude": "100.0",
                "name": ("POI#" + (i + 1))
            });
        }
        World.loadPoisFromJsonData(poiData);
    }
ソースコードをGitHubで見る

loadPoisFromJsonDataの引数はこれまでのような単一オブジェクトではなく配列として扱われるため、コードをそれに適応させる必要があります。loadPoisFromJsonData関数に引数として渡されたPOIデータの配列を使用して、poiDataオブジェクトを作成します。forループですべてのpoiDataオブジェクトを反復処理し、オブジェクトごとに新しいsinglePoiオブジェクトを作成します。poiDataオブジェクトで定義されたロケーション、タイトル、および説明を含む複数のマーカーを作成するため、new Marker(poiData)を複数回呼び出すことができます。そこでMarkerオブジェクトを作成し、Worldクラスのメンバ変数として定義されたmarkerList配列にMarkerオブジェクトを格納します。markerList配列はマーカーの選択/選択解除に必要となるもので、このサンプルのもう少し後で説明します。最後に、ロードされたマーカーの数でステータスメッセージを更新します。


        // called to inject new POI data
        loadPoisFromJsonData: function loadPoisFromJsonDataFn(poiData) {
            // empty list of visible markers
            World.markerList = [];
            // start loading marker assets
            World.markerDrawable_idle = new AR.ImageResource("assets/marker_idle.png");
            // loop through POI-information and create an AR.GeoObject (=Marker) per POI
            for (var currentPlaceNr = 0; currentPlaceNr < poiData.length; currentPlaceNr++) {
                var singlePoi = {
                    "id": poiData[currentPlaceNr].id,
                    "latitude": parseFloat(poiData[currentPlaceNr].latitude),
                    "longitude": parseFloat(poiData[currentPlaceNr].longitude),
                    "altitude": parseFloat(poiData[currentPlaceNr].altitude),
                    "title": poiData[currentPlaceNr].name,
                    "description": poiData[currentPlaceNr].description
                };
                World.markerList.push(new Marker(singlePoi));
            }
            World.updateStatusMessage(currentPlaceNr + ' places loaded');
        }
ソースコードをGitHubで見る

これで複数のマーカーを表示するための実装は完了しました。次に、マーカー背景が選択されたときに、背面画像を変更し、マーカーの選択状態を表現する方法を確認していきます。

選択状態を表現するために使用されるAR.ImageDrawablemarker.jsで定義されています。

ユーザー操作に反応するため、AR.DrawableごとにonClickトリガーを設定します。このトリガーに設定された関数は、ユーザーが拡張オブジェクトをタップするたびに呼び出されます。このAR.ImageDrawableを作成するコードを以下に示します。

this.markerDrawable_idle = new AR.ImageDrawable(World.markerDrawable_idle, 2.5, {
    zOrder: 0,
    opacity: 1.0,
    onClick: Marker.prototype.getOnClickTrigger(this)
});
ソースコードをGitHubで見る

この関数は、marker.jsで定義されています。isSelected変数を基に選択状態をチェックして適切な関数を実行します。パラメータにはクリックされたマーカーが渡されます。

Marker.prototype.getOnClickTrigger = function(marker) {
    return function() {
            if (marker.isSelected) {
                Marker.prototype.setDeselected(marker);
            } else {
                Marker.prototype.setSelected(marker);
                try {
                    World.onMarkerSelected(marker);
                } catch (err) {
                    alert(err);
                }
            }
    };
};
ソースコードをGitHubで見る

setSelected関数とsetDeselected関数は、Markerのプロトタイプ関数です。

これらの関数の動作はまったく反対の実装がされていますが、実行するステップは同じです。したがって、一方の関数(setSelected)のみを詳しく説明します。マーカーを選択するには3つのステップが必要です。まず、状態を適切に設定します。次に、選択状態を表す拡張オブジェクトを有効にし、未選択状態を表す拡張オブジェクトを無効にします。そのため、選択表示状態にする方のopacityプロパティを1.0に設定し、未選択状態にする方のopacityプロパティを0.0に設定します。最後に、選択状態を表す拡張オブジェクトに対してのみonClick関数を設定します。

Marker.prototype.setSelected = function(marker) {
    marker.isSelected = true;
    marker.markerDrawable_idle.opacity = 0.0;
    marker.markerDrawable_selected.opacity = 1.0;
    marker.markerDrawable_idle.onClick = null;
    marker.markerDrawable_selected.onClick = Marker.prototype.getOnClickTrigger(marker);
};
ソースコードをGitHubで見る

拡張オブジェクトのない領域がタップされたときにマーカーを選択解除できるようにするため、各マーカーを含む配列をWorldオブジェクトに格納します。

World.markerList.push( new Marker(poiData) );
ソースコードをGitHubで見る

拡張オブジェクトのない領域がタップされたことを検出するため、AR.context.onScreenClickにカスタム関数を設定し、そこで現在選択されているマーカーの選択状態を解除します。

onScreenClick: function onScreenClickFn() {
    if (World.currentMarker) {
        World.currentMarker.setDeselected(World.currentMarker);
    }
}
ソースコードをGitHubで見る

マーカーへのアニメーション追加と方向インジケーターの表示

対応するサンプルタイトル名「Selecting Pois」

最後のパートでは、マーカーへのアニメーション追加について説明します。あわせてAR.PropertyAnimationAR.AnimationGroupの概念についても説明します。また、選択状態のマーカーがカメラビューから外れたときに、方向インジゲータを表示する方法についても説明します。

AR.PropertyAnimationを使用すると、拡張オブジェクトの多くのプロパティにアニメーションを追加できます。このサンプルでは、2つの拡張オブジェクトの背面に設定する拡張オブジェクトの不透明度を使用して一方をフェードアウト、他方をフェードインします。また、スケーリングにもアニメーションを追加します。マーカーサイズが時間とともに変化するので、背面の拡張オブジェクトに対する相対的な位置関係を維持するためにラベルもアニメーション表示する必要があります。AR.AnimationGroupを使用して、すべてのアニメーションを同時または連続的に再生します。

marker.jsで、2つの新しい変数を定義します。これらの変数にAR.AnimationGroupへの参照を保持し、これを使用してアニメーションを開始または停止します。

this.animationGroup_idle = null;
this.animationGroup_selected = null;
ソースコードをGitHubで見る

marker.jssetSelected関数とsetDeselected関数を適応させる必要があります。今回もsetSelected関数についてのみ説明します。

AR.AnimationGroupには並行アニメーションと連続アニメーションの2種類あります。並行アニメーションはアニメーショングループ内のアニメーションを同時再生し、連続アニメーションはアニメーショングループ内のアニメーションを1つずつ順に再生します。このサンプルでは並行アニメーションを設定したAR.AnimationGroupを使用します。

if (marker.animationGroup_selected === null) {
    var hideIdleDrawableAnimation = new AR.PropertyAnimation(marker.markerDrawable_idle, "opacity", null, 0.0, kMarker_AnimationDuration_ChangeDrawable);
    var showSelectedDrawableAnimation = new AR.PropertyAnimation(marker.markerDrawable_selected, "opacity", null, 0.8, kMarker_AnimationDuration_ChangeDrawable);
    var idleDrawableResizeAnimationX = new AR.PropertyAnimation(marker.markerDrawable_idle, 'scale.x', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var selectedDrawableResizeAnimationX = new AR.PropertyAnimation(marker.markerDrawable_selected, 'scale.x', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var titleLabelResizeAnimationX = new AR.PropertyAnimation(marker.titleLabel, 'scale.x', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var descriptionLabelResizeAnimationX = new AR.PropertyAnimation(marker.descriptionLabel, 'scale.x', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var idleDrawableResizeAnimationY = new AR.PropertyAnimation(marker.markerDrawable_idle, 'scale.y', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var selectedDrawableResizeAnimationY = new AR.PropertyAnimation(marker.markerDrawable_selected, 'scale.y', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var titleLabelResizeAnimationY = new AR.PropertyAnimation(marker.titleLabel, 'scale.y', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    var descriptionLabelResizeAnimationY = new AR.PropertyAnimation(marker.descriptionLabel, 'scale.y', null, 1.2, kMarker_AnimationDuration_Resize, new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
        amplitude: 2.0
    }));
    marker.animationGroup_selected = new AR.AnimationGroup(AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL, [hideIdleDrawableAnimation, showSelectedDrawableAnimation, idleDrawableResizeAnimationX, selectedDrawableResizeAnimationX, titleLabelResizeAnimationX, descriptionLabelResizeAnimationX,idleDrawableResizeAnimationY, selectedDrawableResizeAnimationY, titleLabelResizeAnimationY, descriptionLabelResizeAnimationY]);
}
ソースコードをGitHubで見る

start関数を使用してAR.AnimationGroupを開始します。

    marker.animationGroup_selected.start();
ソースコードをGitHubで見る

Marker.prototype.getOnClickTrigger関数では、アニメーションが現在実行されていない場合にのみ関数を呼び出します。

if (!Marker.prototype.isAnyAnimationRunning(marker)) {
    if (marker.isSelected) {
        Marker.prototype.setDeselected(marker);
    } else {
        Marker.prototype.setSelected(marker);
        try {
            World.onMarkerSelected(marker);
        } catch (err) {
            alert(err);
        }
    }
    } else {
        AR.logger.debug('a animation is already running');
    }
}
ソースコードをGitHubで見る

方向インジケーターを表わす画像をAR.ImageResourceにロードします。さらに、そのAR.ImageResourceを使用してAR.ImageDrawableを作成します。画像のオフセットとアンカーに関するオプションを設定して、方向インジケーターが画面の端に正しく表示されるようにします。

this.directionIndicatorDrawable = new AR.ImageDrawable(World.markerDrawable_directionIndicator, 0.5, {
    enabled: false
});
ソースコードをGitHubで見る

最後のステップは、作成したAR.ImageDrawableAR.GeoObjectindicatorとして定義することです。方向インジケーターは必要なときに自動的に表示されます。AR.Drawableのサブクラス(例: AR.Circle)を方向インジケーターとしても使用できます。

this.markerObject = new AR.GeoObject(markerLocation, {
    drawables: {
        cam: [    this.markerDrawable_idle, 
                this.markerDrawable_selected, 
                this.titleLabel, 
                this.descriptionLabel
             ],
            indicator: this.directionIndicatorDrawable
        }
    });
ソースコードをGitHubで見る