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

AIRアプリからグーグルルート案内を呼び出す方法

AIR 地図 Android Flex4

Android携帯ではGPS取得ができるのでせっかくなので現在位置からお店へのナビをするアプリを作ってみました。

メイド萌えナビ


GPSを使って現在位置を取得し、サーバーへ現在位置付近にあるお店を取得して、グーグルルート案内を使ってナビをします。
メイドや萌などで検索すればサンプルアプリが表示されるかと思います。
以下簡単な説明です。

AIRでの現在位置の取得方法

まずは基本となる緯度経度情報の取得です。

if (Geolocation.isSupported)
{
    g = new Geolocation();
    if (g.muted)
    {
        //GPS機能は有効だが、ミュートされている場合
        return;
    }
    g.setRequestedUpdateInterval(1000);
    g.addEventListener(GeolocationEvent.UPDATE, onUpdate);
}

次に緯度経度情報の表示をします。

//GPSデータ取得完了イベント
protected function onUpdate(event:GeolocationEvent):void
{
    trace( event.latitude );
    trace( event.longitude );
    g.removeEventListener(GeolocationEvent.UPDATE , onUpdate );
}

Googleルート案内の呼び出し方法

スタート地点とゴール地点を設定するだけでOKです。あとはグーグルマップが面倒をみてくれます。

navigateToURL( new URLRequest( "http://maps.google.co.jp/maps?f=d&source=s_d&saddr="
    + homeView.lat + "," + homeView.lng
    + "&daddr="
    + data.lat + "," + data.lng
) );
saddr スタート地点の緯度経度
daddr ゴール地点の緯度経度

を指定することで簡単に呼び出すことができます。
#個人的にはnavigateToUrlでインテント方式で直接アプリを呼び出せるといいのですが(^^;

マップを画像として表示

AIRGoogle Map for Flashを入れると重いので、画像として配置しておくと便利です。

<!--マップ画像-->
<s:Image  width="440" height="350"
          source="{'http://maps.google.com/maps/api/staticmap?center='
          + data.lat + ',' + data.lng
          + '&amp;zoom=18&amp;size=440x350&amp;sensor=false&amp;markers='
          + data.lat + ',' + data.lng }"
/>

mainHome.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        title="現在置から萌店ナビ"
        destructionPolicy="none"
        creationComplete="view1_creationCompleteHandler(event)"
        >
    <fx:Script>
        <![CDATA[
            import com.adobe.serialization.json.JSON;
            import flash.sensors.Geolocation;
            import mx.events.FlexEvent;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.InvokeEvent;
            import mx.rpc.events.ResultEvent;
            import spark.events.IndexChangeEvent;
            //子画面からもアクセスできるような緯度経度情報
            public static var lat:Number = 35.698414;
            public static var lng:Number = 139.773055;
            protected var geoLocation:Geolocation;
            protected var g:Geolocation = new Geolocation();
            //初期化関数
            protected function view1_creationCompleteHandler(event:FlexEvent):void
            {
                loadingGps.visible = true;
                if (Geolocation.isSupported)
                {
                    g = new Geolocation();
                    if (g.muted)
                    {
                        //GPS機能は有効だが、ミュートされている場合
                        return;
                    }
                    g.setRequestedUpdateInterval(1000);
                    g.addEventListener(GeolocationEvent.UPDATE, onUpdate);
                }
                else
                {
                    //GPSがサポートされてないけどとりあえずダミーで進める(デバッグ用)
                    getLatLngData();
                }
            }
            //GPSデータ取得完了イベント
            protected function onUpdate(event:GeolocationEvent):void
            {
                lat = event.latitude;
                lng = event.longitude;
                g.removeEventListener(GeolocationEvent.UPDATE , onUpdate );
                getLatLngData();
            }
            //GPSデータから近くにあるお店を検索する
            protected function getLatLngData():void
            {
                loadingGps.visible = false;
                loadingShop.visible = true;
                hts.send(
                    {
                        "lat"  : lat ,
                        "lng"  : lng
                    }
                );
            }
            //サーバーからデータ取得完了
            protected function hts_resultHandler(event:ResultEvent):void
            {
                mySound.play();
                loadingShop.visible = false;
                //JSONから配列へ変換
                var obj:Object = JSON.decode(event.result.toString());
                myArr.source = obj as Array;
            }
            protected function hts_faultHandler(event:FaultEvent):void
            {
            }
            protected function hts_invokeHandler(event:mx.rpc.events.InvokeEvent):void
            {
            }
            //リストからお店が選択されたイベント
            protected function list_changeHandler(event:IndexChangeEvent):void
            {
                //次の画面へ変数を渡して移動
                navigator.pushView(shopView, list.selectedItem);
            }
            //リストのラベル表示部分の整理関数
            protected function setLabel(item:Object):String
            {
                return "(" + int( item.distance ) + " m) " + item.todouhuken + ' '+ String( item.category ).replace('amp;' , '' );
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!--HTS完了サウンド-->
        <mx:SoundEffect id="mySound" target="{this}" source="@Embed('assets/OMT001_01S030.mp3')" useDuration="false"/>
        <!--リスト表示用配列-->
        <s:ArrayCollection id="myArr"/>
        <!--HTTPサービス-->
        <s:HTTPService id="hts"  url="http://moeten.info/maidcafe/json.php?m=shop"
                       result="hts_resultHandler(event)" fault="hts_faultHandler(event)" invoke="hts_invokeHandler(event)"
                       resultFormat="text"
                       />
    </fx:Declarations>
    <!--お店リスト表示-->
    <s:List id="list" width="100%" height="100%"
            change="list_changeHandler(event)"
            dataProvider="{myArr}" labelFunction="{setLabel}"
            >
        <s:itemRenderer>
            <fx:Component>
                <s:MobileIconItemRenderer
                    creationCompleteEffect="Fade"
                    selectionColor="0x333333"
                    iconField="icon"
                    iconWidth="100"
                    iconHeight="100"
                    height="120"
                    messageField="name"
                    fontSize="16" fontWeight="normal"
                    />
            </fx:Component>
        </s:itemRenderer>
    </s:List>
    <!--GPSローディング画像-->
    <s:Group id="loadingGps" horizontalCenter="0" verticalCenter="0" visible="false" showEffect="Fade" hideEffect="Fade">
        <s:Rect width="400" height="102" radiusX="10" radiusY="10">
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.8"/>
            </s:fill>
        </s:Rect>
        <s:Label text="現在位置を測定しています" horizontalCenter="0" verticalCenter="0"/>
    </s:Group>
    <!--お店ローディング画像-->
    <s:Group id="loadingShop" horizontalCenter="0" verticalCenter="0" visible="false" showEffect="Fade" hideEffect="Fade">
        <s:Rect width="400" height="102" radiusX="10" radiusY="10">
            <s:fill>
                <s:SolidColor color="0xffffff" alpha="0.8"/>
            </s:fill>
        </s:Rect>
        <s:Label text="お店をを探しています" horizontalCenter="0" verticalCenter="0" color="0x000000"/>
    </s:Group>
</s:View>

shopView.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        title="{String(data.name).replace('amp;','')}"
        creationComplete="view1_creationCompleteHandler(event)"
        destructionPolicy="none" clipAndEnableScrolling="true"
        >
    <fx:Script>
        <![CDATA[
            import com.adobe.serialization.json.JSON;
            import flash.net.navigateToURL;
            import mx.events.FlexEvent;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.InvokeEvent;
            import mx.rpc.events.ResultEvent;
            import spark.effects.easing.Linear;
            import spark.events.IndexChangeEvent;
            //完了イベント
            protected function view1_creationCompleteHandler(event:FlexEvent):void
            {
                if( myTitle.width >= 460 ){
                    myMove.play();
                }
            }
            protected function thumbnail_clickHandler(event:MouseEvent):void
            {
                myFadeHide.play([myMain]);
                bigImage.visible = true;
                bigImage.source = event.currentTarget.source;            }
            //リストのラベル整理
            protected function setLabel(item:Object):String
            {
                return String( item.name ).replace('amp;' , '' );
            }
            //Googleマップ
            protected function mapBtn_clickHandler(event:MouseEvent):void
            {
                navigateToURL(new URLRequest(
                    "http://maps.google.co.jp/maps?z=18&ll="
                    + data.todouhuken
                    + String( data.address ).split( ' ' )[0]
                ));
                /*
                navigateToURL(new URLRequest( "http://maps.google.co.jp/maps?"
                    + "&q="  + data.todouhuken + data.address
                    + "&li=" + data.lat + ","  + data.lng
                    + "&ll=" + data.lat + ","  + data.lng
                    + "&z=18&iwloc=A"
                )
                );
                */
                /*
                navigateToURL(new URLRequest( "http://maps.google.co.jp/maps?"
                    + "li=" + data.lat + "," + data.lng
                    + "sll=" + data.lat + "," + data.lng
                    + "q="  + data.lat + "," + data.lng
                    + "&amp;z=18&amp;iwloc=A"
                    ) );
                */
            }
            //公式サイトへGO!
            protected function urlBtn_clickHandler(event:MouseEvent):void
            {
                navigateToURL(new URLRequest( data.url ) );
            }
            //ナビ
            protected function naviBtn_clickHandler(event:MouseEvent):void
            {
                navigateToURL( new URLRequest( "http://maps.google.co.jp/maps?f=d&source=s_d&saddr="
                    + homeView.lat + "," + homeView.lng
                    + "&daddr="
                    + data.lat + "," + data.lng
                ) );
            }
            //大きい画像がクリックされた時
            protected function bigImage_clickHandler(event:MouseEvent):void
            {
                bigImage.visible=false;
                myFadeShow.play([myMain]);
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!---->
        <s:Fade id="myFadeShow" alphaFrom="0.4" alphaTo="1"/>
        <s:Fade id="myFadeHide" alphaFrom="1" alphaTo="0.4"/>
        <s:Parallel id="myMove" repeatCount="0" target="{myTitle}">
            <s:Move xFrom="460" xTo="{-myTitle.width}" easer="{new Linear()}" duration="20000" />
        </s:Parallel>
    </fx:Declarations>
    <!--
    <s:Group width="100%" height="100%">
    <s:Rect width="100%" height="100%">
        <s:fill >
            <s:LinearGradient rotation="90" >
                <s:GradientEntry color="0x999999" ratio="0"/>
                <s:GradientEntry color="0x333333" ratio="1"/>
            </s:LinearGradient>
        </s:fill>
    </s:Rect>
    </s:Group>
    -->
    <s:Scroller width="100%" height="100%">
        <s:Group id="myMain" width="100%" height="100%" clipAndEnableScrolling="true">
            <s:layout>
                <s:VerticalLayout gap="18" horizontalAlign="center" clipAndEnableScrolling="true"
                                  paddingTop="20" paddingBottom="20" paddingLeft="20" paddingRight="20" />
            </s:layout>
            <!--お店名-->
            <s:Group width="100%" clipAndEnableScrolling="true">
                <s:Rect width="100%" height="36" radiusX="10" radiusY="10">
                    <s:fill >
                        <s:SolidColor color="0x333333"/>
                    </s:fill>
                    <s:stroke>
                        <s:SolidColorStroke color="0x000000"/>
                    </s:stroke>
                </s:Rect>
                <s:Label id="myTitle" text="{data.name}" verticalCenter="0" horizontalCenter="0"/>
            </s:Group>
            <!--お店画像-->
            <s:VGroup width="95%">
                <s:Group clipAndEnableScrolling="true" width="100%" height="90">
                    <s:Image source="{data.image1}" click="thumbnail_clickHandler(event)"
                             verticalCenter="0" horizontalCenter="0"/>
                </s:Group>
                <s:Group clipAndEnableScrolling="true" width="100%" height="90">
                    <s:Image source="{data.image2}" click="thumbnail_clickHandler(event)"
                             verticalCenter="0" horizontalCenter="0"/>
                </s:Group>
                <s:Group clipAndEnableScrolling="true" width="100%" height="90">
                    <s:Image source="{data.image3}" click="thumbnail_clickHandler(event)"
                             verticalCenter="0" horizontalCenter="0"/>
                </s:Group>
            </s:VGroup>
            <!--お店説明-->
            <s:Group width="100%">
                <s:Rect width="100%" height="36" radiusX="10" radiusY="10">
                    <s:fill >
                        <s:SolidColor color="0x333333"/>
                    </s:fill>
                    <s:stroke>
                        <s:SolidColorStroke color="0x000000"/>
                    </s:stroke>
                </s:Rect>
                <s:Label text="お店説明" color="0xffffff" horizontalCenter="0" verticalCenter="0"/>
            </s:Group>
            <s:Label text="{data.info}"  width="95%" />
            <!--マップ画像-->
            <s:Image  width="440" height="350"
                      source="{'http://maps.google.com/maps/api/staticmap?center='+data.lat + ',' + data.lng
                      + '&amp;zoom=18&amp;size=440x350&amp;sensor=false&amp;markers='
                      + data.lat + ',' + data.lng }"
                      />
            <s:Label text="{data.todouhuken + data.address}" fontSize="14"/>
            <s:Label text="TEL:{data.tel}" fontSize="14"/>
            <!--ボタン-->
            <s:Button id="mapBtn" icon="@Embed('assets/Google.png')" width="70%" height="60" click="mapBtn_clickHandler(event)" label="Google 地図"/>
            <s:Button id="naviBtn" icon="@Embed('assets/Wireless.png')" width="70%" height="60" click="naviBtn_clickHandler(event)" label="現在地からナビ"/>
            <s:Button id="urlBtn"  icon="@Embed('assets/Document.png')" width="70%" height="60" click="urlBtn_clickHandler(event)"  label="公式サイトを見る"/>
            <!--コピーライト-->
            <s:Label text="- moeten.info -" horizontalCenter="0" color="0x888888" fontSize="14"/>
        </s:Group>
    </s:Scroller>
    <!--画像大プレビュー-->
    <s:Image id="bigImage" visible="false" showEffect="Fade" hideEffect="Fade" click="bigImage_clickHandler(event)"
             horizontalCenter="0" verticalCenter="0" width="90%" height="90%">
        <s:filters>
            <s:GlowFilter blurX="8" blurY="8" color="0xffffff" strength="10"/>
            <s:DropShadowFilter blurX="12" blurY="12" color="0x000000" distance="0" angle="0"/>
        </s:filters>
    </s:Image>
</s:View>

参考リンク

Google Map for Flash を使ってみたのですが、呼び出しまでがあまりにも長く実用的ではないのでとりあえずは今日の記事の方法が実用的かと思います。
モバイルはGPSが簡単に使えるので便利でいいですね〜。