Geolocation 的正确使用姿势

May 26, 2020

前言

之前在实习过程中,接触过有关地理定位的需求,业务场景是通过 H5 页面获取用户的所处位置,很自然想到了 Web API 中的 navigator.geolocation,大致思路如下:

  1. 通过 navigator.geolocation.getCurrentPosition(success, error, options) 获取用户所持有设备的地理位置的经纬度
  2. 使用百度地图开发平台的全球逆地理编码服务,将经纬度转换为对应位置信息(如所在行政区划分)

困于当时自己水平有限 🤣,采用了一种复杂,低精度的方法,现将最初的思路重新梳理一遍,并实现它。

获取经纬度

新建项目 geolocation,并创建 index.html

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>纬度:<span id="latitude"></span></p>
<p>经度:<span id="longitude"></span></p>
<script>
const options = {
enableHighAccuracy: true, // 使用最高精度
timeout: 5000, // 如果5s内没有获取到位置,则视为超时
};
function success(pos) {
var crd = pos.coords;
document.querySelector("#latitude").textContent = crd.latitude;
document.querySelector("#longitude").textContent = crd.longitude;
}
function error(err) {
const errorEle = document.createElement("p");
errorEle.innerText = `定位失败:ERROR(${err.code}): ${err.message}`;
document.body.appendChild(errorEle);
}
navigator.geolocation.getCurrentPosition(success, error, options);
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>纬度:<span id="latitude"></span></p>
<p>经度:<span id="longitude"></span></p>
<script>
const options = {
enableHighAccuracy: true, // 使用最高精度
timeout: 5000, // 如果5s内没有获取到位置,则视为超时
};
function success(pos) {
var crd = pos.coords;
document.querySelector("#latitude").textContent = crd.latitude;
document.querySelector("#longitude").textContent = crd.longitude;
}
function error(err) {
const errorEle = document.createElement("p");
errorEle.innerText = `定位失败:ERROR(${err.code}): ${err.message}`;
document.body.appendChild(errorEle);
}
navigator.geolocation.getCurrentPosition(success, error, options);
</script>
</body>
</html>

为了便于后续 PC 端和移动端的测试,需保持手机和电脑处于同一网络环境下,并启动 Node 服务。

在此,我推荐使用 serve,它能快速搭建本地服务。

bash
$ yarn global add serve
bash
$ yarn global add serve

进入项目根目录,启动服务:

bash
$ serve
bash
$ serve

这里的 192.168.1.202 是我以太网的 IPv4 地址,每个人都是不一样的,可见 serve 自动选取了它作为 Network 地址,只要你的手机和电脑连接了相同 wifi,就可以在手机上通过 Network 地址访问本机地址。

win 系统可打开 cmd,输入 ipconfig 命令进行查询;macOS 则输入 ifconfig 命令。

⚠️ 后续都采用 Network 地址访问,以支持手机端。

打开浏览器,控制台打印以下错误信息:

浏览器鉴于安全问题,getCurrentPosition() 只能在 HTTPS 协议下使用。

搭建本地 HTTPS

这里用到了 https-localhost,它基于 HTTP2 和 SSL 证书,使用 express 框架,实现了本地 HTTPS 服务。

安装依赖包:

bash
$ yarn add https-localhost
bash
$ yarn add https-localhost

在根目录下新建 app.js

js
const fs = require("fs");
const app = require("https-localhost")();
// app is an express app, do what you usually do with express
app.get("/", (req, res) => {
res.setHeader("content-type", "text/html");
res.send(fs.readFileSync("./index.html"));
});
app.listen(8080);
js
const fs = require("fs");
const app = require("https-localhost")();
// app is an express app, do what you usually do with express
app.get("/", (req, res) => {
res.setHeader("content-type", "text/html");
res.send(fs.readFileSync("./index.html"));
});
app.listen(8080);

启动应用:

bash
$ node app.js
bash
$ node app.js

在第一次运行时,终端会输出以下信息:

bash
① Generating certificates...
② Certificates path: C:\Users\81571\AppData\Roaming\https-localhost. Never modify nor share this files.
③ Downloading the mkcert executable...
bash
① Generating certificates...
② Certificates path: C:\Users\81571\AppData\Roaming\https-localhost. Never modify nor share this files.
③ Downloading the mkcert executable...
  1. 开始生成证书文件
  2. 证书文件存放于 C:\Users\81571\AppData\Roaming\https-localhost 路径下
  3. 下载 mkcert 可执行文件,它用于生成本地证书

之后,你可能会一直卡在这个下载步骤,或者提示资源连接失败,显然资源是需要翻墙的。

修改源码(geolocation\node_modules\https-localhost\certs.js)中的 download 函数:

js
// download a binary file
function download(url, path) {
console.log("Downloading the mkcert executable...");
console.log(url); // 添加这行代码,打印下载地址
const file = fs.createWriteStream(path);
return new Promise(resolve => {
function get(url, file) {
https.get(url, response => {
if (response.statusCode === 302) get(response.headers.location, file);
else response.pipe(file).on("finish", resolve);
});
}
get(url, file);
});
}
js
// download a binary file
function download(url, path) {
console.log("Downloading the mkcert executable...");
console.log(url); // 添加这行代码,打印下载地址
const file = fs.createWriteStream(path);
return new Promise(resolve => {
function get(url, file) {
https.get(url, response => {
if (response.statusCode === 302) get(response.headers.location, file);
else response.pipe(file).on("finish", resolve);
});
}
get(url, file);
});
}

重新启动,终端打印下载地址:https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-windows-amd64.exe

打开它,就会自动下载,不过下载速度实在感人,这也就是为什么之前一直卡在 Downloading the mkcert executable... 的原因。

我预先下载好了,链接: https://pan.baidu.com/s/1QaMDk7X0BKTgNaDZLWnwqQ 提取码: npq7

然后将下载好的 mkcert-v1.4.1-windows-amd64.exe,存放到 ② Certificates path 下。

最后一步,修改源码(geolocation\node_modules\https-localhost\certs.js)中的 generate 函数:

js
async function generate(appDataPath = CERT_PATH, customDomain = undefined) {
……
// download the executable
// await download(url + exe, exePath) 注释该行!注释该行!注释该行!
// make binary executable
fs.chmodSync(exePath, "0755")
// execute the binary
await mkcert(appDataPath, exe, domain)
console.log("Certificates generated, installed and trusted. Ready to go!")
}
js
async function generate(appDataPath = CERT_PATH, customDomain = undefined) {
……
// download the executable
// await download(url + exe, exePath) 注释该行!注释该行!注释该行!
// make binary executable
fs.chmodSync(exePath, "0755")
// execute the binary
await mkcert(appDataPath, exe, domain)
console.log("Certificates generated, installed and trusted. Ready to go!")
}

再次重新启动,看到以下打印信息,就说明成功了。

bash
Certificates generated, installed and trusted. Ready to go!
Server running on port 8080.
bash
Certificates generated, installed and trusted. Ready to go!
Server running on port 8080.

测试

本节我们分别针对 PC 端和移动端进行测试,验证 Geolocation.getCurrentPosition() 的可行性,访问地址均为 https://<IPv4>:8080/

PC 端

测试浏览器为:Chrome、Firefox、Edge.

Chrome

Firefox

Edge

不知道为什么,PC 端 Edge 无法访问 https://<IPv4>:8080/,于是采用本机地址。

这里的错误码 (code)代表地理定位失败的状态,错误信息 (message)则代表失败的详细原因,它们均在 W3C 规范中的 geolocation-api 中被定义,你也可以打印 error 对象:

移动端:

测试浏览器为:Chrome、Firefox、Safari.

Chrome

Firefox

Safari

逆地理编码

百度地图开放平台提供的 逆地理编码服务,使得用户可通过该功能,将位置坐标解析成对应的行政区划数据以及周边高权重地标地点分布情况,整体描述坐标所在的位置。

请求接口如下,具体请查看官方文档。

bash
http://api.map.baidu.com/reverse_geocoding/v3/?ak=您的ak&output=json&coordtype=wgs84ll&location=31.225696563611,121.49884033194 //GET请求
bash
http://api.map.baidu.com/reverse_geocoding/v3/?ak=您的ak&output=json&coordtype=wgs84ll&location=31.225696563611,121.49884033194 //GET请求

比如,我在 iPhone 上用 Safari 访问了该站点,数据显示如下:

  • 纬度(latitude):30.294593651777546
  • 经度(longitude):120.03974500181903

在浏览器中输入构造好的 URL,返回以下 JSON 内容:

成功定位到了我居住的街道(已打码),精准度很高。

总结

经过上一节的实践,对于市面上主流浏览器(Chrome、Firefox、Edge、Safari),得出以下结论:

  • 移动端(H5 页面)均支持 Geolocation
  • PC 端除 Edge 外,均不支持 Geolocation,且精度不高

B2D1 (包邦东)

Written by B2D1 (包邦东)