うら干物書き

ゲームを作りたい!

【Unity】WebAPIを呼んで天気予報を取得してみよう

 ……やれやれビショビショだ。
 出かける時は雨が降っていなかったのに、帰りには土砂降りだよ。
 傘を持っていけばよかったなぁ……。
 天気予報はちゃんと見ておくべきだったね。

 というわけで今回はWeb上で提供されているAPIを呼んで、天気予報を取得することにチャレンジしてみますよ。
 ちなみにただ単に、天気予報が知りたいだけの人は、テレビやネットニュースを見ればいいと思うな。


この記事にはUnity2017.4.1f1を使用しています。

天気予報情報を取得したい

 天気予報情報はどこから取得できるのだろうか。
 いろいろ調べてみたけど「お天気Webサービス」がWebAPIを提供してくれているようだ。
 これはありがたい。

weather.livedoor.com

 早速使ってみよう。
 ……と言いたいところだけど、このWebAPIを呼ぶには各都市のIDを知らなくてはいけないようだ。
 な、なんなんだ、都市のIDって。
 とにかく都市のIDを取得してみようか。

都市情報を取得する

 お天気Webサービスでは都市情報が定義されているようだ。
 都市のIDはこの都市情報の中に含まれているようだね。

1次細分区定義表 - livedoor 天気情報

 この情報をスクリプト上で取得する必要がありそうだ。
 インターネット上の情報の取得処理にはUnityWebRequestを使って作成してみよう。

docs.unity3d.com
docs.unity3d.com

WeatherController.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class WeatherController : MonoBehaviour
{
    private IEnumerator CallWebRequest( string i_requestURL, System.Action<string> i_onFinished )
    {
        if( i_onFinished == null )
        {
            Debug.LogErrorFormat( "コールバックが指定されてないぜ。これじゃ結果がわからないぜ。" );
            yield break;
        }

        if( string.IsNullOrEmpty( i_requestURL ) )
        {
            Debug.LogErrorFormat( "URLぐらい指定してくれよ……。" );
            i_onFinished( null );
            yield break;
        }

        var request = UnityEngine.Networking.UnityWebRequest.Get( i_requestURL );
        yield return request.SendWebRequest();

        // ドキュメントなどでは"isError"を使って判定しているようだけど、
        // "isNetworkError"に名前が変わったようなので、こっちを使ってくれよな!
        if( request.isHttpError || request.isNetworkError )
        {
            Debug.LogErrorFormat( "残念ながらエラーが発生しちまったらしい。詳細を添付しておく。{0}", request.error );
            i_onFinished( null );
            yield break;
        }

        i_onFinished( request.downloadHandler.text );
    }

} // class WeatherController

 こんな感じだろうか。
 ではこの関数を使用して、都市情報を取得してみよう。
 ちなみに都市情報はXML形式で取得できるんだ。
 XML形式の扱いについては、以前調査したのでその情報を元にデシリアライズしようっと。

www.urablog.xyz

CityData.cs(デシリアライズ先のクラス情報)

public class CityData
{
    public CityData( string i_id, string i_name )
    {
        ID      = i_id;
        Name    = i_name;
    }

    public string ID
    {
        get;
        private set;
    }

    public string Name
    {
        get;
        private set;
    }
}

WeatherController.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class WeatherController : MonoBehaviour
{
    private static readonly string DEFINITION_TABLE_URL = "http://weather.livedoor.com/forecast/rss/primary_area.xml";
    private static readonly System.Xml.Linq.XName CITY_TITLE_ATTRIBUTE  = System.Xml.Linq.XName.Get( "title" );
    private static readonly System.Xml.Linq.XName CITY_ID_ATTRIBUTE     = System.Xml.Linq.XName.Get( "id" );

    private CityData[] m_cities = null;

    private void Start()
    {
        StartCoroutine( Initialize() );
    }


    #region Initialize

    private IEnumerator Initialize()
    {
        CityData[] cities = null;
        {
            System.Action<CityData[]> onFinished = ( i_cities ) =>
            {
                cities = i_cities;
            };
            yield return GetCityTableProcess( onFinished );
        }

        if( cities == null || cities.Length == 0 )
        {
            Debug.LogErrorFormat( "都市情報取ってこれんかったわ。フォーマットでも変わったのかなぁ。" );
            yield break;
        }

        m_cities = cities;
    }

    private IEnumerator GetCityTableProcess( System.Action<CityData[]> i_onFinished )
    {
        if( i_onFinished == null )
        {
            Debug.LogErrorFormat( "コールバックが指定されてないぜ。これじゃ結果がわからないぜ。" );
            yield break;
        }

        string xmlText = null;
        {
            System.Action<string> onFinished = ( i_text ) =>
            {
                xmlText = i_text;
            };
            yield return CallWebRequest( DEFINITION_TABLE_URL, onFinished );
        }

        if( string.IsNullOrEmpty( xmlText ) )
        {
            Debug.LogErrorFormat( "1次細分区定義表の取得に失敗だ!" );
            i_onFinished( null );
            yield break;
        }

        Debug.LogFormat( "1次細分区定義表(XML):\n{0}", xmlText );
        CityData[] cities = ParseCityList( xmlText );

        i_onFinished( cities );
    }

    private CityData[] ParseCityList( string i_xmlText )
    {

        var cities = new List<CityData>();

        try
        {
            System.Xml.Linq.XDocument xml = System.Xml.Linq.XDocument.Parse( i_xmlText );
            System.Xml.Linq.XElement root = xml.Root;

            // cityタグに都市情報があるはずだ!
            // フォーマットが変わったら、この方法じゃ無理になるかもしれないかもね。
            var cityElements    = root.Descendants( "city" );
            if( cityElements != null )
            {
                foreach( var element in cityElements )
                {
                    var titleAttribute  = element.Attribute( CITY_TITLE_ATTRIBUTE );
                    var idAttribute     = element.Attribute( CITY_ID_ATTRIBUTE );
                    if( titleAttribute == null || idAttribute == null )
                    {
                        continue;
                    }

                    cities.Add( new CityData( idAttribute.Value, titleAttribute.Value ) );
                }
            }
        }
        catch( System.Exception i_exception )
        {
            Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception );
        }

        return cities.ToArray();
    }

    #endregion // Initialize

} // class WeatherController

f:id:urahimono:20180430160442p:plain

 ちゃんと取得できたみたいだぞ!
 ただログに表示するだけでは味気ないので、UIとして画面に表示しようかな。
 UGUIのドロップダウンメニューを使おう。

@code03
f:id:urahimono:20180430160453p:plain

 うんうん、これで知りたい天気予報の都市を指定できるようになったぞ!

天気予報情報を取得する

 都市のIDの取得に成功した。
 これで天気予報のWebAPIが呼べるようになったぞ。
 さっそく呼んでみよう。

 WebAPIを呼ぶのには、先ほど作ったCallWebRequest()を使おう。
 ちなみに天気予報の情報はJson**形式で取得できるようだ。
 それならばJsonUtility.FromJson()を使ってパパっとデシリアライズしてしまおう。

docs.unity3d.com

デシリアライズ先のクラス情報。
WeatherData.cs

using UnityEngine;

[System.Serializable]
public class WeatherData
{
    [SerializeField]
    public ForecastData[] forecasts = null;


    [System.Serializable]
    public class ForecastData
    {
        [SerializeField]
        public string telop = null;
        [SerializeField]
        public string date  = null;
        [SerializeField]
        public TemperatureData temperature = null;
    }

    [System.Serializable]
    public class TemperatureData
    {
        [SerializeField]
        public TemperatureDetailData min = null;
        [SerializeField]
        public TemperatureDetailData max = null;
    }

    [System.Serializable]
    public class TemperatureDetailData
    {
        [SerializeField]
        public string celsius = null;
    }
}

WeatherController.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class WeatherController : MonoBehaviour
{
    private static readonly string API_URL              = "http://weather.livedoor.com/forecast/webservice/json/v1";
    private static readonly string API_CITY_PARAMETER   = "city";


    #region GetWeather

    public void OnGetWeather()
    {
        // UI上で取得ボタンを押したら呼ばれるぜ!

        if( m_cities == null || m_cities.Length == 0 )
        {
            Debug.LogErrorFormat( "都市情報を取得できてないから、天気取得なんて無理無理!" );
            return;
        }

        if( m_dropdownUI == null ||
            !m_dropdownUI.gameObject.activeInHierarchy )
        {
            Debug.LogErrorFormat( "都市のUI表示されてなくね?" );
            return;
        }

        string selectedCityName = m_dropdownUI.captionText.text;
        if( string.IsNullOrEmpty( selectedCityName ) )
        {
            Debug.LogErrorFormat( "どこの都市を選択したか、わかんないんだけど……。" );
            return;
        }

        CityData selectedCity = m_cities.FirstOrDefault( value => value.Name == selectedCityName );
        if( selectedCity == null )
        {
            Debug.LogErrorFormat( "知らない都市なんだが……。この都市知ってる? {0}", selectedCityName );
            return;
        }

        StartCoroutine( GetWeatherProcess( selectedCity.ID ) );
    }

    private IEnumerator GetWeatherProcess( string i_cityID )
    {
        if( string.IsNullOrEmpty( i_cityID ) )
        {
            Debug.LogErrorFormat( "ちゃんと都市IDを入れてくれよ!" );
            yield break;
        }

        string requestURL   = string.Format( "{0}?{1}={2}", API_URL, API_CITY_PARAMETER, i_cityID );
        string jsonText     = null;
        {
            System.Action<string> onFinished = ( i_text ) =>
            {
                jsonText    = i_text;
            };
            yield return CallWebRequest( requestURL, onFinished );
        }

        if( string.IsNullOrEmpty( jsonText ) )
        {
            Debug.LogErrorFormat( "天気情報の取得に失敗だ!" );
            yield break;
        }


        Debug.LogFormat( "天気情報(Json):\n{0}", jsonText );

        var weatherData = JsonUtility.FromJson<WeatherData>( jsonText );
    }

    #endregion // GetWeather

} // class WeatherController

f:id:urahimono:20180430160504p:plain

 データ取得に成功だ!
 あとはこの情報を画面に表示してしまおう。
 まずはUIモデル用のコンポーネントを作成してっと。

WeatherUI.cs

using UnityEngine;
using UnityEngine.UI;

public class WeatherUI : MonoBehaviour
{
    [SerializeField]
    private Text m_date = null;
    [SerializeField]
    private Text m_weather = null;
    [SerializeField]
    private Text m_temperatureMin = null;
    [SerializeField]
    private Text m_temperatureMax = null;

    public string Data
    {
        set
        {
            if( m_date != null )
            {
                m_date.text = value;
            }
        }
    }

    public string Weather
    {
        set
        {
            if( m_weather != null )
            {
                m_weather.text = value;
            }
        }
    }

    public string TemperatureMin
    {
        set
        {
            if( m_temperatureMin != null )
            {
                if( string.IsNullOrEmpty( value ) )
                {
                    m_temperatureMin.text = string.Format( "最低温度:---" );
                    return;
                }
                m_temperatureMin.text = string.Format( "最低温度:{0}℃", value );
            }
        }
    }

    public string TemperatureMax
    {
        set
        {
            if( m_temperatureMax != null )
            {
                if( string.IsNullOrEmpty( value ) )
                {
                    m_temperatureMax.text = string.Format( "最高温度:---" );
                    return;
                }
                m_temperatureMax.text = string.Format( "最高温度:{0}℃", value );
            }
        }
    }


} // class WeatherUI

 あとはこのコンポーネントに、先ほど取得した天気予報の情報を設定すれば完成だ。

WeatherController.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class WeatherController : MonoBehaviour
{

    [SerializeField]
    private WeatherUI[] m_weatherUIs = null;

    private IEnumerator GetWeatherProcess( string i_cityID )
    {
        if( string.IsNullOrEmpty( i_cityID ) )
        {
            Debug.LogErrorFormat( "ちゃんと都市IDを入れてくれよ!" );
            yield break;
        }

        string requestURL   = string.Format( "{0}?{1}={2}", API_URL, API_CITY_PARAMETER, i_cityID );
        string jsonText     = null;
        {
            System.Action<string> onFinished = ( i_text ) =>
            {
                jsonText    = i_text;
            };
            yield return CallWebRequest( requestURL, onFinished );
        }

        if( string.IsNullOrEmpty( jsonText ) )
        {
            Debug.LogErrorFormat( "天気情報の取得に失敗だ!" );
            yield break;
        }


        Debug.LogFormat( "天気情報(Json):\n{0}", jsonText );

        var weatherData = JsonUtility.FromJson<WeatherData>( jsonText );
        SetWeatherUI( weatherData );
    }

    private void SetWeatherUI( WeatherData i_weatherData )
    {
        if( i_weatherData == null )
        {
            Debug.LogErrorFormat( "天気情報が空のようだぜ!" );
            return;
        }

        var forecasts = i_weatherData.forecasts;
        if( forecasts == null || forecasts.Length == 0 )
        {
            Debug.LogErrorFormat( "天気情報が空のようだぜ!" );
            return;
        }

        if( m_weatherUIs == null || m_weatherUIs.Length == 0 )
        {
            Debug.LogErrorFormat( "天気情報を表示するUIの準備が出来ていないようだね" );
            return;
        }

        // 日付毎のUIの数より天気情報の日付数の方が多い場合は、UI分だけ表示。
        // 逆に日付毎のUIの数の方が多い場合は、天気情報分だけ表示。
        for( int i = 0; i < m_weatherUIs.Length; ++i )
        {
            var ui = m_weatherUIs[ i ];
            if( ui == null )
            {
                continue;
            }

            if( forecasts.Length <= i )
            {
                ui.gameObject.SetActive( false );
                continue;
            }

            ui.gameObject.SetActive( true );
            var forecast = forecasts[ i ];

            // 温度は入っていないこともあるみたいなので、一応チェックしておこう。
            ui.Data             = forecast.date;
            ui.Weather          = forecast.telop;
            ui.TemperatureMin   = forecast.temperature.min != null ? forecast.temperature.min.celsius : null;
            ui.TemperatureMax   = forecast.temperature.max != null ? forecast.temperature.max.celsius : null;
        }

    }
} // class WeatherController

f:id:urahimono:20180430160520p:plain

 やった。これで天気予報が取得できた!
 これで雨に濡れる心配もなさそうだ。

 というわけで記事もできたことだし、散歩がてらに出かけようか。
 先ほどの天気予報では、名古屋は晴れのようだけど、大阪はどうか見てなかったな。
 雲行きはそんなに良くなさそうだけど。
 ……ま、いっか。
 今はまだ雨は降ってないし、手ぶらで出かけるか。

【Unity】WebAPIを呼んで天気予報を取得してみよう