……やれやれビショビショだ。
出かける時は雨が降っていなかったのに、帰りには土砂降りだよ。
傘を持っていけばよかったなぁ……。
天気予報はちゃんと見ておくべきだったね。
というわけで今回はWeb上で提供されているAPIを呼んで、天気予報を取得することにチャレンジしてみますよ。
ちなみにただ単に、天気予報が知りたいだけの人は、テレビやネットニュースを見ればいいと思うな。
この記事にはUnity2017.4.1f1を使用しています。
天気予報情報を取得したい
天気予報情報はどこから取得できるのだろうか。
いろいろ調べてみたけど「お天気Webサービス」がWebAPIを提供してくれているようだ。
これはありがたい。
早速使ってみよう。
……と言いたいところだけど、このWebAPIを呼ぶには各都市のIDを知らなくてはいけないようだ。
な、なんなんだ、都市のIDって。
とにかく都市のIDを取得してみようか。
都市情報を取得する
お天気Webサービスでは都市情報が定義されているようだ。
都市のIDはこの都市情報の中に含まれているようだね。
この情報をスクリプト上で取得する必要がありそうだ。
インターネット上の情報の取得処理には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形式の扱いについては、以前調査したのでその情報を元にデシリアライズしようっと。
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
ちゃんと取得できたみたいだぞ!
ただログに表示するだけでは味気ないので、UIとして画面に表示しようかな。
UGUIのドロップダウンメニューを使おう。
@code03
うんうん、これで知りたい天気予報の都市を指定できるようになったぞ!
天気予報情報を取得する
都市のIDの取得に成功した。
これで天気予報のWebAPIが呼べるようになったぞ。
さっそく呼んでみよう。
WebAPIを呼ぶのには、先ほど作ったCallWebRequest()
を使おう。
ちなみに天気予報の情報はJson**形式で取得できるようだ。
それならばJsonUtility.FromJson()
を使ってパパっとデシリアライズしてしまおう。
デシリアライズ先のクラス情報。
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
データ取得に成功だ!
あとはこの情報を画面に表示してしまおう。
まずは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
やった。これで天気予報が取得できた!
これで雨に濡れる心配もなさそうだ。
というわけで記事もできたことだし、散歩がてらに出かけようか。
先ほどの天気予報では、名古屋は晴れのようだけど、大阪はどうか見てなかったな。
雲行きはそんなに良くなさそうだけど。
……ま、いっか。
今はまだ雨は降ってないし、手ぶらで出かけるか。