如何查找任何智能手机的 GPS 位置,然后使其相关






4.94/5 (120投票s)
一个逐步教程,介绍如何从任何智能手机获取 GPS,即使没有内置 GPS,然后使位置有用。

目录
引言
前段时间,我在另一篇文章中提到,我将把位置广播的想法发展成一个移动解决方案作为后续。当时的问题是我无法从手机上获取位置数据,也无法使其变得有用。现在时过境迁!在这篇文章中,我将演示如何获取手机的 GPS 坐标…
即使您的手机没有内置 GPS(就像我的)。
为什么位置定位很困难?
手机技术正在以惊人的速度成熟。目前市面上大部分手机都内置了 GPS。尽管如此,“智能”手机的平均价格高达数百美元,而我们大多数人还没有冲动购买一部新手机(尤其是在撰写本文时,经济形势不容乐观)。
GPS 尚未普及是位置感知开发面临的真正难题。目标是获取任何手机的位置,而不仅仅是支持 GPS 的手机。这其中就存在困难。
谷歌能做到,为什么我们不行?
如果您还没有玩过 Google 移动地图,我强烈推荐您尝试一下。它确实是一个方便的小应用程序,尤其是在您出行时需要地图(对我来说,这种情况经常发生)。但是,移动地图在我的手机上却能正常工作,而我的手机没有 GPS。这怎么可能?谷歌通过其移动地图,利用常见的手机数据,找到了在没有 GPS 的情况下确定您位置的方法。这意味着在没有 GPS 的情况下也可以定位。事实上,让我们通过 C# 来拆解和重建谷歌移动地图的工作原理,开始我们的项目。

协议分析?
本文不打算详细介绍协议或数据包分析器(我使用的是 Microsoft Network Monitor)。 suffice to say that the first step to deconstructing Google Mobile Maps is to analyze HTTP requests while Mobile Maps is running through ActiveSync.
快速查看移动地图的内部工作原理,会发现几件事。首先,谷歌正在向 http://www.google.com/glm/mmap 发送请求以获取位置数据。我已经仔细研究了它们的 API,但这似乎并不是公开的。这意味着,我们需要自己弄清楚如何打包和发送数据。其次,很明显它们发送了四个关键数据
- 蜂窝基站 ID
- 位置区域码 (LAC)
- 移动网络代码 (MNC)
- 移动国家代码 (MCC)
这真是个好消息! 实际上,几乎所有手机都能轻松获取这四条数据。Windows Mobile (5 和 6) 有一个称为无线电接口层 (Radio Interface Layer) 的原生 API,可以获取这四条数据。
蜂窝基站数据 - 无线电接口层
我们需要深入研究 Windows Mobile 无线电接口层来获取蜂窝基站数据。别担心,这并没有您想象的那么复杂。另外,您可以在 MSDN 上搜索 RIL 来获取整个 RIL 库的详细信息。它有非常完善的文档。
我们只对 RIL 的蜂窝基站部分感兴趣。我们需要做的第一件事是添加必要的 PInvoke DLL 导入签名,以调用无线电接口层。
[DllImport("ril.dll")]
private static extern IntPtr RIL_Initialize(uint dwIndex, RILRESULTCALLBACK pfnResult,
RILNOTIFYCALLBACK pfnNotify, uint dwNotificationClasses,
uint dwParam, out IntPtr lphRil);
[DllImport("ril.dll", EntryPoint = "RIL_GetCellTowerInfo")]
private static extern IntPtr RIL_GetCellTowerInfo(IntPtr hRil);
[DllImport("ril.dll", EntryPoint = "RIL_Hangup")]
private static extern IntPtr RIL_Hangup(IntPtr hRil);
[DllImport("ril.dll")]
private static extern IntPtr RIL_Deinitialize(IntPtr hRil);
ril.dll 中包含的这四个方法是我们访问蜂窝基站数据的入口。通过这些方法以及它们支持的 RILRESULTCALLBACK
和 RILNOTIFYCALLBACK
结构,我们可以轻松地调用 Windows 来获取我们的蜂窝基站数据。
public static CellTower GetCellTowerInfo()
{
IntPtr radioInterfaceLayerHandle = IntPtr.Zero;
IntPtr radioResponseHandle = IntPtr.Zero;
// Initialize the radio layer with a result callback parameter.
radioResponseHandle = RIL_Initialize(1, new RILRESULTCALLBACK(CellDataCallback),
null, 0, 0, out radioInterfaceLayerHandle);
// The initialize API call will always return 0 if initialization is successful.
if (radioResponseHandle != IntPtr.Zero)
{
return null;
}
// Query for the current tower data.
radioResponseHandle = RIL_GetCellTowerInfo(radioInterfaceLayerHandle);
// Wait for cell tower info to be returned since RIL_GetCellTowerInfo invokes the
// callback method asynchronously.
waithandle.WaitOne();
// Release the RIL handle
RIL_Deinitialize(radioInterfaceLayerHandle);
// Convert the raw tower data structure data into a CellTower object
return new CellTower()
{
TowerId = Convert.ToInt32(_towerDetails.dwCellID),
LocationAreaCode = Convert.ToInt32(_towerDetails.dwLocationAreaCode),
MobileCountryCode = Convert.ToInt32(_towerDetails.dwMobileCountryCode),
MobileNetworkCode = Convert.ToInt32(_towerDetails.dwMobileNetworkCode),
};
}
这很容易,多亏了 MSDN!现在,我们可以调用 GetCellTowerInfo()
,并将返回一个强类型 CellTower
类,其中包含所有基站的详细信息。我一直 Prefer to work in a strongly typed representation of PInvoke output instead of marshaling pointers.
*注意:此代码在模拟器中无法正常工作,除非您也配置并运行了蜂窝模拟器。我建议使用您的手机而不是模拟器。
蜂窝数据服务
太好了,我们有了蜂窝基站数据,但接下来呢?我们仍然没有 GPS 坐标。我们知道谷歌正在进行这种从蜂窝数据到经纬度的转换,让我们继续深入分析谷歌移动地图。再次查看其内部工作原理,会发现它们正在向上述 URL 发送二进制数据。同样,协议分析超出了本文的范围。您会发现,我们需要将数据打包成一个 55 字节的数组。大部分字节是空的 (0),但我们需要填充一些关键项。下面是创建和填充带有蜂窝基站数据的字节数组的代码
private static byte[] GetFormPostData(int cellTowerId, int mobileCountryCode,
int mobileNetworkCode, int locationAreaCode)
{
byte[] pd = new byte[55];
pd[1] = 14; //0x0e;
pd[16] = 27; //0x1b;
pd[47] = 255; //0xff;
pd[48] = 255; //0xff;
pd[49] = 255; //0xff;
pd[50] = 255; //0xff;
// GSM uses 4 digits while UTMS used 6 digits (hex)
pd[28] = ((Int64)cellTowerId > 65536) ? (byte)5 : (byte)3;
Shift(pd, 17, mobileNetworkCode);
Shift(pd, 21, mobileCountryCode);
Shift(pd, 31, cellTowerId);
Shift(pd, 35, locationAreaCode);
Shift(pd, 39, mobileNetworkCode);
Shift(pd, 43, mobileCountryCode);
return pd;
}
/// <summary>
/// Shifts specified data in the byte array starting at the specified array index.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="leftOperand">The left operand.</param>
private static void Shift(byte[] data, int startIndex, int leftOperand)
{
int rightOperand = 24;
for (int i = 0; i < 4; i++, rightOperand -= 8)
{
data[startIndex++] = (byte)((leftOperand >> rightOperand) & 255);
}
}
简而言之,我们可以传入四个参数:基站 ID、LAC、MNC 和 MCC,然后得到一个字节数组,我们可以通过 HTTP 发送给谷歌。下面的代码演示了整个 HTTP 请求,它获取我们强类型的蜂窝基站数据(来自上面的 PInvoke)并从谷歌数据库返回经纬度!
internal static GeoLocation GetLocation(CellTower tower)
{
try
{
// Translate cell tower data into http post parameter data
byte[] formData = GetFormPostData(tower.TowerId,
tower.MobileCountryCode,
tower.MobileNetworkCode,
tower.LocationAreaCode);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
new Uri(Google_Mobile_Service_Uri));
request.Method = "POST";
request.ContentLength = formData.Length;
request.ContentType = "application/binary";
Stream outputStream = request.GetRequestStream();
// Write the cell data to the http stream
outputStream.Write(formData, 0, formData.Length);
outputStream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
return ReadResponse(response);
}
catch{}
return GeoLocation.Empty;
}
private static GeoLocation ReadResponse(HttpWebResponse response)
{
byte[] responseData = new byte[response.ContentLength];
int bytesRead = 0;
// Read the response into the response byte array
while (bytesRead < responseData.Length)
{
bytesRead += response.GetResponseStream()
.Read(responseData, bytesRead, responseData.Length - bytesRead);
}
// Check the response
if (response.StatusCode == HttpStatusCode.OK)
{
int successful = Convert.ToInt32(GetCode(responseData, 3));
if (successful == 0)
{
return new GeoLocation()
{
Latitude = GetCode(responseData, 7) / 1000000,
Longitude = GetCode(responseData, 11) / 1000000
};
}
}
return GeoLocation.Empty;
}
/// <summary>
/// Gets the latitude or longitude from the byte array.
/// </summary>
/// <param name="data">The byte array.</param>
/// <param name="startIndex">The start index.</param>
/// <returns></returns>
private static double GetCode(byte[] data, int startIndex)
{
return ((double)((data[startIndex++] << 24) |
(data[startIndex++] << 16) |
(data[startIndex++] << 8) |
(data[startIndex++])));
}
简要介绍蜂窝基站
让我花点时间描述一下刚才发生的事情。我们从操作系统读取了手机的蜂窝基站数据,通过 HTTP 发送给谷歌,然后得到了经纬度。这怎么可能?
美国联邦通信委员会 (FCC) 保留了美国每个蜂窝基站的记录。谷歌并没有做什么(太)神奇的事情。它们只是查找我们提交的基站的详细信息,并以经纬度格式返回基站的已知位置。
然而,谷歌并不是唯一一个拥有基站位置数据库的。 OpenCellID.org 是一个开源项目,旨在绘制所有蜂窝基站及其 GPS 坐标的地图。雅虎也在使用其 ZoneTag 服务进行同样的操作。
为什么费尽周折使用谷歌,如果还有更简单的替代方案?
这是个好问题!简单来说,谷歌的数据库是最全面的。OpenCellID 和 ZoneTag 较新,它们的数据库不完整。即使在我家(一个主要城市郊区)也无法从这些“测试版”服务中获得有效的 GPS 结果……但谷歌在我测试过的所有地方都有效。
如果我的手机有 GPS 怎么办?
当然,蜂窝基站数据不如 GPS 精确。您无法通过蜂窝基站数据判断您离基站是 10 英尺还是 1000 英尺。换句话说,如果 GPS 可用,最好还是使用 GPS,因为它精度高。当然也有例外,比如在室内无法获得 GPS 信号时,您很可能仍然有蜂窝连接。
从兼容的手机获取 GPS 数据比获取蜂窝基站数据更容易。更好的是——微软已经分发了一个库来处理所有繁重的工作。我们只需要订阅微软 GPS 类检测到位置变化时触发的事件。剩下的事情微软已经做好了。
private Gps _gps = new Gps();
private GpsPosition _currentPosition;
public void Start()
{
_gps.LocationChanged += new
Microsoft.Location.LocationChangedEventHandler(_gps_LocationChanged);
if (!_gps.Opened)
{
_gps.Open();
}
}
private void _gps_LocationChanged(object sender,
Microsoft.Location.LocationChangedEventArgs args)
{
_currentPosition = args.Position;
}
正如 Staples 的人所说,“太容易了。”
圣杯?
到目前为止,我们已经建立了两种机制来获取手机的经纬度。我们现在拥有与谷歌地图相媲美的潜力!似乎到了可以跳胜利舞、提交代码、喝一杯蛋酒(在我写作时,距离圣诞节还有 5 天)的时候了。
但是,知道自己的位置有什么用呢?
我通常知道我在哪里,所以知道我的位置对我帮助不大。谷歌移动地图等应用程序已经显示了我的位置,但它们并没有做任何其他事情。我就是觉得文章不能到此结束……还没完。
在我看来,基于位置服务的真正价值在于共享您的位置,而不仅仅是知道您的位置。这是社交网络中必然会发生的下一步。
利用位置信息
我坚信创造力,而且有很多开发者比我更有创造力。我将稍微改变方向,尝试提供创造性的影响,而不是具体的解决方案。为什么不让本文的其余部分成为您的移动位置灵感呢?您应该想出下一个关于如何利用您位置的绝妙主意。
我的第一个创意催化剂是更新您的 Yahoo! Fire Eagle 帐户,提供您的经纬度。Fire Eagle 是雅虎的一项新服务,它接收您的位置并将其“广播”给您订阅的应用程序(在其快速增长的列表中)。
实际上,这就是本项目命名为 DeepCast 的原因。我们将位置广播到(潜在的)深度服务和提供商列表。不只是广播…而是深度广播 =)。
Fire Eagle
Fire Eagle 使用 Oauth 作为其授权平台。我对 Oauth 的所有细节并不熟悉。我所知道的是,在任何应用程序可以进行更新之前,都需要一个手动授权过程。
Oauth 身份验证的第一步是生成一个请求令牌和密钥对。

然后,您可以通过(桌面浏览器)访问授权 URL。

您必须手动确认应用程序可以更新您的个人资料。

最后,您必须将请求令牌和密钥换成授权令牌和密钥。这些是您每次应用程序尝试更新您的位置时需要保存和使用的值。

现在您拥有了一个授权令牌和密钥对,您可以随时更新 Fire Eagle。Fire Eagle 将把您的新位置广播给您订阅的底层应用程序。
我从来都不是 Twitter 的忠实粉丝。它需要太多的工作才能获得太少的信息(仅为个人观点……)。但是,如果 Twitter 能够告诉我人们在哪里呢?!
如果我们能发布一条包含指向显示您位置的谷歌地图链接的推文呢?这似乎有一定用处。

在更新 Twitter 方面几乎没有什么可以描述的。它就像发起一个 HTTP 请求一样简单。唯一的困难是添加超链接到消息。Twitter 使用一种 URL 编码形式,该形式会截断大多数查询字符串。我们将生成一个 TinyUrl 来解决这个问题。
以下是更新 Twitter 的方法
internal override void Update(GeoLocation location)
{
if (!string.IsNullOrEmpty(Settings.TwitterEmail) &&
!string.IsNullOrEmpty(Settings.TwitterPassword))
{
string googleMapLink = string.Format(Google_Map_Uri,
location.Latitude, location.Longitude);
string tilyMapLink = TinyUrlService.GetTinyUrl(googleMapLink);
string place = string.Format("I'm here: {0} - 'L:{1}, {2}'",
new object[] { tilyMapLink, location.Latitude, location.Longitude });
string data = string.Format("status={0}", place);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Twitter_Api_Uri);
request.Credentials = new NetworkCredential(Settings.TwitterEmail,
Settings.TwitterPassword);
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
request.AllowWriteStreamBuffering = true;
byte[] bytes = Encoding.UTF8.GetBytes(data);
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Flush();
requestStream.Close();
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader =
new StreamReader(response.GetResponseStream()))
{
reader.ReadToEnd();
}
}
}
}
}
请注意,有一个位置约定,“L: lat, lon”,它具有更深层的目的。TwitterMap.com 的人可以解析这种格式。再次,我们试图让尽可能多的受众能够利用位置信息。
内部服务
当然,共享您的位置并不是使信息有用的唯一方法。该项目已以一种方式构建,使得位置既可以用于内部,也可以用于外部。例如,主页利用了外部服务使用的相同位置。主页上的地图只是一个浏览器控件,其 URL 被设置为雅虎地图的图像。简单明了,这正是我想要的!

您的服务就在这里
没错——轮到您了。项目的大部分内容已经为您搭建好了框架,以便您添加自己的位置感知服务。如果您读到了这里,您可能对位置服务感兴趣,所以请随意修改项目并开始编码吧!我鼓励所有读者将此项目作为通往下一个伟大想法的垫脚石。我能想到的一些位置用途包括
- 通过 Web 服务跟踪您的行程。创建一个后端网站,显示您的行程地图 - 何时何地。绘制您旅行过的历史轨迹。
- 跟踪您的孩子,以便您随时知道他们在哪里。
- 为您的照片添加地理标签。例如,Flickr 支持地理标记图像。
- 记录您的车队在哪里(例如,FedEx 送货员)。
- 地理博客。
- 更新您的 Facebook/MySpace/社交网站上的位置。
- 说真的,现在轮到您了!
感谢您的阅读,祝您编码愉快!
历史
- 2008 年 12 月 22 日 - 文章提交
- 2009 年 2 月 8 日 - 整合了所有用户提交的错误修复,修复了 Twitter HTTP 417 异常
- 非常感谢 pejonsson 的建议!