1

I want to display several polylines on a GoogleMap and some of them could overlap like in the picture below. (It is possible they don't overlap perfectly).

enter image description here

My goal is to obtain a list of polylines that go over the same roads as the initial set of polylines but with the constraint that they do not overlap.

For the previous case, I could end up with this:

enter image description here

Some ideas on how I can achieve this?

daniyelp
  • 915
  • 1
  • 11
  • 26
  • 1
    related question: [How to find the overlap of polylines in order to draw the common segment as shaded on google maps](https://stackoverflow.com/questions/32230611/how-to-find-the-overlap-of-polylines-in-order-to-draw-the-common-segment-as-shad) – geocodezip Jul 31 '21 at 22:05

1 Answers1

1

Easiest way (in case you have not so many polylines) is to use Google Maps Roads API part Snap to Road which

returns the best-fit road geometry for a given set of GPS coordinates. This service takes up to 100 GPS points collected along a route, and returns a similar set of data with the points snapped to the most likely roads the vehicle was traveling along.

like in this example:

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    private GoogleMap mGoogleMap;
    private MapFragment mapFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mapFragment = (MapFragment) getFragmentManager()
                .findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mGoogleMap = googleMap;

        List<LatLng> sourcePoints = new ArrayList<>();
        sourcePoints.add(new LatLng(<COORDINATES_OF_POLYLINE>));
        ...
        sourcePoints.add(new LatLng(<COORDINATES_OF_POLYLINE>));

        PolylineOptions polyLineOptions = new PolylineOptions();
        polyLineOptions.addAll(sourcePoints);
        polyLineOptions.width(5);
        polyLineOptions.color(Color.BLUE);
        mGoogleMap.addPolyline(polyLineOptions);

        mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(sourcePoints.get(0), 15));

        List<LatLng> snappedPoints = new ArrayList<>();
        new GetSnappedPointsAsyncTask().execute(sourcePoints, null, snappedPoints);
    }


    private String buildRequestUrl(List<LatLng> trackPoints) {
        StringBuilder url = new StringBuilder();
        url.append("https://roads.googleapis.com/v1/snapToRoads?path=");

        for (LatLng trackPoint : trackPoints) {
            url.append(String.format("%8.5f", trackPoint.latitude));
            url.append(",");
            url.append(String.format("%8.5f", trackPoint.longitude));
            url.append("|");
        }
        url.delete(url.length() - 1, url.length());
        url.append("&interpolate=true");
        url.append(String.format("&key=%s", <your_Google_Maps_API_key>);

        return url.toString();
    }


    private class GetSnappedPointsAsyncTask extends AsyncTask<List<LatLng>, Void, List<LatLng>> {

        protected void onPreExecute() {
            super.onPreExecute();
        }

        protected List<LatLng> doInBackground(List<LatLng>... params) {

            List<LatLng> snappedPoints = new ArrayList<>();

            HttpURLConnection connection = null;
            BufferedReader reader = null;

            try {
                URL url = new URL(buildRequestUrl(params[0]));
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.connect();

                InputStream stream = connection.getInputStream();

                reader = new BufferedReader(new InputStreamReader(stream));
                StringBuilder jsonStringBuilder = new StringBuilder();

                StringBuffer buffer = new StringBuffer();
                String line = "";

                while ((line = reader.readLine()) != null) {
                    buffer.append(line+"\n");
                    jsonStringBuilder.append(line);
                    jsonStringBuilder.append("\n");
                }

                JSONObject jsonObject = new JSONObject(jsonStringBuilder.toString());
                JSONArray snappedPointsArr = jsonObject.getJSONArray("snappedPoints");

                for (int i = 0; i < snappedPointsArr.length(); i++) {
                    JSONObject snappedPointLocation = ((JSONObject) (snappedPointsArr.get(i))).getJSONObject("location");
                    double lattitude = snappedPointLocation.getDouble("latitude");
                    double longitude = snappedPointLocation.getDouble("longitude");
                    snappedPoints.add(new LatLng(lattitude, longitude));
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (JSONException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            return snappedPoints;
        }

        @Override
        protected void onPostExecute(List<LatLng> result) {
            super.onPostExecute(result);

            PolylineOptions polyLineOptions = new PolylineOptions();
            polyLineOptions.addAll(result);
            polyLineOptions.width(5);
            polyLineOptions.color(Color.RED);
            mGoogleMap.addPolyline(polyLineOptions);

            LatLngBounds.Builder builder = new LatLngBounds.Builder();
            builder.include(result.get(0));
            builder.include(result.get(result.size()-1));
            LatLngBounds bounds = builder.build();
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 10));

        }
    }

}

(for this you need to add Google Maps Roads API support for your project in Google Cloud Console)

After you process all of the "initial" polylines with Snap To Road all of them should overlaps perfectly and you can use Z-Index property of Polyline to manage Z-order of polylines.

Other way is to detect overlapping of polylines "manually". In that case you should:

  1. decide how to determine "main" polyline (longest or having largest amount of points or something else) that should be shown on top of others;
  2. for each point of "non-main" polyline check is it on "main" polyline or not (you can use isLocationOnPath() method of PolyUtil library) and choose the color for the segment depending on check result: if point on the "main" path (within a specified tolerance in meters) you should not to show segment of "non-main" polyline or show it with selected color otherwise.
Andrii Omelchenko
  • 13,183
  • 12
  • 43
  • 79
  • 1
    Thanks. This really helped, the last part being what I was looking for. So, for every segment in the "non-main" polyline, I check whether it lies on the "main" polyline using `isLocationOnPath`. It it does, I discard that segment. – daniyelp Aug 01 '21 at 08:22
  • Regarding the first solution, it doens't really fit what I need. It would be great on this small example. But I was more interested in obtaining a list of polylines that don't overlap, in the code, not just on the map, since I need to reuse that list again and again and I don't want to have identical polyline segments in the memory, since that would be a waste of resources. Moreover, I could end up rendering on the map 50 polylines instead of just one (if, let's say, they would all overlap). – daniyelp Aug 01 '21 at 08:25
  • So, [PolyUtil library](https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/PolyUtil.html) is your choice at first. Later you can implement `isLocationOnPath()` manually. – Andrii Omelchenko Aug 01 '21 at 12:10