Inaccurate Google Maps Elevation Service response when splitting a too large path

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-01 00:38:01

问题


This is a bit of a question with some level of detail to it, so let me first explain the situation, then my implementation and last the question so you understand best.

As of April 4 an update is added and the issues are narrowed down to one pending issue, see the bottom of this question for the up to date info.

TLDR;

I have a long route returned from Google Maps Directions API and want an Elevation chart for that route. Too bad it doesn't work because it's requested via GET and the URL maximum length is 2.048 chars which get exceeded. I splitted the requests; guaranteed the correct processing order using Promises; but Evelation data isn't always complete for full route, isn't always displayed in the correct order, doesn't always follow the given path and inter elevation location spans over several km's sometimes.

Introduction;

Trying to create an elevation chart for a Google Maps DirectionsService response I'm facing an issue with too long routes (this doesn't seem to be related to distance, rather than number of LatLngs per overview_path). This is caused by the fact the ElevationService is requested via GET and a maximum length of an URL is 2048 chars. This problem is described on SO here as well.

Implementation;

I figured I would be smarter than Google (not really, but at least trying to find a way to work around it), to split the path returned by the DirectionsService (overview_path property) into batches and concatenate the results (elevations returned by the ElevationService method getElevationsAlongPath).

  • To get the best level of detail I query the ElevationService with 512 samples per batch;
  • and because the ElevationService spreads the samples over the length of the path I set up a maximum number of LatLng per batch and check how many batches are required to process the full path (totalBatches = overview_path.length / maxBatchSize);
  • and finally get an even spread for my directions result in an attempt to get an equal level of detail for the complete route (batchSize = Math.ceil(overview_path.length / totalBatches)).

While the ElevationService work asynchronously I make sure the requests are all processed in the correct order with help of other SO-users first using setTimout and now working with Promises.

My code

var maxBatchSize = 200;
var currentBatch = 0;
var promise = Promise.resolve();
var totalElevationBatches = Math.ceil(directions.routes[0].overview_path.length / maxBatchSize);
var batchSize =  Math.ceil(directions.routes[0].overview_path.length / totalElevationBatches);

while(currentBatch < totalElevationBatches) {
    promise = addToChain(promise, currentBatch, batchSize);
    currentBatch++;
}

promise.then(function() {
    drawRouteElevationChart(); // this uses the routeElevations to draw an AreaChart
});

function getRouteElevationChartDataBatchPromise(batch, batchSize) {
    return new Promise(function(resolve, reject) {
        var elevator = new google.maps.ElevationService();
        var thisBatchPath = [];

        for (var j = batch * batchSize; j < batch * batchSize + batchSize; j++) {
            if (j < directions.routes[0].overview_path.length) {
                thisBatchPath.push(directions.routes[0].overview_path[j]);
            } else {
                break;
            }
        }

        elevator.getElevationAlongPath({
            path: thisBatchPath,
            samples: 512
        }, function (elevations, status) {
            if (status != google.maps.ElevationStatus.OK) {
                if(status == google.maps.ElevationStatus.OVER_QUERY_LIMIT) {
                    console.log('Over query limit, retrying in 250ms');

                    resolve(setTimeout(function() {
                        getRouteElevationChartDataBatchPromise(batch, batchSize);

                    }, 250));
                } else {
                    reject(status);
                }
            } else {
                routeElevations = routeElevations.concat(elevations);
                resolve();
            }
        });
    });
}

function addToChain(chain, batch, batchSize){
    return chain.then(function(){
        console.log('Promise add to chain for batch: ' + batch);
        return getRouteElevationChartDataBatchPromise(batch, batchSize);
    });
}

Side note;

I'm also batching the DirectionService's request to address the 8 waypoint limitation the service has but I can confirm this is not the issue since I'm also facing the issue with 8 or fewer waypoints.

Problem;

The problems I'm facing are:

  • Elevation data is not always following the full path of route, meaning the last elevation point in the chart is (far) from the end of the route;
  • Elevation data sometimes gets displayed in random order as if it seems the promises were still not waiting for the next task to execute;
  • Elevation data doensn't always follow the given LatLng's from the overview_path provided in a given batch (see screenshot);
  • Inter elevation distance data is a lot. Sometimes spans multiple km's while requesting for 512 samples for an evenly matched batch size with a maximum of 200 LatLngs per batch.

I figured batching the ElevationService using Promises (and before timing with setTimtout) would solve all my problems but the only problem I solved is not exceeding the 2.048 char request URL and facing the above described new issues.

Help is really appreciated

Also I would like to put a 250 rep. bounty on this question right ahead but that's impossible at this moment. So please feel free to reply as I can later add the bounty and award it to the answer that solves the issues described. A 250 rep. bounty has been awarded to show my appreciation for you to point me in the right direction.

Thanks for reading and replying!

Updated at April 4 leaving 1 pending issue (for as far as I can tell at the moment)

Problem with elevations in random order tackled down

I've been able to tackle some of the problems when I was noticing inconsistent behavior in the directions results. This was caused for an obvious reason: the asynchronous calls weren't "Promised" to be scheduled so some of the times the order was correct, most of the times it wasn't. I didn't noticed this at first because the markers were displayed correctly (cached).

Problem with inter elevation distance tackled down

The div displaying the elevation data was only a 300px wide and containing many datapoints. By such a small width I was simply unable to hover over enough points causing to trigger elevation points which lie further apart from each other.

Problem with elevation data not showing along the route

Somehow somewhere down the line I've also solved this issue but I'm not sure if the bigger width or "Promising" the directions order has solved this.

Pending issue: elevation data is not always complete

The only remaining issue is that elevation data is not always covering the full path. I believe this is because an error in the Promising logic because logging some messages in the console tells me the elevation chart is drawn at a point where not all Promise-then's have completed and I think this is caused by refiring a batched call when an Over Query Limit error is returned by the Google Maps API.

How can I refire the same chain when an Over Query Limit error is returned? I've tried not to resolve the same function again, but just fire the setTimeout(...), but then the Promise doesn't seem to resolve the refired batch at the moment it is no longer getting an Over Query Limit. Currently this is how I've set it up (for both directions and elevation):

function getRouteElevationChartDataBatchPromise(batch, batchSize) {
    return new Promise(function(resolve, reject) {
        var elevator = new google.maps.ElevationService();
        var thisBatchPath = [];

        for (var j = batch * batchSize; j < batch * batchSize + batchSize; j++) {
            if (j < directions.routes[0].overview_path.length) {
                thisBatchPath.push(directions.routes[0].overview_path[j]);
            } else {
                break;
            }
        }

        elevator.getElevationAlongPath({
            path: thisBatchPath,
            samples: 512
        }, function (elevations, status) {
            if (status != google.maps.ElevationStatus.OK) {
                if(status == google.maps.ElevationStatus.OVER_QUERY_LIMIT) {
                    console.log('ElevationService: Over Query Limit, retrying in 200ms');

                    resolve(setTimeout(function() {
                        getRouteElevationChartDataBatchPromise(batch, batchSize);

                    }, 200));
                } else {
                    reject(status);
                }
            } else {
                console.log('Elevations Count: ' + elevations.length);
                routeElevations = routeElevations.concat(elevations);
                resolve();
            }
        });
    });
}

回答1:


Concerns raised in the implementation part, side note and problems enumerated are all covered in Google Maps Elevation API. The complete documentation provides a simple interface to query locations on the earth for elevation data and will address all your encountered problems like elevation requests, parameter usage, specifying locations, paths and elevation responses.

For concern discussed in your Introduction, Google Maps Elevation API has standard and premium usage limits. These limits are enforced to prevent abuse of the Google Maps Elevation API. Google Maps Elevation API Usage Limits provides you details regarding usage limits and option to increase your quota.

Additional notes from the documentation which may address your concerns:

  1. Note that elevation data becomes more coarse when multiple points are passed. To obtain the most accurate elevation value for a point, it should be queried independently.
  2. In those cases where Google does not possess exact elevation measurements at the precise location you request, the service will interpolate and return an averaged value using the four nearest locations.
  3. As with positional requests, the path parameter specifies a set of latitude and longitude values. Unlike a positional request, however, the path specifies an ordered set of vertices. Rather than return elevation data at the vertices, path requests are sampled along the length of the path, where each sample is equidistant from each other.
  4. The Google Maps Elevation API returns data for single point queries of the highest accuracy possible. Batch queries involving multiple locations may return data with less accuracy.



回答2:


Mmmh, how many points do you have to handle. Can you publish the path, so maybe some other can test it in their own apps. Did you try to reduce the path points with Douglas-Peuker or comparable methods. Did you try other apps like the free "Routeconverter" (which works with HGT) to see if you get better results. Do you need the elevation points direct/on the fly? Would using other free elevation services be an option. Perhaps you have to read the elevation points back to your route points, so that you can sort out unwanted points.

Only some thinking, in bad English - I'm afraid. Good luck, Reinhard




回答3:


The last remaining issue has also been solved with the help of this SO question: How to re-run a javascript promise when failed?. So if jfriend00 replies to this question I can award the bounty to him, since that's the trick that helped me out in the end.

To be sure the function resolves at status OK, retries at OVER_QUERY_LIMIT and reject at any other status I had to put the Promise logic within a function and call that function, like so:

function getRouteElevationChartDataBatchPromise(batch, batchSize) {
    return new Promise(function(resolve, reject) {
        function run(batch, batchSize) {
            var elevator = new google.maps.ElevationService();
            var thisBatchPath = [];

            for (var j = batch * batchSize; j < batch * batchSize + batchSize; j++) {
                if (j < directions.routes[0].overview_path.length) {
                    thisBatchPath.push(directions.routes[0].overview_path[j]);
                } else {
                    break;
                }
            }

            elevator.getElevationAlongPath({
                path: thisBatchPath,
                samples: 512
            }, function (elevations, status) {
                if(status == google.maps.ElevationStatus.OK) {
                    routeElevations = routeElevations.concat(elevations);
                    resolve();
                } else if (status == google.maps.ElevationStatus.OVER_QUERY_LIMIT) {                        
                    setTimeout(function () {
                        run(batch, batchSize);
                    }, 200);
                } else {
                    reject(status);
                }
            });
        }

        run(batch, batchSize);
    });
}


来源:https://stackoverflow.com/questions/36266442/inaccurate-google-maps-elevation-service-response-when-splitting-a-too-large-pat

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!