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;
カスタム関数の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;
}
}
loadPoisFromJsonData
関数では、マーカーの画像として使用するAR.ImageResource
を作成します。
// start loading marker assets
World.markerDrawable_idle = new AR.ImageResource("assets/marker_idle.png");
マーカーを作成するため、指定されたロケーションに新しい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]
}
});
最後に、すべてのマーカーが正常にロードされたことをユーザーに知らせるために、ステータスメッセージを更新します。
World.updateStatusMessage('1 place loaded');
特定のロケーションへのラベル付きマーカー表示
対応するサンプルタイトル名「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"
};
この他にもマーカーに関する変更があるので、関連するコードを別のMarker
クラスに抽出しました(marker.jsを参照)。loadPoisFromJsonData
からMarker
クラスに移動したのは、AR.GeoLocation
の作成、AR.ImageDrawable
の作成、およびAR.GeoObject
の作成の部分です。そして、loadPoisFromJsonData
関数でMarker
をインスタンス化します。
// create the marker
var marker = new Marker(poiData);
複数の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;
}
また、指定した長さより長いテキスト文字列の末尾を切り捨てる関数も追加します。この関数を使用して、タイトルと説明を短くします。
String.prototype.trunc = function(n) {
return this.substr(0, n - 1) + (this.length > n ? '...' : '');
};
複数のロケーションへのマーカー表示とマーカーへのイベント追加
対応するサンプルタイトル名「Multiple Pois」
3番目のサンプルは2つのパートで構成されています。最初のパートでは複数のマーカーの作成方法を示し、2番目のパートではマーカーへのイベント追加について示します。
複数のマーカーを作成するため、World
クラスを変更します。緯度と経度をパラメーターとして受け取るrequestDataFromLocal
関数を追加し、これを使用してユーザーの近辺のランダムなロケーションに異なるマーカーを作成します。この新しい関数はlocationChanged
から(前のサンプルでのloadPoisFromJsonData
の代わりに)呼び出します。
World.requestDataFromLocal(lat, lon);
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);
}
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');
}
これで複数のマーカーを表示するための実装は完了しました。次に、マーカー背景が選択されたときに、背面画像を変更し、マーカーの選択状態を表現する方法を確認していきます。
選択状態を表現するために使用されるAR.ImageDrawable
はmarker.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)
});
この関数は、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);
}
}
};
};
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);
};
拡張オブジェクトのない領域がタップされたときにマーカーを選択解除できるようにするため、各マーカーを含む配列をWorld
オブジェクトに格納します。
World.markerList.push( new Marker(poiData) );
拡張オブジェクトのない領域がタップされたことを検出するため、AR.context.onScreenClick
にカスタム関数を設定し、そこで現在選択されているマーカーの選択状態を解除します。
onScreenClick: function onScreenClickFn() {
if (World.currentMarker) {
World.currentMarker.setDeselected(World.currentMarker);
}
}
マーカーへのアニメーション追加と方向インジケーターの表示
対応するサンプルタイトル名「Selecting Pois」
最後のパートでは、マーカーへのアニメーション追加について説明します。あわせてAR.PropertyAnimation
とAR.AnimationGroup
の概念についても説明します。また、選択状態のマーカーがカメラビューから外れたときに、方向インジゲータを表示する方法についても説明します。
AR.PropertyAnimation
を使用すると、拡張オブジェクトの多くのプロパティにアニメーションを追加できます。このサンプルでは、2つの拡張オブジェクトの背面に設定する拡張オブジェクトの不透明度を使用して一方をフェードアウト、他方をフェードインします。また、スケーリングにもアニメーションを追加します。マーカーサイズが時間とともに変化するので、背面の拡張オブジェクトに対する相対的な位置関係を維持するためにラベルもアニメーション表示する必要があります。AR.AnimationGroup
を使用して、すべてのアニメーションを同時または連続的に再生します。
marker.js
で、2つの新しい変数を定義します。これらの変数にAR.AnimationGroup
への参照を保持し、これを使用してアニメーションを開始または停止します。
this.animationGroup_idle = null;
this.animationGroup_selected = null;
marker.js
のsetSelected
関数と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]);
}
start
関数を使用してAR.AnimationGroup
を開始します。
marker.animationGroup_selected.start();
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');
}
}
方向インジケーターを表わす画像をAR.ImageResource
にロードします。さらに、そのAR.ImageResource
を使用してAR.ImageDrawable
を作成します。画像のオフセットとアンカーに関するオプションを設定して、方向インジケーターが画面の端に正しく表示されるようにします。
this.directionIndicatorDrawable = new AR.ImageDrawable(World.markerDrawable_directionIndicator, 0.5, {
enabled: false
});
最後のステップは、作成したAR.ImageDrawable
をAR.GeoObject
のindicator
として定義することです。方向インジケーターは必要なときに自動的に表示されます。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
}
});