你苦战通宵的时候,布里斯班的灯鱼早已划过珊瑚丛;
你赶现场招聘会的时候,蒙巴萨的小榭刚刚流出渔夫的掌心;
你写程序代码的时候,布拉格的列车正晃过金色的夕阳;
有人曾说:“人生至少有两次冲动,一次为了奋不顾生的爱情,还有一次就是为了说走就走的旅行”。
爱情往往可遇不可求,但是旅行却是可以说走就走。
又有人说:“我是不是走错频道,来到了文学专区”。没错,说的就是你。
我只想说一句,请别捉急,且听小羊娓娓道来。
国庆刚过,小羊积攒了 10 天的年假。
可以在人生的种种事务中抽离出来,趴在车窗外看沿途的风光,倾听内心的音乐。
小羊希望有一款专为我私人定制的小程序,在旅行路上陪我欢笑陪我闹。
它的功能不需要太多,但能读懂我的心声。
它的长相不求惊天动地,但能合我眼缘。
思来想去,小羊为自己开发了一款属于玩票性质的小程序。
我希望,我可以带着它游览大好河山、尝遍人间美食、留下旅行的美好回忆,于是设计了根据距离用户当前位置,由近及远获取风景、餐饮、住宿和商店等分类地点的功能。
我希望,当我迷失方向而彷徨无措的时候,它可以提供支持我前行的力量,于是设计了获取起点与目的地之间的行走路线、距离、耗时和车费等的路径规划功能。
3. 实时天气
我希望,当我需要选择在雨天出行,倾听下雨的声音时,它可以为我播报天气,于是设计获取当前位置的温度、天气、湿度、风力和风向等实时天气功能。
理清了需求,接下来的工作就是思考数据从哪里获取以及如何去设计数据表的字段。
这里,小羊调用高德地图 Web 服务 API,通过撰写简单的 JS 脚本实现地点数据的批量下载。
这一步需要注意的是对原始数据的处理:添加 geo 类型字段和将数据写入 csv 文件。
知晓云提供了专为地理位置操作相关的 geo 类型字段,为原始数据添加 geo 类型字段可以调用知晓云相关的地理位置 API,快速实现需求。
添加 geo 类型字段的大致思路就是:
request
.url(url)
.end(res => {
let list = res.data.objects
list.map(item => {
let location = item.location.split(',')
return Object.assign(item, {
location_geo: {
coordinates:[...location], // location_geo 是定义在数据表中数据类型为 geojson 的字段,数据格式为知晓云要求的 geojosn 格式
type: "Point"
}
})
})
})
具体实现可以参考小羊写的脚本。
知晓云从 v1.1.0 开始支持数据的导入导出功能,但是将 csv 文件导入数据表之前,还要对复杂类型数据进行数据清洗。
小羊调用 json2csv 库,用脚本写入 sites 文件的 geojson 数据格式为:
{""coordinates"":[""100.132336"",""25.659082""],""type"":""Point""}
而知晓云 geojson 要求的数据格式为:
{"coordinates":[100.19226532543676,25.901452850308967], "type":"Point"}
进行 geojson 数据清洗
"","" 替换为 ,
["" 替换为 [
""] 替换为 ]
此外,复杂数组数据格式,例如 photos 字段的数据,如果直接在数据表将 photos 声明为 array,会出现字段无法导入问题,所以这里建议将复杂数组的数据格式定义为 string,在小程序端进行格式转换。
[{""title"":[],""url"":""http://store.is.autonavi.com/showpic/4e7dc163381dd4bdfb06b3fa9f5d61c2""}]
okay,有了数据文件后,就可以将文件导入数据表。
当然,在导入表之前,结合实际需要,设计表字段。
这款小程序的表字段如下:
字段 | 类型 | 实例
---| --- | ---
id | id | 59cb1def09a8055626658340
name | string | 大理古城
type | string | 风景名胜;风景名胜;国家级景点
typecode | string | 110202
address | string | 一塔路 42 号
location | string | 100.165937,25.694973
location_geo | geo | {"coordinates":[100.16593960808058,25.694973636880942],"type":"Point"}
pname | string | 云南省
cityname | string | 大理白族自治州
tel | string | 0872-2504361
photos | string | '[{"title":"大理古城","url":"http://store.is.autonavi.com/showpic/386123df93c6d1ede6aec843e239ce8b"}]'
接下来,就需要在数据模块创建一张数据表,并把之前创建的 csv 文件导入进去。
如果有童鞋不太清楚创建数据表操作的话,可以参考一下小羊之前写的文章。
这款小程序设计了 3 个页面,地点列表展示、路径规划和实时天气。
这里重点介绍一下地点列表展示页的实现,简洁地讲一下路径规划页和实时天气页。
地点列表展示页主要涉及的是根据用户当前地理位置由近及远的获取地点数据及其距离的计算问题。
对于根据用户当前地理位置由近及远的获取地点数据,知晓云提供一个 withinRegion 接口可以较好的满足开发需求:
let
arr = curPoint.split(','), // 当前地理位置,'100.165937,25.694973'
point = new wx.BaaS.GeoPoint(+arr[0], +arr[1]), // 创建一个 geojson 类型 的点
Sites = new wx.BaaS.TableObject(tableID), // sites 数据表对应的 tableID
query = new wx.BaaS.Query(),
limit = 10,
offset = 0
// 获取 type 字段为风景名胜,距离当前地理位置 point 10 km 的数据,数据按由近及远排序
query
.contains('type', '风景名胜')
.withinRegion('location_geo', point, 100000, 0) // 设置查询条件
Sites
.setQuery(query)
.limit(limit)
.offset(offset)
.find()
.then(res => {
console.log('r', res)
})
值得注意的是,这个接口的调用和我们先前的工作密切相关,前面为原始数据添加一个 数据类型为 geojson 的location_geo
字段,因此我们得以进行符合条件的地点查询。
对于地理空间距离的计算而言,这款小程序面向本地生活服务,由于两点之间的距离不算太远,因此可以近似认为经线和纬线是垂直的。
因此,两空间坐标点之间的距离可近似为:sqrt(sm*sm + em*em)
南北方向 sm = R * 纬度差 * Math.PI / 180 ;
东西方向 me = R * 经度差 * Cos<当地纬度数 * Math.PI / 180>
具体代码实现如下:
// 两空间坐标点之间距离
const distanceSimplify = (opts) => {
let {
curPoint,
location,
} = opts
const R = 6367000.0 // 地球半径
let start = point2Json(curPoint),
end = point2Json(location)
let dx = start.longitude - end.longitude, // 经度差
dy = start.latitude - end.latitude, // 纬度差
avg = (start.latitude + end.latitude) / 2, // 平均纬度
lx = R * toRadians(dx) * Math.cos(toRadians(avg)), // 东西距离
ly = R * toRadians(dy) // 南北距离
return parseInt(Math.sqrt(lx * lx + ly * ly))
}
// 弧度转换
const toRadians = (deg) => {
return deg * Math.PI / 180
}
// 将坐标字符串转换为 json
const point2Json = (point) => {
let pointArr = point.split(',')
return {
longitude: parseFloat(pointArr[0]),
latitude: parseFloat(pointArr[1]),
}
}
当然,如果你想要更高的精确度,也可以直接引用 node-geo-distance 去计算空间坐标的距离。
至于这两个页面的实现,就是使用高德地图的小程序 SKD,里面分别提供路线规划和实时天气的相应接口,开发者只需要根据需求进行数据渲染。
const AMap = require('../../lib/amap-wx.js'),
AMapKey = '123456'
// 获取路线规划
let aMap = new AMap.AMapWX({key: AMapKey})
aMap.getDrivingRoute(configRoute)
// 获取实时天气
aMap.getWeather(configWeather)
笔触行至此处,不由发现作为开发者的小确幸,我们可以为自己的每一次躁动的需求立刻付诸行动并变成现实的应用。
你看到此处,相信我也并没有欺骗你,这真是一篇“技术散文”。
如果在远方的路上,我和正在阅读的你彼此有幸邂逅,我们不妨坐下来喝一杯水酒。此时,我只想说一句,所有的酒都不如你。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.