我们将在线地图连接到智能手机上的导航器。 第3部分-天桥涡轮增压

我们将先前创建的脚本转换为API,以通过智能手机的导航应用程序从OverpassTurbo.eu网站查看交互式地图。


内容:


1- 简介。 标准栅格图
2- 继续。 为矢量地图编写一个简单的光栅化器
3-特例。 我们连接了OverpassTurbo卡


什么是立交桥?


这样啊 有一个地图数据库,例如OpenStreetMaps。 它包含了所有内容:海洋,各大洲的轮廓,山脉,森林,道路,建筑物,游乐场,甚至减速带。 每个对象都有一个名称,坐标和属性。 例如,通过道路-涂层材料,通过建筑物-楼层数等。


所以在这里。 当今Internet上显示的大多数卡都是基于此特定数据库生成的。 但是,如果所有这些现成的卡都不适合我们怎么办? 您可以自己做! 好吧,或者至少补充现有的,这要容易得多。


这就是OverpassTurbo.eu所做的。 这是一个在线IDE。 使用它,您可以查询OSM数据库。 我们单击开始按钮,请求转到数据库,一段时间后数据返回给我们。 OverpassTurbo以位于背景层顶部的矢量标记和线条的形式可视化此数据-来自OpenSteerMap.org的地图。


作为您可以对OverpassTurbo进行操作的一个示例,我想向您展示我最喜欢的脚本。 它由用户以昵称Erelen编写。 因此:此脚本在地图上绘制了各种饮用水来源及其名称。 我认为这非常有用而且非常清楚。 要查看此脚本的工作原理,只需点击链接,然后单击开始 。 (如果站点提供了错误,请通过VPN并重试)


https://overpass-turbo.eu/s/z95



或者这是我已经为满足自己需要而编写的脚本。 有了它,您可以轻松地在陌生的公园中找到良好的慢跑路线。 为此,脚本突出显示了整洁的砾石路:按照我的口味,沿着这些路最方便。 沥青标记为白色。 黑色的普通土路。 但是所有带有“难以到达”或“涂层质量差”标签的路径都将用不显眼的虚线标记:为了减少绊倒的次数,我尽量避免使用它们。 通常,制作地图的目的是使您可以沿最引人注目的直线简单地获取路线。 最终,这条路线是成功的。


http://overpass-turbo.eu/s/KXU



实际上,使用此工具可以为地图添加所需的任何数据。 而且,我注意到这非常非常令人兴奋。 但是这篇文章不是关于这个的。 如果您对此主题感兴趣,可以在此处熟悉天桥的基础知识。


但是在继续编写代码之前,让我们首先看一下我们应该得到的最终结果。


用户说明:如何使用我们的API


这样啊 假设您已经有一个适用于OverpassTurbo的现成脚本,您希望在智能手机上查看其结果。 而不是在浏览器中,而是在导航器中。 为此,请将您的脚本转换为以下格式。


[bbox:{{bbox}}]; ( //     node[amenity=waste_basket]; ); out;>;out skel qt; 

特别是,我们对第一行感兴趣:我们的应用程序将替换它。


之后,单击“ 共享”按钮。 确保取消选中显示地图启用状态框。



之后,复制链接。 例如,我们假设您复制的链接如下所示:


http://overpass-turbo.eu/s/KEy


现在看看我们的API


https://anygis.herokuapp.com/mapshoter/overpass/{x}/{y}/{z}/{crossZoom}?script={script}


使用{x},{y}{z},一切似乎都很清楚:这些是所需图块的坐标。


您需要替换脚本ID来代替{script} 。 在我们的示例中, s / Key


但是{crossZoom}是什么? 假设您有15个。然后,如果您请求缩放比例小于15的图块,服务器将不会对OverpassTurbo发出缓慢的请求,而只是将您重定向到具有空OpenStreetMaps背景层的地图(该图层将立即加载)。 需要这种方法,以便在必要时可以将地图移开,快速滚动到感兴趣的地方,放大并等待。 等到OverpassTurbo生成包含结果的地图。


我希望基本原则是明确的。 现在,查看我们的请求的完整URL。 我认为现在使用我们的API对您来说并不困难:只需将S / KEy替换脚本的ID


https://anygis.herokuapp.com/mapshoter/overpass/{x}/{y}/{z}/15?script=s/KEy


同时,我们将看到如何实现这样的应用程序。


方案3-使用URL和浏览器缓存进行搜索


这样啊 让我们从router.js文件开始。 让我们的方法接受crossZoom脚本参数。 然后我们将它们传递给工人。 我们还添加了一个选项,如果请求的缩放比例过低,该选项将中断脚本并将用户重定向到另一个站点。


 const express = require( 'express' ) const PORT = process.env.PORT || 5000 const app = express() app.listen( PORT, () => { console.log( '    ', PORT ) }) const { StaticPool } = require( 'node-worker-threads-pool' ) const worker = "./worker.js" const workersPool = new StaticPool({ size: 3, task: worker, workerData: "no" }) //     app.get( '/:x/:y/:z/:crossZoom', async ( req, res, next ) => { const x = req.params.x const y = req.params.y const z = req.params.z const crossZoom = req.params.crossZoom const scriptName = req.query.script //     if ( Number( z ) < Number( crossZoom ) ) { res.redirect( `http://tile.openstreetmap.org/${z}/${x}/${y}.png` ) } //      const screenshot = await workersPool.exec( { x, y, z, scriptName } ) const imageBuffer = Buffer.from( screenshot, 'base64' ) res.writeHead( 200, { 'Content-Type': 'image/png', 'Content-Length': imageBuffer.length }) res.end( imageBuffer ) }) 

worker.js文件没有太大变化。 只是进一步转发新变量。


 const { parentPort, workerData } = require( 'worker_threads' ); const puppeteer = require( 'puppeteer' ) const mapshoter = require( './mapshoter' ) var browser = "empty" parentPort.on( "message", ( params ) => { doMyAsyncCode( params ) .then( ( result ) => { parentPort.postMessage( result ) }) }) async function doMyAsyncCode( params ) { await prepareEnviroment() //   const screenshot = await mapshoter.makeTile( params.x, params.y, params.z, params.scriptName, browser ) return screenshot } async function prepareEnviroment( ) { if ( browser === "empty" ) { const herokuDeploymentParams = {'args' : ['--no-sandbox', '--disable-setuid-sandbox']} browser = await puppeteer.launch( herokuDeploymentParams ) } } 

现在让我们看一下mapshoter.js 。 首先,看一下代码:


 const puppeteer = require( 'puppeteer' ) const geoTools = require( './geoTools' ) async function makeTile( x, y, z, scriptName, browserLink ) { //      const runButtonSelector = '#navs > div > div.buttons > div:nth-child(1) > a:nth-child(1)' const codeEditorSelector = '#editor > div.CodeMirror.CodeMirror-wrap > div:nth-child(1) > textarea' //          const coordinates = geoTools.getAllCoordinates( x, y, z ) const bBox = `[bbox:${coordinates.bBox.latMin}, ${coordinates.bBox.lonMin}, ${coordinates.bBox.latMax}, ${coordinates.bBox.lonMax}];` const centerCoordinates = `${coordinates.center.lat};${coordinates.center.lon};${z}` //      const browser = await browserLink const page = await browser.newPage() await page.setViewport( { width: 850, height: 450 } ) //  ,   : //      await page.waitFor( randomInt( 0, 500 ) ) //        URL var pageUrl = `http://overpass-turbo.eu/?C=${centerCoordinates}` await page.goto( pageUrl, { waitUntil: 'networkidle2', timeout: 10000 } ) //       URL pageUrl = 'http://overpass-turbo.eu/' + scriptName await page.goto( pageUrl, { waitUntil: 'networkidle0', timeout: 20000 } ) //      await page.focus( codeEditorSelector ) //        , //     await page.keyboard.type( bBox + ' //' ) // ,  -IDE   await page.waitFor( 100 ) //     - await page.click( runButtonSelector ) // ,      . //     . await page.waitForFunction(() => !document.querySelector('body > div.modal > div > ul > li:nth-child(1)'), {polling: 'mutation'}); await page.waitFor( 1000 ) //    const cropOptions = { fullPage: false, clip: { x: 489, y: 123, width: 256, height: 256 } } const screenshot = await page.screenshot( cropOptions ) //   await page.close() return screenshot } //       function randomInt( low, high ) { return Math.floor( Math.random() * ( high - low ) + low ) } module.exports.makeTile = makeTile 

首先,出于多样性考虑,在此脚本中,我们将使用普通的元素选择器(不是XPath )。 上一篇文章中介绍了如何找到它们。


接下来我们得到坐标。 仅这次,除了中心坐标外,还需要平铺边框( bBox )的坐标。


接下来,启动浏览器。 这里的一切都很典型。 但是在继续加载页面之前,让脚本等待0到500毫秒的随机时间。 这样我们就不会同时收到太多相同的请求,因此我们不会被禁止。


之后,我们转到添加了磁贴中心坐标的URL所在的站点。 结果,期望的位置在地图的中心。


之后,转到另一个URL。 这次我们脚本的ID 。 结果,我们的脚本将出现在代码编辑器的文本中。


(请注意,如果在“ 共享”菜单中复制脚本的URL时,我们不会取消选中“ 保存地图的状态”复选框,则地图会移动。我们根本不需要它)


现在,我将对这个问题做出合理的回答:为什么我们要两次访问该URL,也就是说,我们在加载该网站上花费了两次? 我回答。 因为,首先,我找不到如何在一个URL请求中结合脚本加载和转换到指定坐标的方法。 其次,由于某种原因,Puppeteer打印文本的速度非常慢,并且可以使用该站点上的界面元素。 一分半钟即可打印! 因此,就像在上一篇文章中所做的那样,将坐标插入搜索字段中,然后单击缩放按钮的想法被决定拒绝。 结果,双击链接比完成所有操作要快得多。 也许这是一个错误,迟早会得到解决,但是现在我们正在使用它。


las,您将无法完全离开文本输入。 我们将不得不替换代码编辑器窗口中的第一行。 目前,她报告说有必要从数据库下载当前屏幕上整个领土的信息。


 [bbox:{{bbox}}]; 

我们将其替换为图块边框的坐标。 这是为了避免浪费太多时间从数据库下载。 因此,脚本在第一行中显示的内容如下所示:


 [bbox:55.6279, 37.5622, 55.6341, 37.5732]; // 

为了不必删除原始行(多次为此缓慢地按Delete键 ),我们只需对其进行注释即可。 因此,我们将尽可能减少输入文本所花费的时间以及从数据库中加载的时间。 结果,第一行将如下所示:


 [bbox:55.6279, 37.5622, 55.6341, 37.5732]; //[bbox:{{bbox}}]; 

之后,我们的脚本必须单击“ 开始”按钮,稍等片刻,获取地图的屏幕截图并将其发送给用户。 所有:任务已完成!


如果要查看生成的脚本的示例,可以单击此链接


结论


好吧,由于不难假设,此版本的脚本将比以前的版本运行得更慢。 毕竟,现在该网站将时间花费在第三方数据库的请求上。 就其本身而言,它并不能太快地工作。 但是,这种方法非常容易(尽管很慢)来获得唯一的,定制的卡片。 而且,基于最新数据。 有时这可能非常有用。 因此,值得记住这种方法。


仅此而已。 为了以防万一,我提醒您,我的AnyGIS网站上有一个针对轨迹,OsmAnd和GuruMaps导航器的现成预置存档。 有栅格地图和“栅格化”矢量地图,可以使用这些文章中描述的应用程序进行查看。 快来使用。

Source: https://habr.com/ru/post/zh-CN461073/


All Articles