📜 ⬆️ ⬇️

We connect online maps to the navigator on the smartphone. Part 3 - OverpassTurbo

We turn the previously created script into an API for viewing an interactive map from the OverpassTurbo.eu website via the smartphone’s navigation application.


Content:


1 - Introduction. Standard raster maps
2 - Continued. Writing a simple rasterizer for vector maps
3 - A special case. We connect the OverpassTurbo card


What is OverpassTurbo?


So. There is a map database such as OpenStreetMaps. It contains everything: the seas, the contours of the continents, mountains, forests, roads, buildings, playgrounds and even speed bumps. Each object has a name, coordinates and properties. For example, by the road - the coating material, by the building - the number of floors and so on.


So here. Most of the cards that are presented on the Internet today are generated on the basis of this particular database. But what if all these ready-made cards do not suit us? You can make your own! Well, or at least supplement the existing one, which is much easier.


This is what OverpassTurbo.eu is doing. It is an online IDE. Using it, you can query the OSM database. We click on the Start button, the request goes to the database, and after a while the data returns to us. OverpassTurbo visualizes this data in the form of vector markers and lines located on top of the background layer - a map from OpenSteerMap.org .


As an example of what you can do with OverpassTurbo, I want to show you the script I like the most. It was written by a user under the nickname Erelen. So: this script draws on the map various sources of drinking water and their name. In my opinion, it is very useful and very clear. To see how this script works, just follow the link and click Start . (If the site gives an error, then go through the VPN and try again)


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



Or here is a script that I already did for my own needs. With it, you can easily find good jogging routes in unfamiliar parks. To do this, the script highlights bright orange manicured gravel paths: these are, for my taste, the most convenient way to run. Asphalt is marked in white. Ordinary dirt paths in black. But all the paths with the tag “difficult to reach” or “poor quality of coverage” will be marked with an inconspicuous dashed line: in order to stumble less often, I try to avoid them. In general, the map is made so that you can simply get directions along the most conspicuous and bright lines. And so that in the end this route is successful.


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



In fact, with this tool you can supplement the map with any data you like. And, I note that this is very, very quite exciting. But this article is not about that. If you are interested in this topic, you can familiarize yourself with the basics of Overpass here .


But before moving on to the code, let's first take a look at the final result that we should get.


Instructions for users: how to use our API


So. Suppose you have a ready-made script for OverpassTurbo, the results of which you want to see on your smartphone. And not in the browser, but in the navigator. To do this, bring your script to the following format.


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

In particular, we are interested in the first line: our application will replace it.


After that, click on the Share button. Be sure to uncheck the Enable state of the displayed map box.



After that, copy the link. For example, we assume that your copied link looks like this:


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


Now look at our API


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


With {x}, {y} and {z}, everything seems to be clear: these are the coordinates of the desired tile.


In place of {script} you need to substitute the ID of your script. In our example, s / Key .


But what is {crossZoom} ? Suppose you have 15. Then if you request a tile for a zoom of less than 15, then the server will not make a slow request to OverpassTurbo, but simply redirect you to the OpenStreetMaps card (which will load almost instantly). This approach is needed so that, if necessary, you can move the map away, quickly scroll to the place of interest, zoom in and wait. Wait until OverpassTurbo generates a map with the results.


I hope the basic principle is clear. Now look at the completed URL for our request. I think now using our API will not be difficult for you: just replace s / KEy with the ID of your script.


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


And we, meanwhile, will see how to implement such an application.


Scenario 3 - Search using the URL and browser cache


So. Let's start with the router.js file. Let's make our method accept crossZoom and script parameters. And then we pass them on to the worker. We also add an option that will interrupt the script and redirect the user to another site if the requested zoom is too low.


 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 ) }) 

The worker.js file has not changed much. Just forward the new variables further.


 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 ) } } 

Now let's deal with mapshoter.js . First, look at the code:


 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 

To begin with, in this script, for the sake of variety, we will work with ordinary element selectors (which are not XPath ). How to find them was described in a previous article .


Next we get the coordinates. Only this time, in addition to the center coordinates, the coordinates of the tile borders ( bBox ) are also needed.


Next, launch the browser. Everything is typical here. But before moving on to loading the page, let the script wait a random period of time from 0 to 500 ms. So that we don’t get too many identical requests from us at the same time and we are not banned.


After that, we go to the site at the URL to which the coordinates of the center of the tile are added. As a result, the desired place is in the center of the map.


After that, go to another URL. This time with the ID of our script. As a result, our script will appear in the text of the code editor.


(Please note that if in the Share menu when copying the URL for our script we wouldn’t uncheck the Save map state check box, the map would shift. And we don’t need this at all)


And now I’ll answer a reasonable question: why do we go to the URL twice, that is, we spend twice on downloading this site? I answer. Because, firstly, I couldn’t find how to combine script loading and transition to the specified coordinates in one URL request. Secondly, because for some reason Puppeteer prints text extremely slowly and works with the interface elements on this site. One and a half minutes can print! So the idea to insert the coordinates in the search field, and then click on the zoom buttons, as we did in the previous article, it was decided to refuse. As a result, double clicking on the link turned out faster than doing all this. Perhaps this is a bug and it will be fixed sooner or later, but for now we are working with what is.


Alas, you won’t be able to completely leave the text entry. We will have to replace the first line in the code editor window. At the moment, she reports that it is necessary to download information from the database for the entire territory that is currently on the screen.


 [bbox:{{bbox}}]; 

We will replace it with the coordinates of the borders of the tile. This is so as not to waste time downloading from the database too much. So the script prints in the first line something like this text:


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

And so that you don’t have to erase the original line (by clicking Delete many times), we just comment it out. Thus, we will reduce as much as possible the time spent on entering text and the loading time from the database. As a result, the first line will look like this:


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

After that, our script needs to click on the Start button, wait a bit, take a screenshot of the map and send it to the user. And all: the task is completed!


If you want to look at an example of how the resulting script works, you can follow this link .


Conclusion


Well, as it is not difficult to assume, this version of the script will work even slower than the previous ones. After all, now the site spends time on a request from a third-party database. And by itself, it does not work too fast. However, this method makes it extremely easy (albeit slow) to get a unique, customized card. And, moreover, based on the latest data. And this, at times, can be very useful. So it’s worth having this method in mind.


And that’s all. Just in case, I remind you that on my AnyGIS website there is an archive of ready-made presets for the Locus, OsmAnd and GuruMaps navigators. There are both raster maps and vector maps, for viewing which the application described in these articles is used. Come and use.


')

Source: https://habr.com/ru/post/461073/


All Articles