C# Programming

Willcom03簡単なモバイルアプリケーション開発10 〜 いまどこサービス

開発環境: Visual Studio 2008/SQL Server 2000 

1.目次

1.目次
2.目的
3.参考書
4.作り方
5.Willcom_03_側のアプリケーション開発
6.WEB からの検索
7.ダウンロード
8.まとめ

2.目的

最近、仕事が忙しくって、妻が今どこにいるの?ご飯いるの?と心配してくれるのはうれしいのだけれど、返事する時間ももったいない。そこで、Willcom 03 の位置情報をデータベースにアップロードして、Web から位置情報を見れるようにしておけば・・・

ということで、位置情報を取得する方法はわかっているので、あとは実装するだけ。

注意:

  • このサービスはベーター版です。こちらの都合で、いつ停止するわかかりません。
  • このサービス、およびアプリケーションに対して、いかなる保証も行いません。
  • 位置精度は、数十メートルの誤差があります。

3.参考書

  1. Willcom 03 での自局番号、緯度、経度情報の取得
  2. 現在の緯度、経度の取得

4.作り方

4.1 基本的な動作

基本的な動作は、つぎのようになります。

基本的な動作

4.2 Willcom 03 の位置情報の取得

Willcom 03 では、W-SIM より、基地局の郵便番号、緯度、経度情報を取得することができます。この方法に関しては、参考文献(1)、(2) を参照してください。



ただし、Willcom の基地局の緯度、経度データは日本測地系なのですが、Google マップは 世界測地系で、少しずれが生じます。
http://www.k-erc.pref.kanagawa.jp/learning/gakusyuDB/chizu/I_HK/I_HK.htm 参照。 そこで、緯度、経度情報の日本測地系から世界測地系への返還が必要になります。

変換は、正確に行うためにはかなり大変なんですが、東京近辺での簡易変換式は次に載っていました。

http://homepage3.nifty.com/Nowral/02_DATUM/02_DATUM.html

このツールでは、この近似式を使用しています。なお、浮動小数点だと精度が足りないため、倍精度小数点で計算する必要があります。

double BWGS84 = latitude - 0.00010695D * latitude + 0.000017464D * longitude + 0.0046017D;
double LWGS84 = longitude - 0.000046038D * latitude - 0.000083043D * longitude + 0.010040D;

4.3 データベースの作成

W03 で取得した経度、緯度情報をホスティングサーバーのデータベースにアップロードします。そのために、まず Location テーブルを作成します。

なお、ホスティングサーバーでは、SQL Server 2000, .NET Framework 2.0以上がサポートされている必要があります。

Location Table

テーブルを作るスクリプトは、次の通り。

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Location]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Location]
GO

CREATE TABLE [dbo].[Location] (
	[ID] [int] IDENTITY (1, 1) NOT NULL ,
	[GUID] [nvarchar] (50) COLLATE Japanese_CI_AS NOT NULL ,
	[Latitude] [float] NULL ,
	[Longitude] [float] NULL ,
	[Date] [datetime] NOT NULL 
) ON [PRIMARY]
GO

4.4 ストアドの作成

W03 で取得した経度、緯度情報をデータベースにアップロードしますが、同じ GUID がなければ INSERT、同じ GUID があれば、UPDATEするようにします。そのために、つぎのようなストアドを作成しておきます。UPDATE時には、更新日(Date)を更新するようにします。

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[UpdateLocation]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[UpdateLocation]
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

CREATE PROCEDURE UpdateLocation
@GUID nvarchar(50),
@Latitude float,
@Longitude float
AS
IF EXISTS (SELECT * FROM Location WHERE GUID=@GUID)
UPDATE Location
	SET Latitude = @Latitude, Longitude= @Longitude, Date=GetDate()  WHERE GUID=@GUID
ELSE
INSERT INTO Location (GUID, Latitude, Longitude)
	VALUES (@GUID, @Latitude, @Longitude)

GO
SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

4.5 データベースに緯度、経度情報をアップロードするための Web Service を作成します。

Willcom 03 からのデータベースの書き込みは、呼び出しが簡単な Web Service を使用します。ここで、作成したストアドにパラメータを渡して呼び出すようにします。

    // Hosting
    const string connectionString = "....";

    [WebMethod]
    public int UpdateLocation(string Guid, double latitude, double longitude)
    {
      SqlConnection sqlConn = new SqlConnection(connectionString);
      SqlCommand sqlCmdStored = new SqlCommand("UpdateLocation");
      sqlCmdStored.CommandType = System.Data.CommandType.StoredProcedure;

      SqlParameter nvGuid = new SqlParameter("@GUID", SqlDbType.NVarChar, 50);
      nvGuid.Value = Guid;
      sqlCmdStored.Parameters.Add(nvGuid);

      SqlParameter fLatitude = new SqlParameter("@Latitude", SqlDbType.Float);
      fLatitude.Value = latitude;
      sqlCmdStored.Parameters.Add(fLatitude);

      SqlParameter fLongitude = new SqlParameter("@Longitude", SqlDbType.Float);
      fLongitude.Value = longitude;
      sqlCmdStored.Parameters.Add(fLongitude);

      sqlConn.Open();
      sqlCmdStored.Connection = sqlConn;

      int res = sqlCmdStored.ExecuteNonQuery();

      return res;
    }

実行すると、次のような Web ページが起動します。そこで、適当な値を入れて、「起動」ボタンを押します。

WebService

データベース上で、次のように新しい行が挿入されていればOKです。

Result

次に、次のように同じ Guid で値を変えて試してみます。

Update

次のように、同じGuid の Latitude, Longitudeが更新されていれば OK です。

Updated

4.6 GUID を渡して、Google Map へのURL を返す Web Service を作成します。

次に、クライアントから GUID を渡して、その緯度、経度をデータベースから読みだし、Google Map の URLに変換して返す Web Service を作成します。

ここでは、単に SELECT * From Location WHERE GUID = @nvGuid というように GUIDパラメータに一致する行を取得し、URLを組み立てて、Web Service の引数にして返してあげます。

    // Hosting
    const string connectionString = "....";

    public string GetUrl(string Guid)
    {
      SqlConnection sqlConn = new SqlConnection(connectionString);
      SqlCommand sqlSelect = new SqlCommand("SELECT * From Location WHERE GUID = @nvGuid");
      sqlSelect.CommandType = System.Data.CommandType.Text;

      SqlParameter nvGuid = new SqlParameter("@nvGuid", SqlDbType.NVarChar, 50);
      nvGuid.Value = Guid;
      nvGuid.SourceColumn = "nvGuid";
      sqlSelect.Parameters.Add(nvGuid);

      sqlConn.Open();
      sqlSelect.Connection = sqlConn;

      using (SqlDataReader dr = sqlSelect.ExecuteReader())
      {
        // http://maps.google.co.jp/maps?q=35.655152,+139.704444&hl=ja
        if (dr.Read())
        {
          double latitude = (double)dr["Latitude"];
          double longitude = (double)dr["Longitude"];
          string url = "http://maps.google.co.jp/maps?q=" + latitude.ToString()
            + ",+" + longitude.ToString() + "&hl=ja";


          return url;
        }
      }
      return "";
    }

実行すると、次のような Web ページが起動します。そこで、適当な値を入れて、「起動」ボタンを押します。

GetUrl

次のような結果が帰ってくれば OKです。

結果

4.7 ホスティングサーバーに Web Service を発行

ここまでで、動作を確認したら、ホスティングサーバーに Web Service を発行します。

Submit Web Service

すると、Web Server に Virtual Directory が作成されます。ホストサーバーの Web サーバー管理ツールから、この場所の Script 実行権を与えます。

Virtual Directory

以上で、http://uchukamen.com/w03location/w03location.asmx への Web Service の発行が完了します。

5. Willcom 03 側のアプリケーション開発

Willcom 03 側のアプリケーションは、データベースへの書き込みをWeb Service化してあることにより、かなり簡単になります。

5.1 設定ファイルの書き込み、読み出し

設定ファイルの書き込み、読み出し部分です。最初は、電話番号で引こうと思いましたが、個人情報のこともあり、GUIDにしました。

using System;
using System.Text;
using System.Xml;
using System.Reflection;
using System.IO;

namespace uchukamen.WZero3
{
  public class ConfigFile
  {
    # region プロパティ
    private string guid = "";  // GUID

    public string Guid
    {
      get { return guid; }
      set { guid = value; }
    }

    private int interval = 10*60*1000; // 10分

    public int Interval
    {
      get { return interval; }
      set { interval = value; }
    }

    private bool update = true; 

    public bool Update
    {
      get { return update; }
      set { update = value; }
    }

    private bool upload = true; 

    public bool Upload
    {
      get { return upload; }
      set { upload = value; }
    }
    #endregion

    private string configFilePath = GetCurrentDirectory() + "\\config.xml";

    public ConfigFile()
    {
      // 初期値
      interval = 10 * 60 * 1000;  // 更新間隔 10分
      update = true;              // 地図を自動更新する
      upload = true;              // データをアップロードする
      guid = System.Guid.NewGuid().ToString();

      if (!File.Exists(configFilePath))
      {
        // 設定ファイルがないので、初期値を書き込む
        WriteConfigFile();
      }
      else
      {
        // 設定ファイルから値を読み込む
        ReadConfigFile();
      }
    }

    public void WriteConfigFile()
    {
      using (XmlTextWriter xtw = new XmlTextWriter(configFilePath, Encoding.Unicode))
      {
        xtw.WriteStartElement("configuration");
        xtw.WriteElementString("GUID", guid);
        xtw.WriteElementString("更新間隔", interval.ToString());
        xtw.WriteElementString("自動更新", update.ToString().ToLower());
        xtw.WriteElementString("アップロード", upload.ToString().ToLower());
        xtw.WriteEndElement();
        xtw.Close();
      }
    }

    public void ReadConfigFile()
    {
      try
      {

        using (XmlTextReader xtr = new XmlTextReader(configFilePath))
        {
          xtr.ReadStartElement("configuration");
          guid = xtr.ReadElementString("GUID");
          interval = xtr.ReadElementContentAsInt("更新間隔", "");
          update = xtr.ReadElementContentAsBoolean("自動更新", "");
          upload = xtr.ReadElementContentAsBoolean("アップロード", "");
          xtr.Close();
        }
      }
      catch (Exception)
      {
        // 設定ファイルにエラーがある場合は、無視して継続する
      }
    }

    public static string GetCurrentDirectory()
    {
      string fqn = Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName;
      FileInfo finfo = new FileInfo(fqn);
      return finfo.DirectoryName;
    }

    string number;

    private void GetNumberFromWSIM()
    {
      if (number != "")
        return;

      RasConn.Disconnect();
      WZero3GetNumber getNum = new WZero3GetNumber();
      getNum.Received = MyReceivedNum;
      getNum.GetNumber();
    }

    private void MyReceivedNum(object o, EventArgs e)
    {
      number = ((WZero3GetNumber)o).Number;
    }
  }
}

5.2 設定ファイル用ダイアログ

設定ファイル用のダイアログです。

using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace uchukamen.WZero3
{
  public partial class FormConfig : Form
  {
    public FormConfig()
    {
      InitializeComponent();
    }

    private string guid = "";
    ConfigFile cf = new ConfigFile();

    private void FormConfig_Load(object sender, EventArgs e)
    {
      try
      {
        guid = cf.Guid;
        this.textBoxGUID.Text = cf.Guid;
        this.numericUpDown1.Value = cf.Interval / 60 / 1000;
        this.checkBoxUpdate.Checked = cf.Update;
        this.checkBoxUpload.Checked = cf.Upload;
      }
      catch (Exception exc)
      {
        this.notification1.Caption = "Error";
        this.notification1.Text = exc.Message;
      }
    }

    private void buttonSave_Click(object sender, EventArgs e)
    {
      cf.Interval = (int)this.numericUpDown1.Value * 60 * 1000;
      cf.Update = checkBoxUpdate.Checked;
      cf.Upload = checkBoxUpload.Checked;
      cf.WriteConfigFile();
    }
  }
}

5.3 メイン画面

メイン画面はいたって簡単。通常は、閉じておいてもバックグラウンドで動作して、位置情報をアップしてくれます。

基本動作は次のとおり。

  1. タイマーでセットした時間になったら、通信を切断
  2. 基地局の緯度、経度情報を読み出しをかける
  3. 読み出し終わりイベントが来たら、緯度、経度を世界測地系に変換する
  4. Web Service でデータベースに書き込む

Google Map で表示ボタン、メールで位置を送信が押された時の動作も、同様にGUID, 経度, 緯度情報をデータベースに書き込み、その後 Google Map を起動するか、メールで位置を送信するかの違いだけです。

ちょっとわかりにくいのが、WZero3Locatorクラスで、読み込みが完了した段階で LocationReceived イベントが呼び出され、そこで緯度、経度情報を取得しているあたりです。これは、通信完了を待って、データを読み出すようにしてしまったので、こんなふうになっていますが、もう少しいい方法があるかもしれません。

            WZero3Locator w3loc = new WZero3Locator();
            w3loc.ReceivedLocation += LocationReceived;
            w3loc.GetLocation();

メイン画面

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Reflection;

namespace uchukamen.WZero3
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void menuItem1_Click(object sender, EventArgs e)
    {
      Application.Exit();
    }

    private string guid = "";
    ConfigFile cf = null;
    bool update;
    bool upload;

    private void Form1_Load(object sender, EventArgs e)
    {
      try
      {
        cf = new ConfigFile();
        guid = cf.Guid;
        this.timer1.Interval = cf.Interval;
        update = cf.Update;
        upload = cf.Upload;
      }
      catch (Exception exc)
      {
        this.textBox1.Text += exc.Message;
      }
    }

    // 更新時間になった
    private void timer1_Tick(object sender, EventArgs e)
    {
      UpdateLocation();
    }

    private void UpdateLocation()
    {
      try
      {
        // SIM 切断処理
        this.textBox1.Text = "";
        Cursor.Current = Cursors.WaitCursor;
        this.buttonShowGMap.Enabled = false;
        this.textBox1.Text += "SIM切断中\r\n";
        RasConn.Disconnect();
        this.textBox1.Text += "SIM切断完了\r\n";

        // 基地局情報(郵便番号、緯度、経度)の取得処理
        // エラーとなる場合が多いので、5回成功するまで繰り返す。
        for (int i = 0; i < 5; i++)
        {
          try
          {
            this.textBox1.Text += "・";
            WZero3Locator w3loc = new WZero3Locator();
            w3loc.ReceivedLocation += LocationReceived;
            w3loc.GetLocation();
            // 例外が上がらなければ、成功したのでループから抜ける。
            return;
          }
          catch (Exception exc)
          {
            this.textBox1.Text += exc.Message;
          }
        }
      }
      catch (RasConn.NoSimException)
      {
        this.textBox1.Text += "SIMが見つかりません。SIM を確認してください。\r\n";
      }
      catch (RasConn.SimOffException)
      {
        this.textBox1.Text += "SIMをオンにしてください\r\n";
      }
      catch (Exception exc)
      {
        textBox1.Text += exc.Message + "\r\n";
      }
      finally
      {
        Cursor.Current = Cursors.Default;
        this.buttonShowGMap.Enabled = true;
      }
    }

    double latitude;    // 緯度:日本測地系
    double longitude;   // 経度:日本測地系
    double wLatitude;   // 緯度:世界測地系
    double wLongitude;  // 経度:世界測地系

    private void LocationReceived(object o, EventArgs e)
    {
      latitude = ((WZero3Locator)o).FLatitude;
      longitude = ((WZero3Locator)o).FLongitude;

      this.textBox1.Text += "\r\n";
      textBox1.Text += "位置取得:" + ((WZero3Locator)o).Latitude + ":" + ((WZero3Locator)o).Longitude + "\r\n";

      // 緯度、経度の簡易変換を行う
      ConvertJapanToWorld(latitude, longitude, out wLatitude, out wLongitude);

      try
      {
        Cursor.Current = Cursors.WaitCursor;
        this.buttonShowGMap.Enabled = false;

        // データベースに書き込む
        UpdateDatabse();
      }
      catch (Exception exc)
      {
        textBox1.Text += exc.Message;
      }
      finally
      {
        Cursor.Current = Cursors.Default;
        this.buttonShowGMap.Enabled = true;
      }
    }

    // グーグルマップを表示する
    private void ShowGoogleMap()
    {
      string loc = string.Format("-URL \"?action=locn&a=@latlon:{0:F9},{1:F9}\"", wLatitude, wLongitude);
      Debug.WriteLine(loc);
      Process.Start(@"\Program Files\GoogleMaps\GoogleMaps.exe", loc);
    }

    private void UpdateDatabse()
    {
      UpdateLoc.W03Location svc = new UpdateLoc.W03Location();
      svc.UpdateLocation(guid, wLatitude, wLongitude);
      textBox1.Text += "データベースに登録完了\r\n";
    }

    // 緯度、経度の簡易変換を行う
    // Google マップは 世界測地系、一方でWillcom の基地局データは日本測地系
    // http://uchukamen.spaces.live.com/blog/cns!7CB203A44BF94940!673.entry?&_c02_owner=1
    private void ConvertJapanToWorld(double latitude, double longitude, out double wLatitude, out double wLongitude)
    {
      wLatitude = latitude - 0.00010695d * latitude + 0.000017464d * longitude + 0.0046017d;
      wLongitude = longitude - 0.000046038d * latitude - 0.000083043d * longitude + 0.010040d;
    }
    
    // About 表示
    private void menuItemAbout_Click(object sender, EventArgs e)
    {
      FormAbout formAbout = new FormAbout();
      formAbout.ShowDialog();
    }

    private void buttonShowGMap_Click(object sender, EventArgs e)
    {
      UpdateLocation();

      // グーグルマップを表示する
      ShowGoogleMap();
    }

    private void menuItemUpdate_Click(object sender, EventArgs e)
    {
      CheckNewVersion();
    }

    // ソフトウェアの更新をチェック
    private void CheckNewVersion()
    {
      try
      {
        Cursor.Current = Cursors.WaitCursor;
        Assembly assembly = Assembly.GetExecutingAssembly();
        string currentVersion = assembly.GetName().Version.ToString();
        com.uchukamen.Update update = new com.uchukamen.Update();
        bool isLatest = update.IsLatest("W03DeGMap", currentVersion);
        string version = update.LatestVersion("W03DeGMap");
        if (isLatest)
          MessageBox.Show("最新のバージョン Ver." + version + " です。", "Version");
        else
        {
          DialogResult res = MessageBox.Show("最新のバージョンではありません。最新のバージョンは Ver." +
            version + " です。OK を押すとダウンロードページを開きます。",
            "Version", MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1);
          if (res == DialogResult.OK)
            System.Diagnostics.Process.Start(update.GetUrl("W03DeGMap"), "");
        }
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message, "W03DeGMap",
           MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);

      }
      finally
      {
        Cursor.Current = Cursors.Default;
      }
    }

    private void menuItemConfig_Click(object sender, EventArgs e)
    {
      FormConfig formConfig = new FormConfig();
      DialogResult res = formConfig.ShowDialog();
      if (res == DialogResult.OK)
      {
        cf = new ConfigFile();
        guid = cf.Guid;
        this.timer1.Interval = cf.Interval;
        update = cf.Update;
        upload = cf.Upload;
      }
    }

    private void buttonSendMail_Click(object sender, EventArgs e)
    {
      UpdateLocation();

      string url = "http://uchukamen.com/w03degooglemapweb/?GUID=" + guid;
      Process.Start(@"\Windows\STMail.exe", "mailto:?subject=今このあたりにいます&body="+ url);
    }
  }
}

5.4 緯度、経度取得用のクラス

Willcom 03 の緯度、経度取得用のクラスです。WZero3 でも動きました。

using System;
using System.IO.Ports;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading;

namespace uchukamen.WZero3
{
  class WZero3Locator
  {
    private SerialPort serialPort = new
        SerialPort("COM1", 115200,
            System.IO.Ports.Parity.None, 8);

    public WZero3Locator()
    {
      serialPort.NewLine = "\r\n";
      serialPort.DtrEnable = true;
      serialPort.Handshake = Handshake.RequestToSend;
      serialPort.RtsEnable = true;
      serialPort.DataReceived += new
           SerialDataReceivedEventHandler
               (serialPort_DataReceived);
      serialPort.PinChanged += new
           SerialPinChangedEventHandler
               (serialPort_PinChanged);
      serialPort.ErrorReceived += new
           SerialErrorReceivedEventHandler
               (serialPort_ErrorReceived);
      serialPort.Disposed += new EventHandler
              (serialPort_Disposed);
      serialPort.ReadTimeout = 1000;
      serialPort.WriteTimeout = 1000;
    }

    public void Dispose()
    {
      serialPort.Dispose();
    }

    private void Send(string str)
    {
      while (serialPort.CtsHolding == true)
        Thread.Sleep(100);
      serialPort.WriteLine(str);

      Debug.WriteLine(">>>>>>>>>>>>:" + str);
    }

    public enum DataType
    {
      Location, Number
    }

    public void GetLocation()
    {
      resultString = "";
      if (serialPort.IsOpen == false)
        serialPort.Open();

      Send("AT@LBC1");
      Send("AT@LBC?");
      Send("AT@LBC2");
      Send("AT");

      serialPort.Close();
    }

    public void GetNumber()
    {
      resultString = "";
      if (serialPort.IsOpen == false)
        serialPort.Open();

      Send("ATI6");
      Send("AT");

      serialPort.Close();
    }

    public EventHandler ReceivedLocation;

    private void serialPort_Disposed
           (object sender, EventArgs e)
    {
      Debug.WriteLine(resultString);
      ParseLocationData(resultString);

      ReceivedLocation(this, null);
    }

    private string resultString = "";

    private void serialPort_DataReceived
          (object sender, SerialDataReceivedEventArgs e)
    {
      string retStr = serialPort.ReadExisting();

      Debug.WriteLine("<<<<<<<<<<<<\n" + retStr);

      resultString += retStr;
    }

    private void serialPort_ErrorReceived
        (object sender, SerialErrorReceivedEventArgs e)
    {
      Debug.WriteLine("serialPort_ErrorReceived");
    }

    private void serialPort_PinChanged
        (object sender, SerialPinChangedEventArgs e)
    {
      Debug.WriteLine("serialPort_PinChanged: CTS="
         + serialPort.CtsHolding.ToString());
    }

    #region 郵便番号、緯度、経度のプロパティ
    private string errString = "";
    public string ErrString
    {
      get { return errString; }
    }

    private string zipCode;
    public string ZipCode
    {
      get { return zipCode; }
    }

    private string latitude;
    public string Latitude
    {
      get { return latitude; }
    }

    private string longitude;
    public string Longitude
    {
      get { return longitude; }
    }

    private double fLatitude;
    public double FLatitude
    {
      get { return fLatitude; }
    }

    private double fLongitude;
    public double FLongitude
    {
      get { return fLongitude; }
    }

    private string number;
    public string Number
    {
      get { return number; }
    }
    #endregion

    #region 郵便番号、緯度、経度の文字列処理
    public void ParseLocationData(string str)
    {
      zipCode = GetZipCode(str);
      latitude = GetLatitude(str);
      longitude = GetLongitude(str);

      fLatitude = GetFloatLatitude(latitude);
      fLongitude = GetFloatLongitude(longitude);
    }

    private double GetFloatLatitude(string latitude)
    {
      Regex regVal = new Regex("[0-9]+");
      MatchCollection mcLatitude =
           regVal.Matches(latitude);
      return double.Parse(mcLatitude[0].Value)
          + (double.Parse(mcLatitude[1].Value)) / 60.0d
          + (double.Parse(mcLatitude[2].Value)) / 3600.0d;
    }

    private double GetFloatLongitude(string longitude)
    {
      Regex regVal = new Regex("[0-9]+");
      MatchCollection mcLongitude =
            regVal.Matches(longitude);
      return double.Parse(mcLongitude[0].Value)
          + double.Parse(mcLongitude[1].Value) / 60.0d
          + double.Parse(mcLongitude[2].Value) / 3600.0d;
    }

    private string GetZipCode(string str)
    {
      Regex regZip = new
         Regex("[0-9][0-9][0-9][0-9][0-9][0-9][0-9]");
      Match match = regZip.Match(str);
      if (!match.Success)
        throw (new Exception("郵便番号の取得に失敗"));
      return match.Value;
    }

    private string GetLatitude(string str)
    {
      Regex regLat = new
           Regex("[NS][0-9]+:[0-9]+:[0-9]+");
      Match match = regLat.Match(str);
      if (!match.Success)
        throw (new Exception("緯度の取得に失敗"));
      return match.Value;
    }

    private string GetLongitude(string str)
    {
      Regex regLong = new
          Regex("[EW][0-9]+:[0-9]+:[0-9]+");
      Match match = regLong.Match(str);
      if (!match.Success)
        throw (new Exception("緯度の取得に失敗"));
      return match.Value;
    }
    #endregion
  }
}

5.5 通信切断部分

RAS 接続切断部分です。

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace uchukamen.WZero3
{
  /// 
  /// This class is based on code from "mikinder".
  /// http://www.developersdex.com/vb/message.asp?p=2916&r=5643969
  /// 
  class RasConn
  {
    const int MAX_PATH = 260;
    const int RAS_MaxDeviceType = 16;
    const int RAS_MaxPhoneNumber = 128;
    const int RAS_MaxEntryName = 20;
    const int RAS_MaxDeviceName = 128;
    const int SUCCESS = 0;
    const int ERROR_NOT_ENOUGH_MEMORY = 8;
    const int RASBASE = 600;
    const int ERROR_BUFFER_TOO_SMALL = RASBASE + 3;
    const int ERROR_INVALID_SIZE = RASBASE + 32;
    #region DllImport
    //// --- RASCONN data structure definition (refer to ras.h) --
    //private const int RAS_MaxEntryName = 20;
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct RASCONN
    {
      public int dwSize;
      public IntPtr hrasconn;
      [MarshalAs(UnmanagedType.ByValTStr,
          SizeConst = RAS_MaxEntryName + 1)]
      public string szEntryName;
    }
    // --------------------------------------------
    [DllImport("coredll.dll", SetLastError =
         true, CharSet = CharSet.Auto)]
    private static extern uint RasEnumConnections(
        [In, Out] RASCONN[] rasconn,
        [In, Out] ref int cb,
        [Out] out int connections);
    [DllImport("coredll.dll")]
    private static extern uint RasHangUp(IntPtr pRasConn);
    #endregion
    /// 
    /// Returns all active RAS connections as 
    /// an array of data structure RASCONN
    /// 
    public static RASCONN[] GetAllConnections()
    {
      RASCONN[] tempConn = new RASCONN[1];
      RASCONN[] allConnections = tempConn;
      tempConn[0].dwSize = Marshal.SizeOf(typeof(RASCONN));
      int lpcb = tempConn[0].dwSize;
      int lpcConnections = 0;
      uint ret = RasEnumConnections
          (tempConn, ref lpcb, out lpcConnections);
      if (ret == ERROR_INVALID_SIZE)
      {
        throw new Exception
            ("RAS: RASCONN data structure has invalid format");
      }
      else if (ret == ERROR_BUFFER_TOO_SMALL && lpcb != 0)
      {
        // first call returned that 
        // there are more than one connections
        // and more memory is required
        allConnections = new RASCONN[lpcb
          / Marshal.SizeOf(typeof(RASCONN))];
        allConnections[0] = tempConn[0];
        ret = RasEnumConnections(allConnections,
          ref lpcb, out lpcConnections);
      }
      // Check errors
      if (ret != SUCCESS)
      {
        throw new Exception("RAS returns error: " + ret);
      }
      if (lpcConnections > allConnections.Length)
      {
        throw new Exception
           ("RAS: error retrieving correct connection count");
      }
      else if (lpcConnections == 0)
      {
        // if there are no connections 
        // resize the data structure
        allConnections = new RASCONN[0];
      }
      return allConnections;
    }
    /// 
    /// Closes all active RAS connections
    /// 
    /// 
    private static void CloseAllConnections()
    {
      RASCONN[] connections = GetAllConnections();
      for (int i = 0; i < connections.Length; ++i)
      {
        RasHangUp(connections[i].hrasconn);
      }
    }

    enum WSIMState { Normal = 0, NoSim = -2, Com = -3, Off = -7 };
    // 0:Normal : 通常 
    //-2:NoSim  : W-SIMなし 
    //-3:Com    : 通信中 
    //-7:Off
    [DllImport("shphonelib.dll", EntryPoint =
       "?GetWsimStateInfo@CshphoneClientlib@@SAHXZ")]
    public static extern int GetWsimStateInfo();

    public class NoSimException: Exception
    {
      public NoSimException(string message): base(message)
      {
      }
    }

    public class SimOffException : Exception
    {
      public SimOffException(string message)
        : base(message)
      {
      }
    }

    public static void Disconnect()
    {
      // SIMの状態を調べる
      int result = GetWsimStateInfo();
      if (result == (int)WSIMState.NoSim)
        throw new NoSimException("W-SIMがありません。");
      if (result == (int)WSIMState.Off)
              throw new SimOffException("W-SIMがオフです。");

      if (result == (int)WSIMState.Com)
      {
        // 通信中の場合は、緯度、経度情報を取得できない。
        // このため、強制的に通信を切断する。
        RasConn.CloseAllConnections();

        // MSDN によると、切断後約3秒待つ必要がある。
        for (int i = 0; i < 3; i++)
        {
          Thread.Sleep(1000);
          if (GetWsimStateInfo() == (int)WSIMState.Normal)
            break;
        }
      }
    }
  }
}

6.WEB からの検索


 最後に、次のように GUID から Google Map を表示させるためのページを作成します。

WebPage

Visual Studio で Web サイトを作成し、そこで、次のようにレイアウトします。

Visual Studio

次に、次のような実装を行います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      if (Request.QueryString.Count == 0)
        return;

      string guid = this.TextBox1.Text = Request.QueryString["GUID"];
      com.uchukamen.W03Location w03 = new com.uchukamen.W03Location();
      Response.Redirect(w03.GetUrl(guid));
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
      com.uchukamen.W03Location w03 = new com.uchukamen.W03Location();
      string s = w03.GetUrl(this.TextBox1.Text);
      Response.Redirect(s);
    }
  }
}

ページがロードされた時に、 "http://uchukamen.com/w03degooglemapweb/?GUID=bb11c154-df7c-4dee-a0b8-9d11d599845c" のように GUIDのクエリストリングが渡された場合には、GUIDを引数に W03Location ウェブサービスを呼び出し、返り値としてURLを取得します。そして、そのURLへリダイレクトすることにより、Google Map を表示させます。

同様に、ボタンを押したときのイベントハンドラで、TextBox1のGUIDを取得し、同様に Google Map を表示させる処理を実装します。

7.ダウンロード

 こちらのページより、クライアントソフトをダウンロードしてください。

8.まとめ

忙しくなると、逃避したくなる癖があるようですw