Hace un tiempo publique un post sobre como utilizar el driver intermedio de GPS en Windows Mobile para acceder al GPS del equipo y obtener las coordenadas y con estas un mapa de la ubicación actual. Ahora vamos a comentar el proceso para obtener lo mismo pero para dispositivos que no cuentan con GPS. El proceso consiste básicamente en obtener información del Carrier y torre celular a la cual nuestro equipo esta conectado, con esta información consultar la ubicación aproximada donde estamos usando la API de Google Maps.
El primer paso es obtener de nuestro equipo el Mobile Country Code (MCC) y el Mobile Network Code (MNC) que sirven para identificar el país y tipo de red usada por nuestro carrier, para el caso de México tenemos los siguientes valores: (la lista completa esta disponible en Wikipedia)
| MCC | MNC | Carrier | Red |
| 334 | 01 | Nextel México | iDEN 800 |
| 334 | 02 | Telcel | TDMA 850 / GSM 1900 |
| 334 | 03 | Telefónica Móviles México (movistar) | CDMA2000 1900 / CDMA2000 850 / GSM 1900 / UMTS-HSPA 850 |
| 334 | 04 | Iusacell/Unefon | CDMA 800 / CDMA 1900 |
Para obtener el MCC y el MNC, una forma es consultando la API de Telefonía TAPI. Dado que necesitamos envolver todas llamadas a la API, haremos uso de la clase TAPIHelper que encapsulará todas las llamadas a TAPI, a continuación veremos solamente el método GetOperatorInfo de la clase TAPIHelper
public OperatorInfo GetOperatorInfo()
{
TAPI.LINEOPERATOR lo = new TAPI.LINEOPERATOR();
int result = TAPI.lineGetCurrentOperator(hLine, ref lo);
if (result < 0)
throw new NotSupportedException("lineGetCurrentOperator FAILED. HRESULT=" + result.ToString());
string temp = new string(lo.lpszNetworkInfo);
OperatorInfo li = new OperatorInfo();
li.NetworkCode = temp.Substring(TAPI.MAX_LENGTH_OPERATOR_LONG + TAPI.MAX_LENGTH_OPERATOR_SHORT, TAPI.MAX_LENGTH_OPERATOR_NUMERIC).Replace("\0", "");
return li;
}
Donde observamos que la información que necesitamos se encuentra en NetworkCode. En nuestro caso contiene el valor de “334020”, donde los 3 primeros dígitos son el MMC y los dos dígitos siguientes el MNC.
Cabe mencionar que antes de llamar a esta función es necesario inicializar nuestra clase Helper con el método initTAPI y una vez obtenida la información llamar al método closeTAPI para cerrar la sesión de TAPI.
Lo siguiente que necesitamos obtener es el Local Area Code (LAC) y el Cell Id (CID), para esto necesitamos hacer uso de la API de Radio Interface Layer, mejor conocida cono RIL que puede ser consultada por la mayoría de dispositivos con Windows Mobile. Al igual que TAPI, antes de hacer llamadas a las funciones de RIL requiere inicializarse, esto lo hacemos dentro de la clase Helper de RIL en el método InitRIL:
[DllImport("ril.dll")]
private static extern Int32 RIL_Initialize(int dwIndex, RILRESULTCALLBACK pfnResult, RILNOTIFYCALLBACK pfnNotify, int dwNotificationClasses, int dwParam, out IntPtr lphRil);
public static void InitRIL()
{
RILRESULTCALLBACK result = new RILRESULTCALLBACK(f_result);
RILNOTIFYCALLBACK notify = new RILNOTIFYCALLBACK(f_notify);
res = RIL_Initialize(1, result, notify, 0, 0, out hRil);
if (res < 0)
throw new NotSupportedException("Could not initialize RIL. HRESULT=" + res.ToString());
}
Donde estamos especificando las funciones que se mandaran llamar cuando la API de RIL complete la información solicitada (RILRESULTCALLBACK) y cuando se registre un evento (RILNOTIFYCALLBACK).
Una vez inicializada nuestra clase Helper, podemos llamar al método GetCellTowerInfo de la clase Helper para obtener el LAC y CID
public static RILCELLTOWERINFO GetCellTowerInfo() {
RIL_lastFunction = RIL_FunctionCall.RIL_GetCellTowerInfo;
RIL.done = false;
res = RIL_GetCellTowerInfo(hRil);
if (res < 0)
throw new NotSupportedException("RIL_GetCellTowerInfo FAILED. HRESULT=" + res.ToString());
WaitUntilDone();
>
return rci;
}
Este método se encarga de hacer la llamada a la función GetCellTowerInfo definida como
[DllImport("ril.dll", EntryPoint = "RIL_GetCellTowerInfo")]
private static extern Int32 RIL_GetCellTowerInfo(IntPtr hRil);
Como la mayoría de las llamadas a la API de RIL son asíncronas, se establece cual es la última función que se mando llamar en la variable estática RIL_lastFunction para diferenciar entre las diferentes llamadas a RIL, y esperamos hasta que este disponible el resultado, esto es, cuando la variable RIL.donde sea modificada a true en nuestra CallBackFunction f_result:
private static void f_result(int dwCode, IntPtr hrCmdID, IntPtr lpData, int cbData, int dwParam)
{
switch (RIL_lastFunction)
{
case RIL_FunctionCall.RIL_GetCellTowerInfo:
rci = new RILCELLTOWERINFO();
Marshal.PtrToStructure(lpData, rci);
done = true;
break;
case RIL_FunctionCall.RIL_GetAudioDevices:
rdi = new RILAUDIODEVICEINFO();
Marshal.PtrToStructure(lpData, rdi);
done = true;
break;
}
}
Ahora sí, se llena la estructura RILCELLTOWERINFO que contiene los valores de LAC y CID, con estos valores lo siguiente es hacer un Request la API de Google Maps enviando los 4 valores obtenidos:
String url = "http://www.google.com/glm/mmap";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(url));
req.Method = "POST";
int MCC, MNC, LAC, CID = -1;
GetCellInfo(out MCC, out MNC, out LAC, out CID);
byte[] pd = PostData(MCC, MNC, LAC, CID);
req.ContentLength = pd.Length;
req.ContentType = "application/binary";
Stream outputStream = req.GetRequestStream();
outputStream.Write(pd, 0, pd.Length);
outputStream.Close();
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
byte[] ps = new byte[res.ContentLength];
int totalBytesRead = 0;
while (totalBytesRead < ps.Length)
{
totalBytesRead += res.GetResponseStream().Read(ps, totalBytesRead, ps.Length - totalBytesRead);
}
if (res.StatusCode == HttpStatusCode.OK)
{
short opcode1 = (short)(ps[0] << 8 | ps[1]);
byte opcode2 = ps[2];
System.Diagnostics.Debug.Assert(opcode1 == 0x0e);
System.Diagnostics.Debug.Assert(opcode2 == 0x1b);
int ret_code = (int)((ps[3] << 24) | (ps[4] << 16) | (ps[5] << 8) | ( ps[6 ] ) );
if (ret_code == 0)
{
double lat = ((double)((ps[7] << 24) | ( ps [ 8 ] << 16) | (ps[9] << 8) | (ps[10]))) / 1000000;
double lon = ((double)((ps[11] << 24) | ( ps[12 ] << 16) | (ps[13] << 8) | (ps[14]))) / 1000000;
}
}
Con esto hemos obtenido la latitud y longitud con una precisión aproximada de 300 a 500 metros y podemos utilizar el control WebBrowser para desplegar la imagen con la ubicación. Cabe mencionar que a pesar de no ser tan exacto como el GPS, puede ser de mucha utilidad para algunos casos, por ejemplo, registrar la hora cuando un equipo salió de un lugar, la ruta aproximada que tomó y la hora en la que regreso.
En otro post comentaré como registrar los puntos por los que se va moviendo el equipo y convertirlos en una ruta que pueda ser visualizada en Google Maps.