Help us understand the problem. What is going on with this article?

Fusetools - Fuse.Maps パッケージ利用時のTips

More than 1 year has passed since last update.

この記事では、Fusetoolsというプロトタイピングツールの
公式パッケージの1つであるFuse.Mapsと、その利用時のTipsについて紹介します。
Fusetoolsの基本的な知識がある方を前提としています。

この記事を通して実現できるもの

  • Fusetoolsアプリ上での地図表示。
    • 地図上でのマーカー表示を含む。
  • 地図上へのUIパーツの配置。(UIパーツ例: 検索窓, メニュー)
    • Uberや食べログなど、地図表示を用いるアプリでほぼ避けられない表示形式。

Fuse.Maps パッケージとは

Fusetoolsが公式で提供しているパッケージの1つで、
アプリ上での地図表示を可能とするMapViewクラスを内包。
地図上でのピンの表示も当然可能で、例えばGoogle Places APIと連動させ
Google Map同様の地図をNativeアプリ上で描画することが可能。
地図の回転や、ズーム、当然、現在位置表示にも対応。

今回は、アプリ内での地図表示のための一連の処理を説明しますが、
外部の地図アプリを開くための FuseJS/Maps というモジュールも用意されています。

環境

  • Fusetools ver1.4.0 (2017/11/30時点で最新)

使い方

プロジェクトファイルの編集

Fusetoolsのプロジェクトルートに存在する、"プロジェクト名.unoproj"ファイルにて
以下の通り、利用パッケージとしてFuse.Mapsを宣言。
現在地情報が必要な場合は、Fuse.GeoLocationも宣言します。

加えて、Androidでの地図表示向けに、GoogleのApi Keyが必要となる。
以下の具合で、Android.Geo.ApiKey にてApiKeyを設定する。

プロジェクト名.unoproj
{
  ...,
  "Packages": [
    ...
    "Fuse.GeoLocation",
    "Fuse.Maps",
    ...
  ],
  ...,
  "Android": {
   "Geo": {
        "ApiKey": "ご自身のGoogle API Key"
    }
  }
}

UXファイル, JSファイルの編集

UXファイル(Fusetools独自のXML記法のHTMLのようなもの)にて、
以下のようにNativeViewHostタグを親とし、その下に各要素を配置する。

地図を表示したいページ(の抜粋).ux
<NativeViewHost>
    <DockPanel>
        <Panel Dock="Top">
            <!-- 例えば、ここで定義した検索窓は、地図上に表示される -->
        </Panel>
        <Panel Layer="Background">
            <MapView ShowCompass="true" ShowMyLocation="true" ShowMyLocationButton="true" Latitude="{latitude}" Longitude="{longitude}" Zoom="14" LocationTapped="{onMapTapped}">
                <Each Items="{markerList}">
                    <MapMarker Latitude="{latitude}" Longitude="{longitude}" Label="{name}" Tapped="{onMarkerTapped}" />
                </Each>
            </MapView>
        </Panel>
    </DockPanel>
</NativeViewHost>

以下の通り、対応するjsファイル内では、位置情報の取得処理や

地図を表示したいページ.js
var Observable = require("FuseJS/Observable");

// 位置情報の取得
var GeoLocation = require("FuseJS/GeoLocation");
var currentLocation = GeoLocation.location;
var latitude = Observable(currentLocation.latitude);
var longitude = Observable(currentLocation.longitude);

// Google Place API関連
var googlePlaceApiUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?';
var googleApiKey = 'ご自身のGoogle API Key'
var radiusMeter = 500; // place API の探索半径(メートル)

// 地図上に表示するマーカーオブジェクトを格納するリスト
var markerList = Observable();
// 選択されたマーカーを保持するオブジェクト
var selectedMarker = Observable();

function GooglePlaceMarker(google_palce_result) {
    // 一部の代表的なパラメータをセット
    this.place_id = google_palce_result['place_id'];
    this.name = google_palce_result['name'];
    this.price_level = (google_palce_result['price_level']) ? google_palce_result['price_level'] : '-';
    this.rating = (google_palce_result['rating']) ? google_palce_result['rating'] : '-';
    this.latitude = google_palce_result['geometry']['location']['lat'];
    this.longitude = google_palce_result['geometry']['location']['lng'];
    this.open_now = (google_palce_result['opening_hours']) ? google_palce_result['opening_hours']['open_now'] : null;
    this.permanently_closed = google_palce_result['permanently_closed'];
    this.vicinity = (google_palce_result['vicinity']) ? google_palce_result['vicinity'] : '';
    this.icon = google_palce_result['icon'];
    // マーカーがタップされた際のイベントハンドラー
    this.onMarkerTapped = function(self) {
        console.log(JSON.stringify(self.data));
        selectedMarker.value = self.data;
    };
};

function searchGooglePlace() {
    var tmpMarkerList = [];
    var url = googlePlaceApiUrl + 'location=' + latitude.value + ',' + longitude.value
            + '&radius=' + radiusMeter + '&types=food&key=' + googleApiKey;
    fetch(url)
    .then(function(response) {
        return new Promise(function(resolve, reject) {
            if (response.ok) {
                resolve(response.json());
            } else {
                reject(response);
            }
        });
    }).then(function(json) {
        json.results.forEach(function(result) {
            tmpMarkerList.push(new GooglePlaceMarker(result));
        });
        // Observableのリストの総入れ替えは、replaceAllでの実行が確実。
        markerList.replaceAll(tmpMarkerList);
    }).catch(function(e) {
        console.log(e.status, e.statusText, e.toString());
    });
}

function onMapTapped() {
    // マーカーの選択解除
    selectedMarker.clear();
}

(function() {
    // 地図上への初期表示
    searchGooglePlace();
})();

module.exports = {
    latitude: latitude,
    longitude: longitude,
    markerList: markerList,
    selectedMarker: selectedMarker,
    onSearchClicked: searchGooglePlace,
    onMapTapped: onMapTapped,
};

どこに落とし穴があったか

普通に開発者が地図とUIパーツを、Fusetoolsのタグ名を頼りに組もうとすると、
まず、以下のように、MapViewだけを納めたNativeViewHostを作り、
その外に、地図上に表示したい検索窓やメニュー等のパーツを実装すると思います。
しかし、これでは想定通りに動きません。
というのも、NativeViewHostは、UI上最前面に描画されるレイヤーとなるらしく、
この外でUIパーツをいくら定義しようと、ユーザが触れる/見れるところには出てこないのです。
なので、少し気持ち悪いですが、上述のサンプルコード(uxファイル)のように、
NativeViewHost内 にて、必要となるパーツを配置する必要が出てきます。

最初にやってしまいがちな地図とUIパーツの埋め込み方.ux
<Panel Layer="Overlay">
    <!-- 地図の上に表示させたいUIパーツ -->
</Panel>
<NativeViewHost>
    <MapView ShowCompass="true" ShowMyLocation="true" ShowMyLocationButton="true" Latitude="{latitude}" Longitude="{longitude}" Zoom="14" LocationTapped="{onMapTapped}">
        <Each Items="{markerList}">
            <MapMarker Latitude="{latitude}" Longitude="{longitude}" Label="{name}" Tapped="{onMarkerTapped}" />
        </Each>
    </MapView>
</NativeViewHost>

おまけ

Fusetoolsを選ぶべき理由

  • プロトタイプと本制作の垣根がもはやなくなる。
    • 簡単に、でも、こだわれば独自で綺麗なiOS/Androidアプリを一挙に制作可能。
  • 基本的な、Native機能にはアクセスできる。
    • WebRTC等は未対応だった気がします。
  • デザイナーとエンジニアの連携にも思想としてこだわっている(らしい)。
    • 最近、UIビルダーも導入され、エンジニア以外にも敷居が低下。
  • 何よりも動作が軽快。
    • Androidでも、iOS並みの挙動を期待できる。(これは結構大きい)
    • 低レイヤーから独自実装しているので、高い描画パフォーマンスを出せているらしい。
  • XMLベースの独自記法であるものの、慣れれば直感的でわかりやすい。
    • 豊富な動作サンプル。
    • プラットフォーム別(Android / iOS)の記述を必要としない(今のところ経験していない)。
  • コミュニティが活発。
    • 公式フォーラムに質問を投げかけると、1~2日でFusetoolsのエンジニアから直々に返答がある。(英語)
    • 改善要望も、同フォーラムにて容易に提案可能なため、役職問わず意見が回収・検討されやすい。
  • アップデート頻度の高さ(いい意味で)。
  • 個人で利用するには安価。
    • 料金プランは近い将来に、変更の可能性があるらしいものの。

トレンド

久々のFusetoolsに関する記事ということで、
Fusetoolsのトレンド(vs Prott, Origami Studio)を調べてみました。
上昇傾向ではあるものの、伸び悩んではいるようですね。頑張って欲しい。
ノルウェーと韓国での利用が盛んで、他の地域では拡大途上のようです。

参考資料

次回はおそらく、Fusetools上での
画像の切り出し範囲選択(クライアントからの画像投稿用)の実装サンプルを紹介します。
それでは。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした