さまざまなPOIデータの表示方法
1度にいくつのマーカーを表示すべきか? 同じ方向にあるPOIデータの扱いをどうするか? 表示対象となるPOIデータの最大距離範囲は?POIデータに関する 説明が長文になる場合どのように表示すればいいか?など、ARchitect Worldに多数のマーカーを表示する場合、多くの検討すべき点があります。 以下のサンプルは6つのパートで構成されており、ロケーションベース型ARを実装する場合によくある質問を取り上げています。
POIデータの詳細情報の表示
対応するサンプルタイトル名「Presenting Details」
POIデータには通常名前があり、その説明にかなり長い文章が必要なこともあります。そのような場合、たとえばマーカーとその名前、および説明の一部を表示するだけにし、ユーザーがそのマーカーを選択すると詳しい情報が表示されるようにするといった対処をすることが考えられます。
jQuery Mobileは、「より少ないコードでより多くのことを実行する」という思想で設計され、モバイルデバイス用の魅力的なユーザーインタフェースを作成する簡単な手段の1つです。jQuery Mobileを使用すれば、モバイルデバイスまたはOSごとに固有のアプリケーションを作成するのではなく、一般に普及しているあらゆるスマートフォン、タブレット、およびデスクトッププラットフォームで動作する高度にブランディングされた単一のWebサイトまたはアプリケーションを設計できます(jQuery MobileのWebサイトからの引用)。このセクションのサンプルではARchitect WorldへのUIの実装にはjQuery Mobileを使用します。jQuery Mobileは十分なドキュメントを備えており、アプリケーションにバンドルすることも、独自のWebサーバーでホストすることもできます。
このサンプルでは、マーカーが選択されたときに対応するPOIデータの詳細情報をパネルに表示します。サンプルのディレクトリにあるindex.htmlは以下のようになっています。
<!-- panel containing POI detail information -->
<div data-role="panel" id="panel-poidetail" data-position="right" data-display="overlay" style="background-color:#F0F0F0;" data-theme="c">
<!-- header with "close" button -->
<div data-role="header" data-theme="c">
<h1>Details</h1>
<a href="#header" data-rel="close">Close</a>
</div>
<!-- content of POI detail page, you may also add thumbnails etc. here if you like -->
<div data-role="content">
<!-- title -->
<h3 id="poi-detail-title"></h3>
<!-- description -->
<h4 id="poi-detail-description"></h4>
<!-- distance -->
<h4>Distance: <a id="poi-detail-distance"></a></h4>
</div>
マーカーを選択すると、パネルが右から左にアニメーション表示され、POIデータの詳細情報を確認できます。
マーカーの選択状態を解除するため、panelbeforeclose
イベントを使用します。presentingdetails.jsを参照してください。
onMarkerSelected: function onMarkerSelectedFn(marker) {
World.currentMarker = marker;
// update panel values
$("#poi-detail-title").html(marker.poiData.title);
$("#poi-detail-description").html(marker.poiData.description);
var distanceToUserValue = (marker.distanceToUser > 999) ? ((marker.distanceToUser / 1000).toFixed(2) + " km") : (Math.round(marker.distanceToUser) + " m");
$("#poi-detail-distance").html(distanceToUserValue);
// show panel
$("#panel-poidetail").panel("open", 123);
$("#panel-poidetail").on("panelbeforeclose", function(event, ui) {
World.currentMarker.setDeselected(World.currentMarker);
});
POIデータのレーダー表示
対応するサンプルタイトル名「Adding Radar」
ユーザーの近辺にある任意のロケーションに関してユーザーにヒントを与えることを推奨します。方位を示す最も簡単な方法は、AR.Radar
を使用することです。AR.GeoObject
はすべてレーダーに表示でき、通常は小さな点でロケーションが示されます。
レーダーにおけるAR.GeoObject
の表現は、その拡張オブジェクトの設定で定義します(AR.GeoObject
コンストラクターの2番目の引数)。drawables.radar
を設定すると、そのオブジェクトがたとえばAR.Circle
としてレーダーに示されます。詳しくはmarker.jsを参照してください。
this.radarCircle = new AR.Circle(0.03, {
horizontalAnchor: AR.CONST.HORIZONTAL_ANCHOR.CENTER,
opacity: 0.8,
style: {
fillColor: "#ffffff"
}
});
this.radardrawables = [];
this.radardrawables.push(this.radarCircle);
さらに、選択されたマーカーをレーダー上に異なる色で表示します。詳しくはmarker.jsを参照してください。
this.radarCircleSelected = new AR.Circle(0.05, {
horizontalAnchor: AR.CONST.HORIZONTAL_ANCHOR.CENTER,
opacity: 0.8,
style: {
fillColor: "#0066ff"
}
});
this.radardrawablesSelected = [];
this.radardrawablesSelected.push(this.radarCircleSelected);
this.markerObject = new AR.GeoObject(markerLocation, {
drawables: {
cam: [ this.markerDrawable_idle,
this.markerDrawable_selected,
this.titleLabel,
this.descriptionLabel ],
indicator: this.directionIndicatorDrawable,
radar: this.radardrawables
}
});
次の図のように、選択されたマーカーをレーダーで強調するため、
Marker.prototype.setSelected
関数とMarker.prototype.setDeselected
関数でレーダー上の表示を更新します。詳しくはmarker.jsを参照してください。
marker.markerObject.drawables.radar = marker.radardrawablesSelected;
[...]
marker.markerObject.drawables.radar = marker.radardrawables;
レーダーの位置とサイズはDOM要素を使用して定義します。このサンプルでは、idがradarContainer
であるdiv
要素を使用しています。index.htmlを参照してください。
<div class="radarContainer_left" id="radarContainer"></div>
レーダーのサイズと位置はCSSクラスで定義します。poi-radar.cssを参照してください。
/* position of POI-radar*/
.radarContainer_left {
position:absolute;
top:0px;
left:0px;
width:100px;
height:100px;
}
レーダーコンテナーとなるDOM要素では絶対位置を使用することを推奨します。DOM要素がjQueryまたはレスポンシブデザインによって即座に更新される場合は、AR.radar.notifyUpdateRadarPosition();
を使用してレーダーの位置とサイズを強制的に更新します。それ以外の場合は、最初に設定した位置とサイズが使用されます。
レーダーはカスタマイズ可能であり、サンプルでは、JavaScriptコードで独自のコンポーネントとして実装します。詳しくはradar.jsを参照してください。
var PoiRadar = {
hide: function hideFn() {
AR.radar.enabled = false;
},
show: function initFn() {
// the div defined in the index.htm
AR.radar.container = document.getElementById("radarContainer");
// set the back-ground image for the radar
AR.radar.background = new AR.ImageResource("assets/radar_bg.png");
// set the north-indicator image for the radar
// (not necessary if you don't want to display a north-indicator)
AR.radar.northIndicator.image = new AR.ImageResource("assets/radar_north.png");
// center of north indicator and radar-points in the radar asset,
// usually center of radar is in the exact middle of the background,
// meaning 50% X and 50% Y axis --> 0.5 for centerX/centerY
AR.radar.centerX = 0.5;
AR.radar.centerY = 0.5;
AR.radar.radius = 0.3;
AR.radar.northIndicator.radius = 0.0;
AR.radar.enabled = true;
},
updatePosition: function updatePositionFn() {
if (AR.radar.enabled) {
AR.radar.notifyUpdateRadarPosition();
}
},
// you may define some custom action when user pressed radar,
// e.g. display distance, custom filtering etc.
clickedRadar: function clickedRadarFn() {
alert("Radar Clicked");
},
setMaxDistance: function setMaxDistanceFn(maxDistanceMeters) {
AR.radar.maxDistance = maxDistanceMeters;
}
};
レーダーをアクティブにするには、PoiRadar.show
関数を呼び出します。必要に応じてonClickトリガーを定義できます。詳しくはaddingradar.jsを参照してください。
// show radar & set click-listener
PoiRadar.show();
$('#radarContainer').unbind('click');
$("#radarContainer").click(PoiRadar.clickedRadar);
レーダーに表示するPOIデータの距離による表示制限
対応するサンプルタイトル名「Limiting Range」
ユーザーはある特定の距離範囲内のPOIデータにのみ関心がある場合があります。このサンプルでは、タイトルバーにボタンを追加してユーザーが対象範囲を変更できるようにします。
まず、タイトルバーにボタンを追加します。
index.html
<!-- header of UI holding feature buttons -->
<div id ="header-status" data-role="header" data-position="fixed" data-theme="c">
<a href="javascript: World.showRange();" data-icon="gear" data-inline="true" data-mini="true">Range</a>
<h1></h1>
</div>
次に、対象範囲の変更に使用するパネルを定義します。このサンプルでは、現在の対象範囲(m単位)とその範囲内にあるPOIデータの数をパネルに表示します。
index.html
<!-- range panel -->
<div data-role="panel" id="panel-distance" data-position="left" data-display="overlay" style="background-color:#F0F0F0;" data-theme="c">
<!-- header with close button -->
<div data-role="header" data-theme="c">
<h1>Range</h1>
<a href="#header" data-rel="close">Close</a>
</div>
<!-- distance information, calculated/updated in code -->
<div data-role="content">
<!-- Range in m/km-->
<h4> Range: <a id="panel-distance-value"></a></h4>
<!-- Amount of visible places -->
<h4> Visible: <a id="panel-distance-places"></a></h4>
<!-- default slider -->
<input id="panel-distance-range" type="range" data-highlight="true" name="rangeSlider" min="0" max="100" value="100" data-show-value="false" step="5" data-popup-enabled="false">
</div>
</div>
ユーザーがスライダーの値を変更するたびにWorld.updateRangeValues
関数が実行されます。この関数は、最大範囲と範囲内にあるPOIデータの総数を計算し、AR.context.scene.cullingDistance
を設定します。さらに、PoiRadar.setMaxDistance
を実行してレーダーとマーカーの表示を更新します。詳しくはlimitingrange.jsを参照してください。
// updates values show in "range panel"
updateRangeValues: function updateRangeValuesFn() {
// get current slider value (0..100);
var slider_value = $("#panel-distance-range").val();
// max range relative to the maximum distance of all visible places
var maxRangeMeters = Math.round(World.getMaxDistance() * (slider_value / 100));
// range in meters including metric m/km
var maxRangeValue = (maxRangeMeters > 999) ? ((maxRangeMeters / 1000).toFixed(2) + " km") : (Math.round(maxRangeMeters) + " m");
// number of places within max-range
var placesInRange = World.getNumberOfVisiblePlacesInRange(maxRangeMeters);
// update UI labels accordingly
$("#panel-distance-value").html(maxRangeValue);
$("#panel-distance-places").html((placesInRange != 1) ? (placesInRange + " Places") : (placesInRange + " Place"));
// update culling distance, so only places within given range are rendered
AR.context.scene.cullingDistance = Math.max(maxRangeMeters, 1);
// update radar's maxDistance so radius of radar is updated too
PoiRadar.setMaxDistance(Math.max(maxRangeMeters, 1));
},
// returns number of places with same or lower distance than given range
getNumberOfVisiblePlacesInRange: function getNumberOfVisiblePlacesInRangeFn(maxRangeMeters) {
// sort markers by distance
World.markerList.sort(World.sortByDistanceSorting);
// loop through list and stop once a placemark is out of range ( -> very basic implementation )
for (var i = 0; i < World.markerList.length; i++) {
if (World.markerList[i].distanceToUser > maxRangeMeters) {
return i;
}
};
// in case no placemark is out of range -> all are visible
return World.markerList.length;
},
レーダーの位置を更新するには、別のCSSスタイル(たとえば、jQueryのremoveClass
とaddClass
を使用)を使用してPoiRadar.updatePosition();
を呼び出します。このサンプルでは、パネルが開いたときにレーダーを右に移動しています。詳しくはlimitingrange.jsを参照してください。
handlePanelMovements: function handlePanelMovementsFn() {
$("#panel-distance").on("panelclose", function(event, ui) {
$("#radarContainer").addClass("radarContainer_left");
$("#radarContainer").removeClass("radarContainer_right");
PoiRadar.updatePosition();
});
$("#panel-distance").on("panelopen", function(event, ui) {
$("#radarContainer").removeClass("radarContainer_left");
$("#radarContainer").addClass("radarContainer_right");
PoiRadar.updatePosition();
});
},
ユーザーが[Range]
ボタンをクリックすると、World.showRange
関数が実行されます。
// display range slider
showRange: function showRangeFn() {
if (World.markerList.length > 0) {
// update labels on every range movement
$('#panel-distance-range').change(function() {
World.updateRangeValues();
});
World.updateRangeValues();
World.handlePanelMovements();
// open panel
$("#panel-distance").trigger("updatelayout");
$("#panel-distance").panel("open", 1234);
} else {
// no places are visible, because the are not loaded yet
World.updateStatusMessage('No places available yet', true);
}
}
マーカーのリロード
対応するサンプルタイトル名「Reloading Content」
ユーザーの移動やその他のなにかしらの理由でPOIデータをリロードしなければならないことがあります。このサンプルでは、ユーザーがrefreshボタンをクリックときにPOIデータがリロードされます。ボタンはindex.htmlで定義されており、クリック時にWorld.reloadPlaces()
を呼び出します。
<a href="javascript: World.reloadPlaces()" data-icon="refresh" >Reload</a>
World.reloadPlaces()
の実装はARchitect Worldの一部です(reloadingcontent.js)。この関数はWorld.requestDataFromServer
を実行して、ユーザーの現在の位置に従ってWebサービスからデータを取得します。
メモ: 状況によっては、Webサービスが使用できなかったり、その他の接続の問題が発生したりする可能性があります。接続の問題をユーザーに知らせるため、ステータスメッセージを更新しています。実際のアプリケーションでは、ポップアップのようなものを使用してもかまいません。
var World = {
[…]
// reload places from content source
reloadPlaces: function reloadPlacesFn() {
if (!World.isRequestingData) {
if (World.userLocation) {
World.requestDataFromServer(World.userLocation.latitude,
World.userLocation.longitude);
} else {
World.updateStatusMessage('Unknown user-location.', true);
}
} else {
World.updateStatusMessage('Already requesting places...', true);
}
}
[…]
}
POIデータの詳細情報をアクティビティに表示
対応するサンプルタイトル名「Reloading Content」
POIデータの詳細情報をネイティブのアクティビティに表示したい場合があります。このサンプルでは、ユーザーがHTMLの[More]ボタンを押したときにシンプルなアクティビティが開きます。このサンプルではJavaScriptとネイティブコード間でやり取りする方法を示します。
[More]ボタンはindex.htmlで定義されており、クリック時にWorld.onPoiDetailMoreButtonClicked
関数を呼び出します。
<!-- more button-->
<a href="javascript: World.onPoiDetailMoreButtonClicked();"
data-role="button" data-icon="arrow-r" data-iconpos="right" data-inline="true">
More
</a>
World.onPoiDetailMoreButtonClicked
はnativedetailscreen.jsで実装されており、AR.platform.sendJSONObject(...)
を実行します。ネイティブプロジェクトがこの呼び出しをインターセプトすると、次のようになります。
var World = {
[…]
// user clicked "More" button in POI-detail panel -> fire event to open native screen
onPoiDetailMoreButtonClicked: function onPoiDetailMoreButtonClickedFn() {
var currentMarker = World.currentMarker;
var markerSelectedJSON = {
name: "markerselected",
id: currentMarker.poiData.id,
title: currentMarker.poiData.title,
description: currentMarker.poiData.description
};
AR.platform.sendJSONObject(markerSelectedJSON);
}
[…]
}
カスタムURLスキームのネイティブ部分についてはここのセクションで説明します。
サンプルは、AR.platform.sendJSONObject
のコールバックメソッドを使用して、JSとObjCの間で通信行います。 AR.platform.sendJSONObjectはいつでも呼び出すことができますが、オプションのWTArchitectViewDelegateメソッド-architectView:receivedJSONObject:
を実装して、ObjCで対応する呼び出しを取得する必要があります。 WTArchitectViewデリゲートを設定するには、delegate
オブジェクトをWTArchitectViewDelegate
プロトコルに準拠させ、前述の省略可能なオプションのメソッドを実装します。 WTArchitectView
オブジェクトにdelegate
を設定すると、すべてのカスタムURL呼び出しが受信されます。 デリゲートメソッドが呼び出されるとき、次のパラメータが与えられます。
- 最初のパラメーターは、カスタムURLスキームが呼び出された、JSコンテキストの
WTArchitectView
インスタンスです。 - 2番目のパラメーターは、完全なURLを表す
NSURL
オブジェクトです。
クラス定義のコードを以下に示します。
@interface WTPresentingPoiDetailsARViewController () <WTArchitectViewDelegate>
@end
クラスをプロトコルに準拠させたら、WTArchitectView
インスタンスを作成し、自身をWTArchitectView
のデリゲートに設定します。
self.architectView.delegate = self;
最後に、オプションのWTArchitectViewDelegate
メソッドである-architectView:invokedURL:
に必要な処理を実装します。以下のコードは、iOSサンプルアプリケーションの実装を示します(WTPresentingPoiDetailsARViewController.m
)。
- (void)architectView:(WTArchitectView *)architectView receivedJSONObject:(NSDictionary *)jsonObject
{
if ( jsonObject ) {
if ( [[jsonObject objectForKey:@"name"] isEqualToString:@"markerselected"] )
{
NSUInteger poiId = [[jsonObject objectForKey:@"id"] integerValue];
WTPoi *poi = [self.poiManager poiForId:poiId];
if (poi) {
WTPoiDetailViewController *poiDetailViewController = [[WTPoiDetailViewController alloc] initWithNibName:@"WTPoiDetailViewController" bundle:nil];
poiDetailViewController.poi = poi;
[self.navigationController pushViewController:poiDetailViewController animated:YES];
}
}
}
}
スクリーンショットの取得
対応するサンプルタイトル名「Bonus: Capture Screen」
このサンプルは、captureScreen
関数を使用してスクリーンショットを共有する方法を示します。JavaScriptとネイティブコード間のやり取りの概念は前のパートのサンプルと同じですが、今回はurlListener
で写真の共有を処理します。[Snapshot]ボタンはタイトルバーの右上にあります。このボタンをクリックすると現在の画面がキャプチャされ、その画面を共有するかどうかを確認するメッセージが表示されます。
<!-- header of UI holding feature buttons -->
<div id ="header-status" data-role="header" data-position="fixed" data-theme="c">
<a href="javascript: World.showRange();" data-icon="gear" data-inline="true" data-mini="true">Range</a>
<a href="javascript: World.captureScreen()" data-icon="refresh" >Snapshot</a>
<h1></h1>
</div>
写真共有の処理はネイティブコードで行います。
// tell native (urlListener) that user pressed 'Snapshot' button
captureScreen: function captureScreenFn() {
AR.platform.sendJSONObject({
name: "button",
action: "captureScreen"
});
},