Google Maps API and Custom Polyline Route Between Markers

Google Maps API and custom polyline route between Markers

You can do this using the Google Maps API v2 for Android, and the Google Maps Directions webservice API

For getting started with the Google Maps API, there are plenty of other good answers already. See here for a complete working example of a simple map Activity. Note that you'll also need to get an API key set up to work with your project.

As for using the Google Maps Directions webservice API, you should first read the documentation. You can use an API key and enable the API in your developer console, but it still works currently without using an API key.

Here is the basic code you'll need in order to use the Google Maps API to draw a Polyline between two points, note that the points returned from the API are encoded in a base 64 encoded String that needs to be decoded.

First, ensure that your project includes the Google Maps Utility library, which will be used to decode the base64 encoded polyline:

dependencies {
compile 'com.google.maps.android:android-maps-utils:0.5+'
//.......
}

Here is the AsyncTask, that you should give two LatLng points to when calling it.

You would call the AsyncTask with two LatLng objects, for example between two Markers:

new GetDirectionsAsync().execute(markerOne.getPosition(), markerTwo.getPosition());

Here is the AsyncTask code:

class GetDirectionsAsync extends AsyncTask<LatLng, Void, List<LatLng>> {

JSONParser jsonParser;
String DIRECTIONS_URL = "https://maps.googleapis.com/maps/api/directions/json";

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

@Override
protected List<LatLng> doInBackground(LatLng... params) {
LatLng start = params[0];
LatLng end = params[1];

HashMap<String, String> points = new HashMap<>();
points.put("origin", start.latitude + "," + start.longitude);
points.put("destination", end.latitude + "," + end.longitude);

jsonParser = new JSONParser();

JSONObject obj = jsonParser.makeHttpRequest(DIRECTIONS_URL, "GET", points, true);

if (obj == null) return null;

try {
List<LatLng> list = null;

JSONArray routeArray = obj.getJSONArray("routes");
JSONObject routes = routeArray.getJSONObject(0);
JSONObject overviewPolylines = routes.getJSONObject("overview_polyline");
String encodedString = overviewPolylines.getString("points");
list = PolyUtil.decode(encodedString);

return list;

} catch (JSONException e) {
e.printStackTrace();
}

return null;
}

@Override
protected void onPostExecute(List<LatLng> pointsList) {

if (pointsList == null) return;

if (line != null){
line.remove();
}

PolylineOptions options = new PolylineOptions().width(5).color(Color.MAGENTA).geodesic(true);
for (int i = 0; i < pointsList.size(); i++) {
LatLng point = pointsList.get(i);
options.add(point);
}
line = mMap.addPolyline(options);

}
}

The AsyncTask references some member variables of the Activity, namely the Polyline and the GoogleMap, the Activity definition would look like this:

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback{

GoogleMap mMap;
Polyline line;
//.....

Here's the JSONParser class used in this example, note that this is a modified version updated for android-23 that I wrote a blog post about:

public class JSONParser {

String charset = "UTF-8";
HttpURLConnection conn;
DataOutputStream wr;
StringBuilder result;
URL urlObj;
JSONObject jObj = null;
StringBuilder sbParams;
String paramsString;

public JSONObject makeHttpRequest(String url, String method,
HashMap<String, String> params, boolean encode) {

sbParams = new StringBuilder();
int i = 0;
for (String key : params.keySet()) {
try {
if (i != 0){
sbParams.append("&");
}
if (encode) {
sbParams.append(key).append("=")
.append(URLEncoder.encode(params.get(key), charset));
}
else{
sbParams.append(key).append("=")
.append(params.get(key));
}

} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
i++;
}

if (method.equals("POST")) {
// request method is POST
try {
urlObj = new URL(url);

conn = (HttpURLConnection) urlObj.openConnection();

conn.setDoOutput(true);

conn.setRequestMethod("POST");

conn.setRequestProperty("Accept-Charset", charset);

conn.setReadTimeout(10000);
conn.setConnectTimeout(15000);

conn.connect();

paramsString = sbParams.toString();

wr = new DataOutputStream(conn.getOutputStream());
wr.writeBytes(paramsString);
wr.flush();
wr.close();

} catch (IOException e) {
e.printStackTrace();
}
}
else if(method.equals("GET")){
// request method is GET

if (sbParams.length() != 0) {
url += "?" + sbParams.toString();
}

Log.d("JSONParser", "full GET url: " + url);

try {
urlObj = new URL(url);

conn = (HttpURLConnection) urlObj.openConnection();

conn.setDoOutput(false);

conn.setRequestMethod("GET");

conn.setRequestProperty("Accept-Charset", charset);

conn.setConnectTimeout(15000);

conn.connect();

} catch (IOException e) {
e.printStackTrace();
}

}

try {
//Receive the response from the server
InputStream in = new BufferedInputStream(conn.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

String line;
result = new StringBuilder();
while ((line = reader.readLine()) != null) {
result.append(line);
}

Log.d("JSON Parser", "result: " + result.toString());

} catch (IOException e) {
e.printStackTrace();
}

conn.disconnect();

// try parse the string to a JSON object
try {
jObj = new JSONObject(result.toString());
} catch (JSONException e) {
Log.e("JSON Parser", "Error parsing data " + e.toString());
}

// return JSON Object
return jObj;
}
}

Result of drawing a route between two Markers:

Sample Image

Make polylines based on routing on Google Maps

You should check the return status of the DirectionsService. It is returning OVER_QUERY_LIMIT.

Note that the maximum number of waypoints in the Google Maps API v3 is 8 (plus origin and destination), unless you pay (Maps API for Business).

If you check the status and retry after a delay if the error is OVER_QUERY_LIMIT, it will eventually finish:

http://www.geocodezip.com/geoxml3_test/v3_directions_multipleWayPts.html

Compute the distance between polyline (route) and marker in Google Maps API v3

As far as I know, the Google Maps API does not give you a way to do this easily. And unfortunately, the algorithm you use will not give an accurate answer, because it gives the distance from the marker to the closest vertex on the path, not the closest point on the polyline itself, which will usually not be one of the points.

If you really need an accurate calculation, the best option I know of is to use the Javascript Topology Suite (JSTS). JSTS has a ton of geographic formulas for calculating this sort of thing. That means converting the polyline returned from the directions API into a JSTS object and calling the right utility function. Not trivial, but not too difficult either.

Selecting section of polyline - Google Maps Api

To get closest polyline i use triangle dependencies, so for each couple of markers i compute distance between points (d^ = (x2-x1)^ + (y2-y1)^) next get area of triangle from Heron's formula, than from p=(a*h)/2 got hight of triangle which is our point to polyline distance.
I also devide hight distance throught distance to closes mark point to get closest section - it's not perfect but works preety good.

When we got our section have to restore full path, change markers order and its done.

So when you click on poly line it should highlight, if you click again new marker will appear and highlight will be removed, and main path can be continuing from last marker.

Here is working JSFiddle

var poly, map, 
markers = {}, // here we handle all markers
tmp_path = null, // temporary path for select sections
id_i = 1, // initial value for marker id
MD = []; // selection section markers id

function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
center: { lat: 41.879, lng: -87.624} // Center the map on Chicago, USA.
});

poly = new google.maps.Polyline({
strokeColor: '#000000',
strokeOpacity: 1.0,
strokeWeight: 3
});
poly.setMap(map);

// Add a listener for the click event
map.addListener('click', addLatLng);
poly.addListener('click', polyOnClick);

}

function polyOnClick(event) {
if(tmp_path !== null) tmp_path.setMap(null);
tmp_path = null;

var
p_x = event.latLng.lat(),
p_y = event.latLng.lng(),
min_dist = 999999;

$.each(markers, function(k, v) {
if(typeof markers[(v.id+1)] == 'undefined') return false;

var
m_x = v.x,
m_y = v.y,
m2_x = markers[(v.id+1)].x,
m2_y = markers[(v.id+1)].y;

var
a = getDist(m_x, m2_x, m_y, m2_y),
b = getDist(m_x, p_x, m_y, p_y),
c = getDist(p_x, m2_x, p_y, m2_y);
var h = getHgh(a, b, c);

var min_mark_dist = (c > b)? b : c;

console.info(h/min_mark_dist);

if((h/min_mark_dist) < min_dist) {
min_dist = (h/min_mark_dist);
MD = [v.id, markers[(v.id+1)].id];
}

});

// append temporary path to haighlight section
tmp_path = new google.maps.Polyline({
path: [{lat: markers[MD[0]].x, lng: markers[MD[0]].y},{lat: markers[MD[1]].x, lng: markers[MD[1]].y}],
geodesic: true,
strokeColor: '#76EE00',
strokeOpacity: 1,
strokeWeight: 6
});

tmp_path.addListener('click', tmp_pathOnClick);
tmp_path.setMap(map);
}

function tmp_pathOnClick(event) {
tmp_path.setMap(null);
tmp_path = null;

/* restore markers order and path */
var tmp_markers = {},
ctx_marker_id = 0; // here we handle pushed marker id
var full_path = [], flag = false;
id_i = 1;

$.each(markers, function(k, v) {
if(MD[0] < v.id && !flag) {
flag = true;
full_path.push(event.latLng);
ctx_marker_id = id_i;
tmp_markers[id_i] = {id: id_i, x: event.latLng.lat(), y: event.latLng.lng()};
id_i++;
}

full_path.push({lat: v.x, lng: v.y});
tmp_markers[id_i] = {id: id_i, x: v.x, y: v.y, marker: v.marker};
v.marker.setTitle('#'+id_i);
id_i++;
});

markers = tmp_markers;

// create new marker
var marker = new google.maps.Marker({
position: event.latLng,
title: '#' + ctx_marker_id,
map: map,
id: ctx_marker_id
});

markers[ctx_marker_id].marker = marker;

/* restore main path */
poly.setMap(null); // clear view
poly = null; // delete object

poly = new google.maps.Polyline({
strokeColor: '#000000',
strokeOpacity: 1.0,
strokeWeight: 3,
path: full_path
});
poly.setMap(map);

poly.addListener('click', polyOnClick);
MD = []; // clear selected section markers id
}

// get distance between two points
function getDist(x1, x2, y1, y2) {
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
}

// get hight from abc triangle
function getHgh(a, b, c) {
var p = (a+b+c)/2;
var F = Math.sqrt((p * (p-a) * (p-b) * (p-c)), 2);
return 2*F/a;
}

// Handles click events on a map, and adds a new point to the Polyline.
function addLatLng(event) {
console.log('addLatLng');

if(MD.length) return false;
if(tmp_path !== null) tmp_path.setMap(null);
tmp_path = null;

var path = poly.getPath();

// Because path is an MVCArray, we can simply append a new coordinate
// and it will automatically appear.
path.push(event.latLng);

// Add a new marker at the new plotted point on the polyline.
var marker = new google.maps.Marker({
position: event.latLng,
title: '#' + path.getLength(),
map: map,
id: id_i
});

markers[id_i++] = {id: marker.id, x: marker.position.lat(), y: marker.position.lng(), marker: marker};
}

Draw Polyline from Marker on click Google Maps API v3

  1. you need to create the polyline in the marker click listener (where the marker is accessible and you can get its position).
  2. use documented methods (.getPosition(), not undocumented properties .position)
for (var i in points) {
var p = points[i];
var latlng = new google.maps.LatLng(p[1], p[2]);

var marker = new google.maps.Marker({
position: latlng,
icon: points[i][3],
zIndex: p[5],
map: map,
title: p[0]
});

overviewMarkers.push(marker);

google.maps.event.addListener(marker, 'click', (function (marker, i) {
return function () {
var originPoint = this.getPosition();
var oLat = parseFloat(this.getPosition().lat().toFixed(4));
var oLng = parseFloat(this.getPosition().lng().toFixed(4));
for (var d in destPoint) {
var t = destPoint[d];

var linePath = new google.maps.Polyline({
path: [originPoint, [t][0]],
strokeColor: '#4A484D',
strokeOpacity: 1.0,
strokeWeight: 2,
geodesic: true,
icons: [{
icon: lineSymbol,
offset: '100%',
repeat: '60px'
}],
map: map

});
arrayLine.push(linePath);
}
infowindow.setContent(points[i][6] + '<div id="infopanel">' +
'<input onclick="addLine();" type=button value="Show Routes">' +
'<input onclick="removeLine();" type=button value="Remove Routes"></div>');
infowindow.open(map, marker);
};
})(marker, i));
} //end for loop

working fiddle

code snippet:

var map;var arrayLine = [];var overviewMarkers = [];var oLat, oLng;
function initialize() { var myLatlng = new google.maps.LatLng(0, 180); var mapOptions = { zoom: 1, center: myLatlng }; map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
var points = [ ['Karachi, Pakistan', 25.0111453, 67.0647043, 'http://maps.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png', 'Overview', 1, '<h4>Sample Text</h4'], ['Bangkok, Thailand', 13.7246005, 100.6331108, 'http://maps.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png', 'Overview', 1, '<h4>Sample Text</h4>' ], ['Rotterdam, Netherlands', 51.9279723, 4.4904063, 'http://maps.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png', 'Overview', 1, '<h4>Sample Text</h4>'], ['New York, NY, USA', 40.7056308, -73.9780035, 'http://maps.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png', 'Overview', 1, '<h4>Sample Text</h4>'], ['Memphis, TN, USA', 35.129186, -89.9696395, 'http://maps.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png', 'Overview', 1, '<h4>Sample Text</h4>'] ];
var infowindow = new google.maps.InfoWindow();
var originPoint = []; // Creates markers for (var i in points) { var p = points[i]; var latlng = new google.maps.LatLng(p[1], p[2]);
var marker = new google.maps.Marker({ position: latlng, icon: points[i][3], zIndex: p[5], map: map, title: p[0] });
overviewMarkers.push(marker);

google.maps.event.addListener(marker, 'click', (function(marker, i) { return function() { var originPoint = this.getPosition(); var oLat = parseFloat(this.getPosition().lat().toFixed(4)); var oLng = parseFloat(this.getPosition().lng().toFixed(4)); for (var d in destPoint) { var t = destPoint[d];
var linePath = new google.maps.Polyline({ path: [originPoint, [t][0]], strokeColor: '#4A484D', strokeOpacity: 1.0, strokeWeight: 2, geodesic: true, icons: [{ icon: lineSymbol, offset: '100%', repeat: '60px' }], map: map
}); arrayLine.push(linePath); } infowindow.setContent(points[i][6] + '<div id="infopanel">' + '<input onclick="addLine();" type=button value="Show Routes">' + '<input onclick="removeLine();" type=button value="Remove Routes"></div>'); infowindow.open(map, marker); }; })(marker, i)); } //end for loop }var arrayLine = [];var originPoint = [new google.maps.LatLng(oLat, oLng)];var destPoint = [new google.maps.LatLng(51.9279723, 4.4904063), new google.maps.LatLng(40.136482, -73.831299), new google.maps.LatLng(34.0204989, -118.4117325)];var lineSymbol = { path: google.maps.SymbolPath.FORWARD_OPEN_ARROW};
function addLine() { setLines(map); // alert(parseFloat(oLat));}google.maps.event.addDomListener(window, 'load', initialize);
function setLines(map) { for (var i = 0; i < arrayLine.length; i++) { arrayLine[i].setMap(map); }}
function removeLine() { setLines(null);}
html,body,#map-canvas {  height: 100%;  margin: 0px;  padding: 0px}#panel {  position: absolute;  top: 5px;  left: 50%;  margin-left: -180px;  z-index: 5;  background-color: #fff;  padding: 5px;  border: 1px solid #999;}#infopanel {  width: 200px;  height: 60px;}
<script src="https://maps.googleapis.com/maps/api/js"></script><div id="panel">  <input onclick="showFactoryMarkers();" type=button value="Show Factories">  <input onclick="showCFSMarkers();" type=button value="Show CFS">  <input onclick="showPortMarkers();" type=button value="Show Ports">  <input onclick="hideMarkers();" type=button value="Hide All"></div><div id="map-canvas"></div>

Google Maps Polyline and Marker together

I tested with some bogus data here and there doesn't seem to be a problem. The difference is I'm using userCoorPath for the polyline and userCoor to set the markers. I'm repeating the information, but they are being used differently. One is an array of LatLngs, the other an array of string and two floats.

http://jsfiddle.net/nSf9N/

Google Maps API Snap Marker To Polyline

Here is my own implementation for snapping marker to polyline (based on GPS location):

First, use the polyline points to create a list of LatLng that are at a smaller distance (I'm using a one meter distance specified in splitPathIntoPoints function):

for (int i = 0; i < polylinePoints.size(); i++) {
LatLng src = new LatLng(Double.parseDouble(polylinePoints.get(i).get("lat")), Double.parseDouble(polylinePoints.get(i).get("lng")));

if (polylinePoints.size() > i + 1) {
LatLng dest = new LatLng(Double.parseDouble(polylinePoints.get(i + 1).get("lat")), Double.parseDouble(polylinePoints.get(i + 1).get("lng")));
List<LatLng> splitPoints = splitPathIntoPoints(src, dest);
mSplitPoints.addAll(splitPoints);
} else {
break;
}
}

Code for splitPathIntoPoints:

public static List<LatLng> splitPathIntoPoints(LatLng source, LatLng destination) {
Float distance = findDistance(source, destination);

List<LatLng> splitPoints = new ArrayList<>();
splitPoints.add(source);
splitPoints.add(destination);

while (distance > 1) {
int polypathSize = splitPoints.size();
List<LatLng> tempPoints = new ArrayList<>();
tempPoints.addAll(splitPoints);

int injectionIndex = 1;

for (int i = 0; i < (polypathSize - 1); i++) {
LatLng a1 = tempPoints.get(i);
LatLng a2 = tempPoints.get(i + 1);

splitPoints.add(injectionIndex, findMidPoint(a1, a2));
injectionIndex += 2;
}

distance = findDistance(splitPoints.get(0), splitPoints.get(1));
}

return splitPoints;
}

Code for findDistance:

public static Float findDistance(LatLng source, LatLng destination) {
Location srcLoc = new Location("srcLoc");
srcLoc.setLatitude(source.latitude);
srcLoc.setLongitude(source.longitude);

Location destLoc = new Location("destLoc");
destLoc.setLatitude(destination.latitude);
destLoc.setLongitude(destination.longitude);

return srcLoc.distanceTo(destLoc);
}

Code for findMidPoint:

public static LatLng findMidPoint(LatLng source, LatLng destination) {
double x1 = toRad(source.latitude);
double y1 = toRad(source.longitude);

double x2 = toRad(destination.latitude);
double y2 = toRad(destination.longitude);

double Bx = Math.cos(x2) * Math.cos(y2 - y1);
double By = Math.cos(x2) * Math.sin(y2 - y1);
double x3 = toDeg(Math.atan2(Math.sin(x1) + Math.sin(x2), Math.sqrt((Math.cos(x1) + Bx) * (Math.cos(x1) + Bx) + By * By)));
double y3 = y1 + Math.atan2(By, Math.cos(x1) + Bx);
y3 = toDeg((y3 + 540) % 360 - 180);

return new LatLng(x3, y3);
}

Once mSplitPoints is filled with smaller polyline points that are separated by 1 meter apart from each other, the function below finds the snapped location on the polyline based on my current GPS location. Note that mMinorIndexTravelled is a private field in my class with initial value set to zero.

public static LatLng snapToPolyline(LatLng currentLocation) {
LatLng snappedLatLng = null;

Location current = new Location("current");
current.setLatitude(currentLocation.latitude);
current.setLongitude(currentLocation.longitude);

Integer minConfirmCount = 0;
float currentMinDistance = 0, previousMinDistance = 0;
List<Float> distances = new ArrayList<>();

for (LatLng point: mSplitPoints.subList(mMinorIndexTravelled, mSplitPoints.size() - 1)) {
Location pointLoc = new Location("pointLoc");
pointLoc.setLatitude(point.latitude);
pointLoc.setLongitude(point.longitude);

distances.add(current.distanceTo(pointLoc));
previousMinDistance = currentMinDistance;
currentMinDistance = Collections.min(distances);

if (currentMinDistance == previousMinDistance) {
minConfirmCount++;
if (minConfirmCount > 10) {
mMinorIndexTravelled = distances.indexOf(currentMinDistance) + mMinorIndexTravelled;
snappedLatLng = mSplitPoints.get(mMinorIndexTravelled);

break;
}
}
}

return snappedLatLng;
}

Custom Polylines on Google Maps API V3

This doesn't look like the right syntax to me:

poly = new google.maps.Polyline (polylineOptions)({map: map});

Instead I'd say either add the map attribute to your polylineOptions, or call the setMap function on the poly object.

Marker on all of polyline from Google maps directions V3

I think that I found your mistake which is in this piece of code:

for (var i = 0; i < legs.length; i++) {
...
for (var j = 0; j < steps.length; j++) {
...
for (var k = 0; k < nextSegment.length; k++) {
...
// Here you add the marker to the array if there is no marker set yet
var marker = markerArray[k] = markerArray[k] || new google.maps.Marker;
...
}
}
}

The mistake you did is, that you just look at the array's index k. However, the value of k is reset to 0 every time the for-loop starts again. Therefore, you have to add the array's length to the index:

for (var i = 0; i < legs.length; i++) {
...
for (var j = 0; j < steps.length; j++) {
...
for (var k = 0; k < nextSegment.length; k++) {
...
// This is the real index you want to look at
var marker = markerArray[markerArray.length + k] = markerArray[markerArray.length + k] || new google.maps.Marker;
...
}
}
}

However, I think that actually this reaches out and makes your code also lighter and more readable:

for (var i = 0; i < legs.length; i++) {
...
for (var j = 0; j < steps.length; j++) {
...
for (var k = 0; k < nextSegment.length; k++) {
...
// This looks much easier IMHO
var marker = new google.maps.Marker;
...
markerArray.push(marker);
}
}
}


Related Topics



Leave a reply



Submit