2013年3月16日土曜日

Xamarin で android の GPS NMEA メッセージを取得する

Xamarin で android の GPS 位置情報を利用 に少しコードを追加して、GPS の NMEA メッセージを取得してみました。
これをテキストファイルに保存するだけで、地図ソフトなんかで読み込める GPS のログファイルになります。フォーマットを変換して google アースで軌跡を表示させたりもできます。


まず、Xamarin で AndroidManifest.xml が見当たらないんだけど、どうするの? なんかを参考に、GPS のパーミッションを設定します。(ACCESS_FINE_LOCATION を許可)

アクティビティは、文字を表示するために TextView を 3個貼り付けています。NMEA メッセージを表示する TextView はスクロールさせたかったので、ScrollView の上に貼り付けました。

以下コード


using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Locations;    // GPSを使うので追加
using System.Linq;          // クエリの処理に必要

namespace gps_nmea0316
{
    [Activity (Label = "gps_nmea0316", MainLauncher = true)]
    public class Activity1 : Activity, ILocationListener, GpsStatus.INmeaListener
    {
        private LocationManager _locationManager;
        private TextView text1;
        private TextView text2;
        private TextView text3;
        private string _locationProvider;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            text1 = FindViewById<TextView> (Resource.Id.textView1);
            text2 = FindViewById<TextView> (Resource.Id.textView2);
            text3 = FindViewById<TextView> (Resource.Id.textView3);
            InitializeLocationManager();            // GPS を使う準備(すぐ下)
        }

        // GPSを使う準備
        private void InitializeLocationManager()
        {
            _locationManager = (LocationManager) GetSystemService(LocationService);
            var criteriaForLocationService = new Criteria
            {
                Accuracy = Accuracy.Fine
                // Fine なら GPS 、Coarse なら ネットワークを使用して測位
            };

            _locationManager.AddNmeaListener (this);

            // criteriaForLocationService(すぐ上)を使いたいな とおねがい
            var acceptableLocationProviders
                = _locationManager.GetProviders(criteriaForLocationService, true);
            
            // ロケーションプロバイダが使えるかな?
            if (acceptableLocationProviders.Any())
            {
                // acceptableLocationProvidersに何かクエリが入っていたら
                // 最初のクエリを取り出す
                _locationProvider = acceptableLocationProviders.First();
            }
            else
            {
                // ロケーションプロバイダが使えないみたい
                _locationProvider = String.Empty;
                text1.Text="測位サービスが使えないみたい";
            }
        }

        protected override void OnResume()
        {
            base.OnResume();
            
            // アクティビティがレジュームされたら
            // 位置情報を更新してくれるように依頼する
            _locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);
            
            text1.Text = _locationProvider.ToUpper () + " 位置情報更新待ち";
        }
        
        protected override void OnPause()
        {
            base.OnPause();
            
            // アクティビティが停止されたら
            // 位置情報更新しないように依頼
            _locationManager.RemoveUpdates(this);
            _locationManager.RemoveNmeaListener (this);
        }

        // 位置情報が更新された時の処理
        public void OnLocationChanged(Location location)
        {
            // 何もしない
        }
        
        // ロケーションプロバイダが有効になった時の処理
        public void OnProviderEnabled(string provider) 
        {
            // 何もしない
        }
        
        // ロケーションプロバイダが無効になった時の処理
        public void OnProviderDisabled(string provider) 
        {
            // 何もしない
        }
        
        // ロケーションプロバイダのステータスが変わった時の処理
        public void OnStatusChanged(string provider, Availability status, Bundle extras)
        {
            // 表示してみる
            text1.Text=string.Format("{0}, {1}", provider, status);
        }

        // NMEA メッセージを受信した時の処理
        public void OnNmeaReceived(long time,string nmea)
        {
            // 表示する
            text1.Text=_locationProvider.ToUpper () + " 測位中";
            text2.Text = "TIME: " + time.ToString ();
            text3.Text = text3.Text.Insert (0, nmea);

            // 2000文字を超えた分を削除する
            // (メモリ節約)
            if (2000 < text3.Text.Length) {
                text3.Text.Remove(2000);
            }
        }
    }
}



実行してみた様子

位置情報が更新されなくても(有効な位置情報が無くても)時間のデータは更新されて、OnNmeaReceived イベントが発生するので、データを利用するときには注意する。OnLocationChanged イベントは有効な位置情報が得られてから発生するので、それを利用するとか、NMEA データの内容を解釈して判断するとか。

(この写真、位置データを取得する前の写真だった。ちょっと失敗。)

2013年3月15日金曜日

Xamarin で AndroidManifest.xml が見当たらないんだけど、どうするの?

Xamarin で AndroidManifest.xml が無いんだけど、パーミッションとかどうしたらいいんだろう? と思ったのですが、ソリューションの設定で AndroidManifest.xml ファイルを生成することが出来ました。

以下、その手順。

[プロジェクト] - [<ソリューション名>のオプション] を開きます。

[プロジェクトオプション] の [ビルド] - [Android Application] を選ぶと、右側に「このプロジェクトには Android Manifest が無いよ」と表示されるので [Add Android manifest] ボタンを押します。

ボタンを押すとこんな感じで AndroidManifest.xml を編集できます。

GPS を使うなら [ACCESS_FINE_LOCATION] を選んでチェックを入れるだけです。

Eclipse では、プロジェクトを作成する時に指定する OS (API) のバージョンも、ここで指定出来ます。


[OK] ボタンを押すと [Properties] フォルダの下に AndroidManifest.xml が作成されます。
AndroidManifest.xml の中身。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="test0314.test0314">
    <uses-sdk android:minSdkVersion="7" />
    <application android:label="test0314"></application>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>




パーミッションの設定は、AndroidManifest.xml を作成する以外にも [Properties] - [Assemblyinfo.cs] ファイルに下のように追記して設定することもできます。


using Android; // Manifest のため追加
[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)] // GPS アクセス許可

2013年3月14日木曜日

Xamarin で android の GPS 位置情報を利用

android で GPSの位置データの取得するテストを Xamarin で やってみました。
文字を表示するために、TextView をふたつ貼りつけたアクティビティを作っておきます。

以下コード。

まずは GPS 使用の許可を求めるために [Properties] - [Assemblyinfo.cs] を編集。
JAVA で書く時には AndroidManifest.xml でやることですね。

using System.Reflection;
using System.Runtime.CompilerServices;
using Android.App;
using Android; // Manifest のため追加

// Information about this assembly is defined by the following attributes. 
// Change them to the values specific to your project.

[assembly: AssemblyTitle("test0314")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("uts")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.

[assembly: AssemblyVersion("1.0.0")]

// The following attributes are used to specify the signing key for the assembly, 
// if desired. See the Mono documentation for more information about signing.

//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)] // GPS アクセス許可



using Android; // Manifest のため追加
[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)] // GPS アクセス許可

の 2行を追加しただけです。

次にメインのコード [MainActivity.cs] です。


using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Locations;    // GPSを使用するので追加
using System.Linq;          // クエリの処理に必要

namespace test0314
{
    [Activity (Label = "test0314", MainLauncher = true)]
    public class Activity1 : Activity, ILocationListener
    {                                           //  Activity と ILocationListener から継承
        private Location _currentLocation;
        private LocationManager _locationManager;
        private TextView text1;
        private TextView text2;
        private string _locationProvider;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            text1 = FindViewById<TextView> (Resource.Id.textView1);
            text2 = FindViewById<TextView> (Resource.Id.textView2);
            InitializeLocationManager();            // GPS を使う準備(すぐ下)
        }

        // GPSを使う準備
        private void InitializeLocationManager()
        {
            _locationManager = (LocationManager) GetSystemService(LocationService);
            var criteriaForLocationService = new Criteria
            {
                Accuracy = Accuracy.Fine
                // Fine なら GPS 、Coarse なら ネットワークを使用して測位
            };

            // criteriaForLocationService(すぐ上)を使いたいな とおねがい
            var acceptableLocationProviders
                 = _locationManager.GetProviders(criteriaForLocationService, true);

            // ロケーションプロバイダが使えるかな?
            if (acceptableLocationProviders.Any())
            {
                // acceptableLocationProvidersに何かクエリが入っていたら
                // 最初のクエリを取り出す
                _locationProvider = acceptableLocationProviders.First();
            }
            else
            {
                // ロケーションプロバイダが使えないみたい
                _locationProvider = String.Empty;
                text1.Text="測位サービスが使えないみたい";
            }
        }

        protected override void OnResume()
        {
            base.OnResume();

            // アクティビティがレジュームされたら
            // 位置情報を更新してくれるように依頼する
            _locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);

            text1.Text = _locationProvider.ToUpper () + " 位置情報更新待ち";
        }
        
        protected override void OnPause()
        {
            base.OnPause();

            // アクティビティが停止されたら
            // 位置情報更新しないように依頼
            _locationManager.RemoveUpdates(this);
        }


        // ここから Location クラスを使う時に実装しなきゃならない所

        // 位置情報が更新された時の処理
        public void OnLocationChanged(Location location)
        {
            _currentLocation = location;
            if (_currentLocation == null)
            {
                // 位置情報が空だった
                text1.Text = "位置を特定することができません";
            }
            else
            {
                // 位置情報が入っていた
                text1.Text=_locationProvider.ToUpper () + " 測位中";
                text2.Text =
                    String.Format("緯度 {0}\r\n経度 {1}\r\nProvider {2}"
                                  , _currentLocation.Latitude
                                  , _currentLocation.Longitude
                                  ,_currentLocation.Provider)
                    +String.Format("\r\nSPEED {0}\r\nTIME {1}"
                                   ,_currentLocation.Speed
                                   ,_currentLocation.Time)
                        +"\r\n(UTC 1970年1月1日からの経過時間[ミリ秒])";
            }
        }

        // ロケーションプロバイダが有効になった時の処理
        public void OnProviderEnabled(string provider) 
        {
            // 何もしない
        }
        
        // ロケーションプロバイダが無効になった時の処理
        public void OnProviderDisabled(string provider) 
        {
            // 何もしない
        }

        // ロケーションプロバイダのステータスが変わった時の処理
        public void OnStatusChanged(string provider, Availability status, Bundle extras)
        {
            // 表示してみる
            text1.Text=string.Format("{0}, {1}", provider, status);
        }
    }
}


実機で実行した様子

2013年3月13日水曜日

Xamarin で android の加速度センサーを使う



using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Hardware; // センサーを使うので追加

namespace test0313_1
{
    [Activity (Label = "test0313_1", MainLauncher = true)]
    public class Activity1 : Activity, ISensorEventListener
    {                       // Activity と ISensorEventListener から継承
        float x,y,z;

        private TextView text1;
        private SensorManager _sensorManager;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            SetContentView (Resource.Layout.Main);
            text1 = FindViewById<TextView> (Resource.Id.textView1);
            _sensorManager = (SensorManager)GetSystemService (SensorService);
        }

        protected override void OnResume()
        {
            base.OnResume();
            _sensorManager.RegisterListener(this, _sensorManager.GetDefaultSensor(SensorType.Accelerometer), SensorDelay.Ui);
        }

        protected override void OnPause()
        {
            base.OnPause();
            //_sensorManager.UnregisterListener(this);
        }

        public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
        {
            // センサーの Accuracy(精度?)が変わった時の処理
            // 何もしない
        }

        public void OnSensorChanged(SensorEvent e)
        {
            x = e.Values [0];
            y = e.Values [1];
            z = e.Values [2];

            text1.Text = "x=" + x.ToString ()
                + "\r\ny=" + y.ToString ()
                + "\r\nz=" + z.ToString ();
        }
    }
}


実機で実行した様子

Xamarin タイマーで表示を定期更新

タイマーを使ってみる単純な例として、0.5秒ごとに数を加算して表示するプログラムを作ってみます。

xamarin で使えるタイマーのクラスには何種類かあるようなのですが、System.Timers.timer クラスが簡単に使えそうだったので、これを使うことにします。
まず、文字を表示するために  TextView を一つ貼りつけたアクティビティを作成しておきます。(やり方は Xamarin でとても簡単な android アプリを作る なんかを参照)

コードはこんな感じ。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Timers;    // タイマーを使うので追加

namespace timer2
{
    [Activity (Label = "timer2", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;
        private System.Timers.Timer timer1;
        private TextView text1;
        Handler mHandler = new Handler ();

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            text1 = FindViewById<TextView> (Resource.Id.textView1);
            text1.TextSize = 64;    // さみしいので字を大きく
            
            timer1 = new System.Timers.Timer ();
            timer1.Enabled = true;
            timer1.AutoReset = true;    // 繰り返し
            timer1.Interval = 500;      // 500ms

            timer1.Elapsed += new ElapsedEventHandler (OnTimerEvent);

            timer1.Start ();    // タイマースタート

        }

        private void OnTimerEvent(object source, ElapsedEventArgs e)
        {
            count++;

            mHandler.Post (delegate {
                text1.Text = count.ToString ();
            });
        }
    }
}

けっこう簡単に書けました。

下のように書いても同じ。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Timers;    // タイマーを使うので追加

namespace timer2
{
    [Activity (Label = "timer2", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;
        private System.Timers.Timer timer1;
        private TextView text1;
        Handler mHandler = new Handler ();

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            text1 = FindViewById<textview> (Resource.Id.textView1);
            text1.TextSize = 64;    // さみしいので字を大きく
            
            timer1 = new System.Timers.Timer ();
            timer1.Enabled = true;
            timer1.AutoReset = true;    // 繰り返し
            timer1.Interval = 500;      // 500ms

            timer1.Elapsed += delegate {
                count++;
                
                mHandler.Post (delegate {
                    text1.Text = count.ToString ();
                });
            };

            timer1.Start ();    // タイマースタート
        }
    }
}



実行してみた様子。ちゃんと 0.5秒毎に数が増えて、表示されます。


ポイントは

    mHandler.Post (delegate {
        text1.Text = count.ToString ();
    });

の所。ここでタイマーイベントで起動したスレッドから、メインのスレッドに Text を更新してもらうようにお願いしています。

ここを普通に

    text1.Text = count.ToString ();

と書くと、メインのスレッドとは別のスレッドから text を更新する事になって表示が更新されません。
自分もよくわかっていないのですが、そういう事のようです(笑)。

2013年3月9日土曜日

Xamarin でとても簡単な android アプリを作る

前回の Hello, Android では、コードもアクティビティ(Windows アプリの ウィンドウのようなもの。アプリの画面。)も自動的に用意されていて、仕組みがよくわからなかったので、これらを自分で作ってみることにします。
とても簡単な例ということで、「ボタンを押したら文字を表示する」アプリを作ります。

Hello, Android の時と同じように、新規ソリューションを作成します。

まず画面(アクティビティ)を編集してみましょう。
左側のツリーから、[Resources] - [layout] - [Main.axml] をダブルクリックしてデザイナを開きます。
下の [コンテンツ] [ソース] のタブでソースの表示もできます。

デザイナの画面サイズを変更できます。
縦画面/横画面の切り替えもできます。
Android OS のバージョン違いによる、見栄えの違いも確認できます。
さて、自動的に作成されているボタンを削除します。
マウスでボタンを選択して、[Delete] キーを押せば削除出来ます。
消えました。
新しいボタンを配置します。
右側の [Toolbox] から [Button] をドラッグして配置します。
文字を表示させるために、[TextView] も配置します。
TextView を空きスペース一杯に広げます。
TextView を選択して [▼] 印をクリックすると空きスペース一杯に広がります。
[▼] 印が出ない時はもう一回クリックすると出てきます。(クリックするたびに、幅/高さの設定とマージンの設定が切り替わる。)
広がりました。

Button と TextView の id を確認しておきます。
右側の [プロパティ] - [Widget] の一番上に表示されています。これまでの手順通り進めてきたら、button1 と textView1 になっていると思います。自分のわかりやすいように変更しても良いです。

画面の編集はこれで終了。


ここから、ソースを編集して行きます。
まず、自動的に作られる MainActivity.cs です。


"MainActivity.cs"

namespace test0309
{
    [Activity (Label = "test0309", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button> (Resource.Id.myButton);
            
            button.Click += delegate {
                button.Text = string.Format ("{0} clicks!", count++);
            };
        }
    }
}


元々あったボタンを削除しているので、このままビルドすると myButton が無くてエラーになりますので修正します。


"MainActivity.cs"

namespace test0309
{
    [Activity (Label = "test0309", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            // Button button = FindViewById<Button> (Resource.Id.myButton);
            Button button = FindViewById<Button> (Resource.Id.button1);
            TextView textView = FindViewById<TextView> (Resource.Id.textView1);

            button.Click += delegate {
                textView.Text+=count.ToString()+"\r\n";
                button.Text = string.Format ("{0} clicks!", count++);
            };
        }
    }
}


太字の三行を追加しました。
リソース ID は、画面の編集の最後で確認した値を使用します。[Resources] - [Resource.designer.cs] ファイルで確認することもできます。

実行してみたところ。

2013年3月5日火曜日

Xamarin 2.0 のインストール と Hello Android

C# で Android OS や iOS や Mac OS のアプリケーションを作成できる Xamarin を使ってみたら、結構便利に使えました。(iOS と Mac OS のアプリ開発には、Mac OS が動いているコンピュータが必要。Android は Windows のみで可能)
C# を少し触ったことがあるなら、Eclipse と Android SDK で Android OS のアプリを作るよりも、とっつきやすいなと思います。開発環境のインストールも Xamarin のほうが手間なく簡単です。

作成できるアプリケーションのサイズが制限される (32KB) かわりに、無料で使用出来る STARTER 版もあるので、気軽に試用してみることができます。

Xamarin Studio のインストール

http://xamarin.com/ のトップページのわかりやすいところに、ダウンロードのリンクがあるのでインストーラーをダウンロードします。この時、名前とメールアドレスと使用目的のアンケートに答える必要があります。

ダウンロードしたインストーラを実行して画面の指示に従って数回クリックすると、JDK や Android SDK や Xamarin Studio とか必要な物を自動的にダウンロードしてインストールしてくれます。
インストールはこれだけで終了です。Eclipse を使う時のように、SDK を別々にインストールする必要が無いのでとても楽ちんです。

Hello, Android - 新規ソリューション(プロジェクト)の作成

動作テストとして新規のソリューション(Visual Studio でいうプロジェクト)を作成して、実行してみます。

Xamarine Studio を 起動したら、[ファイル]-[新規]-[ソリューション] を選択。

特別なことをしていなくても、メニューが日本語になっています。ありがたいですね。
「新しいソリューションダイアログ」で、[C#]-[Android]-[Android Application] を選択。

「名前」と「位置」は適当に指定します。

[OK] を押すとソリューションが作成されます。

Android エミュレーターで実行

すでに Hello, Android アプリケーションが組み込まれていて、なにやらコードが表示されていますが、気にせずこのまま実行してみましょう。

[実行]-[Start Debugging] を選択します。
「Select Device」ダイアログが表示されるので、ここで使用するエミュレーターを選択してから [Start Emulator] ボタンでエミュレーターを起動させます。エミュレーターの起動にはしばらく時間がかかります。

API_7 とか API_8 とかの API のレベルは Android OS のバージョンごとに決まっていて、Android 2.1 が API_7 、Android 4.1 が API_16 のように対応しています。好きなのを選べば良いです。
エミュレーターが起動しました。
エミュレーターが起動すると「Select Device」ダイアログで、起動したエミュレータを選択できるようになるので、選択して [OK] を押します。

少し待つと、Hello, Android アプリケーションが立ち上がります。
(初回の実行では C# の実行環境なんかをエミュレーターに転送するため、しばらく時間がかかります。2回目の実行からは早くなります。)
ボタンを押すと、押した回数が表示されます。

後でどんなコードで動いているのかを見てみましょう。

実機で実行

あらかじめ Android 端末のメーカーサイトから、使用する端末用の USBドライバーをダウンロードして、PC にインストールしておきます。

Android 端末側で、USBデバッグを有効にしておきます。
USB デバッグを有効にした端末を USB で PC に接続していると、デバイスの選択でエミュレーターと一緒に実機も表示されるようになるので、それを選択するだけで実機で実行出来ます。
動きました。簡単です。

エミュレーターよりさくさく動きますので、実機を持っているなら実機でデバッグしたほうが快適です。

どちらで実行するかの選択

実行するデバイス(エミュレーター)は [プロジェクト] - [Android Device Target] で選択出来ます。

Hello, Android のソースコード


"MainActivity.cs"

namespace test1
{
    [Activity (Label = "test1", MainLauncher = true)]
    public class Activity1 : Activity
    {
        int count = 1;

        // 以下が実行時に一回呼ばれるブロック
        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button> (Resource.Id.myButton);
   
            // ↓ここでボタンのクリックイベントの処理を追加
            button.Click += delegate {
                button.Text = string.Format ("{0} clicks!", count++);
            };
        }
    }
}


Xamarin Studio のテキストエディタで日本語のコメントを入力する時に、変換中の文字が表示されなくて、ちょっと使いにくい。確定すると表示される。
そのうち改善されるといいなぁ。

2013年3月2日土曜日

雪がたくさん積もった

久しぶりにたくさん雪が積もった。一晩でこれだけ積もったのは今冬初かな?
週間天気予報を見ると、しばらく天気が良さそうなので最後の大雪かな?もう一回くらい大雪降るかな?
夕方からまた吹雪いている。天気予報では降雪量はあまり多くはならないみたいだけど、風がとても強い。