这是一个关于我如何解决一个使我自己无法解决的奇怪问题的故事。 展望未来,我会说我对最终的解决方案感到满意,并将应用程序推向了逻辑。 但是,要完全运行它,您需要更多的资源,所以我决定休息一下,问人们是否需要其他工具。 为了这个目的(也只是为了交谈),我在这里写。
关于我自己的两个词:我住在爱尔兰的都柏林,是一名程序员。 它并不完全是当场,这就是为什么在我的业余时间我看到各种各样的项目,主要是在桌子上。 尽管我读了很多年,但我还是第一次写《哈布雷》。
问题
相当早以前,当我不得不经常去陌生的地方工作时,我开始注意到,今天在任何卡上进行标准搜索都绝对不适用于驾驶员。 看:您在不熟悉的地方开车,并且汽油箭头为零。 你的举动? 如果那时候我不是一个人在车上,那么我告诉乘客:“好吧,我开车时在附近找一个加油站。” 因为如果您自己执行此操作,则需要执行以下操作:
- 停止
- 在地图应用程序中,在搜索中输入“汽油”(或单击某些卡片现在提供的快速按钮之一)
- 该应用程序进行搜索,并向您显示带有十二个加油站的巨大地图
- 您尝试找出最接近您的人,然后单击它以构建一条路线
我认为这是一场噩梦。 首先,您需要停止或至少等待交通信号灯。 因为卡片很复杂,图标很小。 其次,nifig卡不会告诉您最近的加油站是什么。 在这方面,谷歌是最糟糕的:即使是列表形式的结果,它也一直在推高距离最近的地方,而评分最高的(带照片的)/付费的(我不知道)。

好,第三个因素:我们正在前进。 发行卡的结果在某种程度上是相关的,但我们已经离它很远了。 您是否注意到,即使我们已经转移,即使Google铺设的路线也不会自动更新? 仅当导航已经启动时,它才会重建。
总的来说,问题很明显。 适用于行人及其需求和驾驶员的逻辑几乎没有。 我不在乎这个地方的等级以及那里的厨房类型-我需要在不分散道路注意力的情况下实时获取到最近的加油站,收费站,停车场,自动取款机等的路线。
主意
现在,让我们尝试确定理想的搜索方案。 准则如下:
- 互动简短明了,以免分散驾驶员的注意力
- 透明的基于距离的输出
- 实时更新
首先,用扫描代替标准的一次性搜索。 也就是说,动作链是这样的:
- 启动扫描
- 骑行,查看实时更新结果
- 当我喜欢某物时,我点击并铺了路线
您可以预先确定搜索条件-实际上,这是地点的类型和扫描半径。 此外,当汽车行驶时,该应用程序就像雷达一样工作。 很快就很清楚,首先,半径不是圆的,而是等值线的形式。 其次,它不是按距离而是按时间来构建,因为分钟比公里容易得多。
然后,我考虑了一种输出结果的方法。 更确切地说,我是否完全需要一张卡? 驾驶员用一只眼睛看着该应用程序,并用一根手指进行交互-他不需要地图,而是一个大文本和大按钮。 因此,我立即决定主屏幕将是一个列表,并且我可能会首先添加地图,然后查看是否应该离开它。
计划
知道我自己的拉伸项目的特殊性后,我决定花2个月的时间来完成自己的所有工作,最后我在3中遇到了。原则上,该应用程序非常简单:
- 一对屏幕上的客户端(搜索,列表和地图)
- 定期将其坐标,搜索半径和地点类型发送到服务器
- 服务器会及时建立等值线(顺便说一下,它的名称为英文-isochrone),在地方进行搜索并返回列表
听起来像是更轻的肺。 我已经有一些制图方面的经验(几年前,我做了一个房地产门户网站,在地图上进行了搜索),因此后端的堆栈就很清楚了:
- 将数据从OpenStreetMaps导入Elasticsearch
- OpenTripPlanner用于构建轮廓
我认为,在客户端上,决定使用Google的新框架-Flutter。 它是跨平台的,非常灵活,并且允许您使用最少的代码来构建功能完善的应用程序。 当然,这是原始的,尚不清楚生产什么,但是看起来非常适合原型制作。 必须澄清的是,在这一点上,我在android的本机开发方面有经验(我是团队负责人),因此可以决定面对敌人。 敌人并不那么恐怖。
实作
第一个原型应用程序很快就准备好了-Flutter具有较低的入门门槛和易于理解的类似redux的理念。 奇怪的是,接口的声明性描述以及热重启(React Native,您的位图)也令人愉悦。 通常,印象是Google占了先前尝试的大多数先天性疾病。 但是,据我了解,有些人可能不想使用它-有人不喜欢飞镖,数量有限的小部件,并且此处提供的“视觉调试”是非常原始的。
在后端,我做了以下工作:
- 交付了Nominatim,并使用其标准osm2pgsql实用程序将OpenStreetMaps数据摘录( 在此处获取 )上传到其数据库中。 为什么我转向小型但非常令人愉快的开放式地理编码器Photon 。 之前,我已经在几个项目中使用过它-它生成Elasticsearch索引,从那里的Nominatim数据库导入数据,并搜索该索引。 我喜欢它的速度和纯映射功能(例如,我尝试了Pelias,但我不太喜欢它)。 它的主要问题是旧版本的elastic,但在我的情况下,我不需要地理编码器本身的功能,只需要数据,因此在导入后,我将索引转移到安装了最新版本的Elastic上。 顺便说一句,为什么我选择Elasticsearch? 它非常快,并且具有按多边形查找坐标的功能。
- 垃圾填埋场(又名isochrone)最初为我生成了OpenTripPlanner 。 这是一个非常不错的开源路由计划器。 它的工作方式如下:提取相同的OpenStreetMaps提取并将其编译为大型路线图,该路线图作为一个单独的对象保存到磁盘。 服务器启动时,此图将加载到RAM中,并通过它搜索所有路由。 优点:快速上手,功能丰富(例如,从盒子中生成轮廓)并且速度快。 缺点:此速度取决于RAM的数量,文档非常令人讨厌。 只是可怕的文档。 越南的倒叙。
- 我在python上扔了一个小的api,它以秒为单位获取位置的类型和搜索半径,从OpenTripPlanner请求一个多边形,然后在Elasticsearch中搜索它。 它请求到每个找到的位置的路由(同样来自OpenTripPlanner),并获取其长度和时间。 之后,将收集的所有数据打包并返回。
我通过将设备的坐标偏移5米来更新结果。 该地图是静态的-我只是使用了静态Google地图的api(如您所见,这是公司唯一进入我们舒适的开放式资源世界的地方)。 第一个实现如下所示:




在玩完应用程序之后,我决定隐藏地图。 她很好地理解了搜索的目的是什么多边形,并且看起来很有趣-有趣地观察到这种章鱼如何实时改变形状。 但是,这种娱乐方式并不能帮助应用程序实现其功能,并且占据了屏幕的三分之一。
我还想添加一个箭头,指示每个结果的方向。 它像这样工作:
- 记住你以前的坐标
- 换档时,我们将从先前位置到当前位置的路线
- 我们选择路线的最后一段,并将每个结果与路线的第一段进行比较。 由于它们沿着相同的道路网格放置,因此它们之间的角度以99%的概率接近0或180。
这个非常简单的技巧极大地有助于我们了解我们是否已经朝这个地方前进,或者是否有必要转身。


此时,我对最终的应用程序感到非常满意,并决定尝试将其部署到多个国家/地区。 尽管如此,爱尔兰分别是一个非常小的国家,弹性指数和路线图很小。 为了进行测试,我决定连接邻国英国。 它的面积大约是它的4倍,并且道路网络更加密集(尤其是首都和大城市)。 然后出现了问题。
Elasticsearch预期可以很好地消化增加的索引,但是使用OpenTripPlanner会完全失败。 它是用Java编写的,并且如上所述,它生成道路图,以便在将其加载到RAM之后。 爱尔兰的图形为1 GB,英国的图形已经为5。当然,可以将其划分为国家,地区甚至地区,然后根据用户的坐标将其重定向到所需的图形。 但是,这使得不可能在区域之间放置路线,最重要的是,它并不能解决将所有这些图保留在内存中的需求。 最后,仅编译每个这样的对象就占用了很多资源,并且永远持续下去。 为了娱乐,我在计算机上启动了16位法国伯爵(Count of France)组件,等待了一天,然后取消了。
显然,在小任务中证明自己出色的技术根本不是为了扩展而设计的(至少没有利用我的资源)。 因此,它要么承认失败,要么爬行到另一种技术。 我休息了几天,开始研究世界上还有哪些其他开源解决方案。 原来基本上有两个:
如果第一个用Java编写并将路线图加载到RAM中,则OSRM-开源路由机-已经用加号编写,并将其(同样重要的)中间文件保留在磁盘上。 因此,对大容量RAM的需求被大而快速的磁盘的需求所取代。 这是更真实的。
终点线
经过两夜的挑选,所有服务器代码都转移到了新的解决方案中。 它确实有效,并且效果很好。 可以连接多个国家,甚至搜索速度也提高了。 一般原则是相同的:从OpenStreetMaps提取的中间文件针对“机器”配置文件进行了编译(配置文件是一组权重和图形边缘的说明,其中包括“徒步”,“自行车”等配置文件)。 然后将这些文件放入目录中,并且OSRM api已经从磁盘读取了它们。 顺便说一句,Api相当大-支持各种细微差别的轮廓和路线规划,甚至为地图生成了图块。 我决定更详细地讨论后者。
回到该应用程序并继续进行测试,我发现了另外两件事:
- 最上面的菜单不好,到达的太远了
- 一般地图绝对不是必需的,它只将我与Google联系在一起
- 结果卡无聊单调
他兴高采烈地扔出了一张Google地图(欢呼声,现在100%开放源代码和他的数据),简化了菜单,下移了。 开始思考如何处理卡片。 然后我在上面提到了非常合适的api切片。 它允许您为给定的坐标和缩放级别生成矢量图块。 结果以类型为application / x-protobuf的二进制Blob的形式发布-这是一种非常不便于操作的数据类型。 我将不赘述(我不得不一点点流汗),但简而言之,我的动作如下所示:
- 以多段线的形式取所构造路线的线到该点
- 折线-> GeoJSON
- 获取此形状的边界框
- 请求此边界框捕获的所有图块
- 将图块数据从二进制格式转换为GeoJSON
- 胶水瓷砖,通过边界框修剪,与路线合并,着色
- 生成的GeoJSON转换为位图
在操作过程中,会有细微的差别,例如,用彩色环缩进边界框或标记点(并使其半径在所有缩放级别上均保持不变)。 结果图片如下:


画龙点睛
当我为每个结果添加视觉路线时,列表开始以新的颜色闪烁。 另外,意识到默认情况下每张照片都骑在北方,我使它们相对于指南针旋转。 因此,除了视觉效果外,该芯片还具有功能-取代了方向箭头。 现在,您正在开车,您可以肯定地看到此结果或结果。
开发的第三个月到期了,已经有必要四舍五入了。 添加的越多,您就越想要,所以在某个时候,您只需要团结一致并放开项目即可。 我对界面进行了一些调整和绘制,为使操作更完整,我草绘了应用程序徽标:


和介绍页面:


最后是应用程序的最终版本:





总结
好,谢谢收看。 我希望这种意识流对某人会很有趣,甚至可能有用。 在此阶段,我认为该应用程序已准备就绪:它速度很快,没有特殊的错误,可以在世界上任何国家/地区使用。 顺便说一句,您可能已经注意到屏幕截图来自iPhone和Android,因为多亏了Flutter,该应用程序在两个平台上的工作方式完全相同。
但是,到目前为止,我已经决定冻结所有内容-换了工作,出现了新的担忧。 几个月后,我除尘,决定写一篇回顾展。 您的评论很有趣:您想要使用它,可以更改什么。
PS当然,关于应用程序的就绪性是胡说八道。 它已经准备好作为原型-如果您打算进行认真的生产,则需要制作脚本以与OpenStreetMaps同步数据,在设备动物园检查操作,本地化接口等。 节点和python上的相同后端将承受任何严重的负载。