How to re-draw a route after the origin location changes using Azure Maps in Android application

268 views Asked by At

I am building an Android app that will render a map. Using the phone location and a specific destination, I need to display the route on the map, and re-draw the route when the user's phone is moving. Sorry for the long post, I am trying to make this as explicit as possible.

I have to use Azure Maps, and the user has to follow the original route, so I need to re-draw the route when the user is following it. Basically I just need to make the original route start from the phone location everytime the user moves.

I am getting the latitude and longitude of the phone every three seconds, and when the user picks a destination location, I am calling the Azure API for the route, which returns a JSON array that contains points(latitude and longitude) from origin to destination. I save these points in an ArrayList and draw the route using a LineString and a LineLayer. My problem is that I don't want to call the Azure Api for the route every three seconds or even everytime the phone moves, because the call is really slow, and it puts to much pressure on my backend server.

Considering that the JSON array is sorted from origin to destination, I tried to compute the absolute difference between the phone location and the points from the array, and save the point for which the difference is smaller than a threshold, because it's the closest point from the route to the phone actual location. Every three seconds I am computing this difference and save the point as the start from the new route, and re-draw the route. The code for the difference is this:

private void getStartIndices() {
        //r_points is the ArrayList with the points from the route
        if(r_points != null)
        {
            for (int i = 0; i < r_points.length() - 1; i++) {
                try {
                    
                    JSONObject point = r_points.getJSONObject(i);
                    
                    if( abs(mCurrentLocation.getLongitude() - point.getDouble("longitude")) < 0.00001 && abs(mCurrentLocation.getLatitude() - point.getDouble("latitude")) < 0.00001  )
                    {
                        Log.e(TAG,  i + " They both change");
                        returnable = i;
                    }
                    else{
                        if( abs(mCurrentLocation.getLongitude() - point.getDouble("longitude")) < 0.000004 )
                        {
                            Log.e(TAG,i + "The longitude is changing");
                            returnable = i;
                        }
                        else{
                            if(abs(mCurrentLocation.getLatitude() - point.getDouble("latitude")) < 0.000004  )
                            {
                                Log.e(TAG,i + " The latitude is changing");
                                returnable = i;
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }
    }

This works but not accurate enough and it has some really bad bugs that I can't solve.

My question is: Is there any better way to do this, I really need to make as few calls as possible and I also need to make this redraw as accurate as possible.

1

There are 1 answers

3
rbrundritt On BEST ANSWER

The standard way to accomplish this is to loop through each line segment (pair of points in the line) and calculate the nearest point on that segment from your point. Then as you go through, find which segment has the closest point. Here is an algorithm that does this: https://softwareengineering.stackexchange.com/questions/270655/efficient-method-for-finding-closest-point-to-a-line-segment-from-a-set-of-point

However, this algorithm is based on pixel coordinates. If you use longitude/latitude coordinates, there will be some inaccuracies since the latitude and longitude scales are different (they aren't a flat 2D coordinate system, but part of a spherical coordinate system). An easy way to handle this is to convert your positions to pixel coordinates using the built in mercatorPositionsToPixels method in the MapMath namespace of the Azure Maps Android SDK. This will convert the longitude/latitude positions into pixel coordinates base don the Mercator map projection, and will result in much higher accuracy calculations. The zoom level specifies the resolution. Set this to 22, and that should work always (unless you want to start getting into microscopic accuracy).

  /**
     * Converts an array of positions into an array of global Mercator pixel coordinates at a specified zoom level.
     * @param positions Array of positions.
     * @param zoom Zoom level.
     * @returns Array of global Mercator pixels.
     */
    public static Pixel[] mercatorPositionsToPixels(@NonNull Position[] positions, double zoom)

Convert all the points of your line using this method, and store those since it sounds like your base line isn't changing. Then on each GPS ping, convert the GPS position to a Mercator pixel and use that nearest point on line algorithm to find the nearest point out of all the line segments. This will give you a point that could be between other points, but is on the path which is likely much more inline with what you are looking for.

In terms of performance, unless your line has millions of points in it, these calculations will run really fast. I used the same algorithm in JavaScript at a much higher frequency and it runs fine.

Update: the calculated coordinate will be in pixel units, you will need to convert it back to a position using the mercatorPixelsToPositions method.