I'm working on Android Studio>> I have an origin point and destiny point. I draw the route with the JSONData from Google Services and I create a polyline. Now, I need to monitor user's current position from the polyline in the map, but I can't.
Here's my code:
private void drawRoute(List<LatLng> list) {
if ((list != null) && (list.size() > 0)) {
polylineOptions = new PolylineOptions()
.addAll(list)
.width(8);
map.addPolyline(polylineOptions);
CircleOptions circleOptions;
for (int i = 0; i < polylineOptions.getPoints().size(); i++) {
LatLng point = polylineOptions.getPoints().get(i);
circleOptions = new CircleOptions()
.center(new LatLng(point.latitude, point.longitude))
.strokeColor(Color.TRANSPARENT)
.strokeWidth(1)
.fillColor(Color.argb(100, 164, 171, 167))
.radius(10);
map.addCircle(circleOptions);
}
} else {
Log.i("Msg", "No routes");
}
}
And I get the distance with this:
private double getDistance(LatLng originPoint, LatLng destinyPoint) {
try {
Location origin = new Location("Origin point");
origin.setLatitude(originPoint.latitude);
origin.setLongitude(originPoint.longitude);
Location destiny = new Location("Destiny point");
destiny.setLatitude(destinyPoint.latitude);
destiny.setLongitude(destinyPoint.longitude);
return origin.distanceTo(destiny);
} catch (Exception e) {
Log.e("Msg", e.getMessage());
return 0d;
}
}
I don't know if there's a way to find a "piece" of polyline in the circle around user's current location and calculate the distance between that "piece" and current location. I've been searching but the code I found is in V3 and I'm starting Android apps. I appreciate your help!
Well, if you need it, I resolve it with this code:
#Override
protected Boolean doInBackground(List<LatLng>... params) {
publishProgress();
List<LatLng> list = params[0];
LatLng currentPosition = list.get(0);
for (int i = 1; i < list.size(); i++) {
if (obtenerDistancia(currentPosition, list.get(i)) <= 100) {
return false;
}
}
return true;
}
Where list = current position + polyline.getPoints()
list[0] = current position;
list[1], list[2]..., list[n] = polyline points.
I calculate distance between my current position (list[0]) and each point in polyline (list[1], list[2]..., list[n]]. If I find at least one point that is <= 100 meters from my current position, that means user is close to the drawn route.
You don't need to find the "piece" around the user current postion.
You just need to decodePolyLine the points in the JSon file and put all the LatLng to a list and use the class I have created to check it.
Here is my answer for the relative topic it have the Class and the method you're looking for: https://stackoverflow.com/a/49904276/7548514
Related
I am building a car navigation application in Xamarin.Android (native not forms). I had read a lot, and follow instructions I found on how to add the map in the application and also how to use the location services, either with the FusedLocationProvider or the NavigationManager. I decided to use the NavigationManager since it looks more accurate because of the number of decimal digits (14) in the LatLng. All good up to now. I am testing my app and while the marker and the map are moving, in order to follow the car's direction, the map is blurred (see the attached pictures), it looks like it does not have the appropriate time to get refreshed.
What I am doing in order to move the camera and the marker?
1. OnLocationChanged from the LocationManager I am calling the UpdateCameraBearing function
internal static void UpdateCameraBearing(float bearing, Location newLocation)
{
if (Map == null)
return;
// Save current zoom
int originalZoom = (int)Map.CameraPosition.Zoom;
if (!startingZoom.HasValue)
{
startingZoom = originalZoom = MapZoomDef;
}
LastLocation = newLocation;
LatLng newPosition = new LatLng(newLocation.Latitude, newLocation.Longitude);
CameraPosition camPos = new CameraPosition(newPosition, originalZoom != MapZoomDef ? originalZoom : MapZoomDef, 0, bearing);
try
{
Map.AnimateCamera(CameraUpdateFactory.NewCameraPosition(camPos));
UpdateMarkerPosition(newPosition);
}
catch (Exception ex)
{
Log.Error(TAG, ex.Message);
}
}
This function animates the camera to the new position and also updates the marker position using the UpdateMarkerPosition function as follows
private static void UpdateMarkerPosition(LatLng newPosition)
{
if (null == MapMarker)
{
SetNewMarker(newPosition);
}
else
{
//positionMarker.Visible = true;
//positionMarker.Position = newPosition;
AnimateMarker(newPosition, new LatLngInterpolatorSpherical());
}
}
private static void SetNewMarker(LatLng myPosition)
{
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.SetPosition(myPosition);
markerOptions.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Mipmap.ic_car));
MapMarker = Map.AddMarker(markerOptions);
}
private static void AnimateMarker(LatLng finalPosition, LatLngInterpolatorSpherical latLngInterpolator)
{
Android.OS.Handler handler = new Android.OS.Handler();
long start = Android.OS.SystemClock.UptimeMillis();
LatLng startPosition = MapMarker.Position;
Android.Views.Animations.AccelerateDecelerateInterpolator interpolator = new Android.Views.Animations.AccelerateDecelerateInterpolator();
float durationInMs = 1000;
Java.Lang.Runnable mUpdateGeneration = null;
mUpdateGeneration = new Java.Lang.Runnable(() => {
long elapsed;
float t;
float v;
// Calculate progress using interpolator
elapsed = Android.OS.SystemClock.UptimeMillis() - start;
t = elapsed / durationInMs;
v = interpolator.GetInterpolation(t);
MapMarker.Position = latLngInterpolator.Interpolate(v, startPosition, finalPosition);
// Repeat till progress is complete.
if (t < 1)
{
// Post again 16ms later.
handler.PostDelayed(mUpdateGeneration, 16);
}
});
handler.Post(mUpdateGeneration);
}
The UpdateMarkerPosition function checks if the marker exists and if not (this is the first time) it creates it, otherwise it calls the AnimateMarker function where I am using an Interpolator in order to simulate a smooth marker movement.
Last but not least the LocationManager.RequestLocationUpdates is configured with 1-second minimum time and 1-meter minimum Distance.
I will appreciate any suggestions on how to solve the problem...
enter image description here
enter image description here
When I click on polyline I want time (custom object) to be displayed at that particular lat long position.
Code to achieve polyline
PolylineOptions lineOptions = new PolylineOptions().width(7).color(Color.BLACK).geodesic(true);
for (int i = 0; i < points.size(); i++) {
LatLng latLng1 = new LatLng(Double.parseDouble(points.get(i).getmLatitude()), Double.parseDouble(points.get(i).getmLongitude()));
lineOptions.add(latLng1);
}
if (mPolyline != null) {
mPolyline.remove();
}
mPolyline = mMap.addPolyline(lineOptions);
mPolyline.setClickable(true);
for (int i = 0; i < points.size(); i++) {
//setting tags to be used on ployline click
mPolyline.setTag(points.get(i).getTime());
}
List<PatternItem> pattern = Arrays.asList(
new Gap(15), new Dash(15), new Gap(15));
mPolyline.setPattern(pattern);
mPolyline.setJointType(JointType.ROUND);
Now when I click on polyline I get only one tag which is same for all. I want unique tags(custom objects) for every polyline position which relate to lat long
mMap.setOnPolylineClickListener(new GoogleMap.OnPolylineClickListener() {
#Override
public void onPolylineClick(Polyline polyline) {
Toast.makeText(mContext, (String) polyline.getTag(), Toast.LENGTH_SHORT).show();
}
});
Thanks for contributing :)
EDIT
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
boolean isOnRoute = PolyUtil.isLocationOnPath(latLng, lineOptions.getPoints(), false, 25);
if (isOnRoute) {
for (LocationVo mCoordinates : points) {
double distanceInMeters = SphericalUtil.computeDistanceBetween(latLng, mCoordinates.getmLatLong());
boolean isWithin50m = distanceInMeters < 50;
if (isWithin50m) {
Toast.makeText(mContext, mCoordinates.getTime(), Toast.LENGTH_SHORT).show();
break;
}
}
}
}
});
Not all my polylines match with the conditions and show no toast on click
With a tolerance of 25 meters you are defining a corridor along each path segment 25 meters on either side. So as implemented, all this tells you is the click point is somewhere in the corridor centered on the path - or effectively they've clicked on the polyline with some room for error.
The problem you are having is in your assumption that you'll always be within 50 meters of a point - this is incorrect and also not what you want as best as I can tell. By definition, once isOnRoute is true, you always want to find a point since by definition they've clicked on the polyline; this is done (inefficiently) by simply calculating all distances from click-point to all polyline points and determining the shortest distance (a simple loop
with a min logic); then use this point for your toast.
Now if you really wanted to limit what are considered "successful" clicks, where a successful click is both "on the polyline" and "near a point" then your distance check would be added using some acceptable value - this would be in effect defining a "box" around each point with dimensions 50 meters (25 x 2)-by-whatever your distance check is. Note this is not the same as just checking a radius circle around each point unless the radius of the desired circle is equivalent to the polyline tolerance.
(One other trivial point - your mixing measurement systems when using false for geodesic and then computing spherical distance - but should not be an issue with your implementation.) (If helpful I'll add a picture later.)
Summary of modification: "check if user clicks along the polyline" AND "determine closest point along line" AND "display some toast for that point".
So make this modification after isOnRoute is true:
LocationVo closestPoint = null;
double minDist = -1;
for (LocationVo mCoordinates : points) {
double distanceInMeters = SphericalUtil.computeDistanceBetween(latLng, mCoordinates.getmLatLong());
if (minDist < 0 || distanceInMeters < minDist) {
minDist = distanceInMeters;
closestPoint = mCoordinates;
}
}
if (closestPoint != null) {
Toast.makeText(mContext, closestPoint.getTime(), Toast.LENGTH_SHORT).show();
}
To implement the "box" discussed above around each point modify the one condition:
if ((distanceInMeters < MAX_DISTANCE_FROM_POINT) && (minDist < 0 || distanceInMeters < minDist)) {
Note this means in some cases you will not get a toast even if they clicked along the polyline.
I'm trying to show a ground overlay with markers on it to my users. I'm trying to restrain the view to only this image placed on the map. I want the user to only see the image as a ground overlay placed on the map and not be able to go to the surrounding map. And if they go over the edge, the gestures will be blocked.
I want something like this:
I don't want this:
show-only-ground-overlays-map-android or this:
I tried to set my map.setLatLngBoundsForCameraTarget() to my image bounds but the result is something like the previous image...
The next thing I tried is to set a bunch of LatLng objects all around the ground overlay and check with curScreen.contains(customMapDetectionPoints.get(LatLng Object)) to see if they appear on the viewport. It's does work but I can't stop the camera to go over the edge...
Here my test code so far :
private GroundOverlay groundOverlay;
private GoogleMap globalMap;
private final int DETECTION_POINTS_CUSTOM_MAP = 20;
private List<LatLng> customMapDetectionPoints = new ArrayList<>();
//Fully Working as suppose to
#Override
public void onMapReady(GoogleMap map) {
//Other Stuff...
LatLngBounds mapBounds = groundOverlay.getBounds();
map.setLatLngBoundsForCameraTarget(mapBounds);
globalMap = map;
LatLng northwest = new LatLng( mapBounds.northeast.latitude, mapBounds.southwest.longitude);
LatLng northeast = mapBounds.northeast;
LatLng southeast = new LatLng( mapBounds.southwest.latitude, mapBounds.northeast.longitude);
LatLng southwest = mapBounds.southwest;
//My ground overlay is rectangle so I don't need to follow a path or something like that
setDetectionPoints(northwest, southwest);
setDetectionPoints(northeast, southeast);
setDetectionPoints(northwest, northeast);
setDetectionPoints(southwest, southeast);
map.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
#Override
public void onCameraMoveStarted(int i) {
LatLngBounds curScreen = globalMap.getProjection().getVisibleRegion().latLngBounds;
CameraPosition cameraPosition = globalMap.getCameraPosition();
for (int x =0;x<customMapDetectionPoints.size();x++) {
if (curScreen.contains(customMapDetectionPoints.get(x))) {
cancelMapMovement(cameraPosition);
Log.d("OUT", "Ground Overlay is outside viewport");
return;
} else {
globalMap.getUiSettings().setAllGesturesEnabled(true);
Log.d("IN", "Ground Overlay is inside viewport");
}
}
}
});
//Add 20 new location between two location
//Fully Working as suppose to
public void setDetectionPoints(LatLng fromPos, LatLng toPos) {
double pointLatitude = fromPos.latitude;
double pointLongitude = fromPos.longitude;
double addingValue;
if (fromPos.latitude == toPos.latitude) {
addingValue = (toPos.longitude - fromPos.longitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLongitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
} else if (fromPos.longitude == toPos.longitude) {
addingValue = (toPos.latitude - fromPos.latitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLatitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
}
}
//The problem is here!
public void cancelMapMovement(CameraPosition camPos ) {
//HOW CAN I STOP THE MOVEMENT OVER THE GROUND OVERLAY EDGE
//And make sure that the user dosen't see over the edge
globalMap.getUiSettings().setAllGesturesEnabled(false);
globalMap.moveCamera(CameraUpdateFactory.newCameraPosition(camPos));
}
At this point I think I have two possible solutions:
1- Only use the setLatLngBoundsForCameraTarget() function and set a offset or margin to the camera. But is it possible and will it work for my use case ?
2- Solve my camera restriction problem with the code already written
Thanks for helping! Suggest other solution if you find one!
I will provide more info if wanted.
I wrote this method to determine a distance to a destination along a path and also to determine a distance to the next waypoint along that path. The problem is, sometimes I don't go within 10 meters of the waypoint so it's not getting caught as having been reached. Sure, I could increase the threshold of distance that would make the waypoint met, but I'd rather come up with another solution to determine if I've passed that waypoint and just skip it (by adding it to the waypointsReached list) to move on to the next one. Checking to see if the distance from my location to the waypoint is increasing instead of decreasing won't work because I may stray off the path before I even get to the waypoint.
Can anyone help me wrap my head around an idea of a way I can determine if I've just passed the waypoint and move on to the next one?
Here's the method I've written to get my distance to the final destination and to the next waypoint.
// method to calculate distance to destinatoin from a loaded route
private void CalculateRouteToDestination(Location location) {
new AsyncTask<Location, Void, ArrayList<Double>>(){
#Override
protected ArrayList<Double> doInBackground(Location... params) {
ArrayList<Double> distances = new ArrayList<Double>();
Location location = params[0];
//predefinedRoutePoints
int closestLocationIndex = 0;
double smallestDistance = -1;
int cnt = 0;
for(LatLng ltlg : predefinedRoutePoints) {
// create new location object for the latlng coords
Location cLocation = new Location("");
cLocation.setLatitude(ltlg.latitude);
cLocation.setLongitude(ltlg.longitude);
// get the distance from current location to all of the points in the array and set the index from the array where it is
double cDistance = location.distanceTo(cLocation);
if(smallestDistance == -1 || cDistance < smallestDistance) {
closestLocationIndex = cnt;
smallestDistance = cDistance;
}
cnt++;
}
// now with the index from the array of the cloest point, calculate the distance to the last point in the array from that index (distance to destination)
double distanceToDestination = 0;
for(int i = closestLocationIndex; i < predefinedRoutePoints.size(); i++) {
//Log.d("home","iteration:"+i);
if(i != (predefinedRoutePoints.size() -1)) {
Location l1 = new Location("");
l1.setLatitude(predefinedRoutePoints.get(i).latitude);
l1.setLongitude(predefinedRoutePoints.get(i).longitude);
Location l2 = new Location("");
l2.setLatitude(predefinedRoutePoints.get(i+1).latitude);
l2.setLongitude(predefinedRoutePoints.get(i+1).longitude);
distanceToDestination += l1.distanceTo(l2);
}
}
// now add the distance the user is to the cloest point to get the final output of distance to destinatoin
distanceToDestination += smallestDistance;
distances.add(distanceToDestination);
// check if need to measure distance to next waypoint
double distanceToNextWaypoint = 0;
if(predefinedWaypoints.size() > 0){
if(predefinedWaypoints.size() != waypointsReached.size()) {
for (int i = closestLocationIndex; i < predefinedWaypoints.get(waypointsReached.size()).WaypointIndex; i++) {
Location l1 = new Location("");
l1.setLatitude(predefinedRoutePoints.get(i).latitude);
l1.setLongitude(predefinedRoutePoints.get(i).longitude);
Location l2 = new Location("");
l2.setLatitude(predefinedRoutePoints.get(i + 1).latitude);
l2.setLongitude(predefinedRoutePoints.get(i + 1).longitude);
distanceToNextWaypoint += l1.distanceTo(l2);
}
distanceToNextWaypoint += smallestDistance;
if(distanceToNextWaypoint < 10) {
waypointsReached.add(predefinedWaypoints.get(waypointsReached.size()));
}
distances.add(distanceToNextWaypoint);
}
}
return distances;
}
#Override
protected void onPostExecute(ArrayList<Double> v) {
distanceToDestinationText.setText("Destination To Destination: "+Utilities.GetMiles(v.get(0))+"mi");
if(v.size() > 1) {
distanceToNextWaypoint.setText("Next Waypoint: "+Utilities.GetMiles(v.get(1))+"mi");
} else {
distanceToNextWaypoint.setVisibility(View.GONE);
distanceToNextWaypoint.setText("Next Waypoint: 0.0mi");
}
}
}.execute(location);
}
Hi Im having a little issue with displaying points on a map.
I use an Arraylist to store multiple lat/lng values and then do a for loop to add the point and auto zoom. Everything works fine when there are 2 or more markers. The problem is when only 1 marker is added it zooms in too close.
Anyone know how to resolve this?
public static void processMap()
{
for (int i = 0; i < lat.size(); i++)
{
MarkerOptions markerOptions = new MarkerOptions();
LatLng latLng = new LatLng(lat.get(i), lng.get(i));
markerOptions.position(latLng);
markerOptions.title("title");
markerOptions.snippet("description");
mMap.addMarker(markerOptions);
bounds.include(new LatLng(lat.get(i), lng.get(i)));
}
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
Updated code
public static void processMap()
{
int num = 0;
double lat2 = 0;
double lng2 = 0;
for (int l = 0; l < lat.size(); l++)
{
lat2 = lat.get(l);
lng2 = lng.get(l);
LatLng latLng = new LatLng(lat2, lng2);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(latLng);
markerOptions.title(title.get(l));
markerOptions.snippet(description.get(l));
mMap.addMarker(markerOptions);
bounds.include(new LatLng(lat2, lng2));
num++;
}
if (num == 1)
{
// if only 1 marker
LatLng latLng2 = new LatLng(lat2, lng2);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng2, 16));
}
else
{
// more than 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
}
Hi sorry I didn't reply earlier I was attempting to fix the problem. I managed to fix it, but is this the correct way of going about it?
The edit you made should fix your problem. Anyway you don't need to set new LatLng if you only have 1 marker. Passing the bound should do. I would make this little edit:
if (num == 1)
{
// if only 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.build(), 16));
}
else
{
// more than 1 marker
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 150));
}
Hope it helps. Good luck!
Your code does not really tell why you are using .newLatLngBounds(), but this will center the map on your given bounding box and respecting the given 150px padding around the box.
The zoom is then adapted according to the elements inside the bounding box to show all of them. When there is only one marker inside the box, it will zoom to it (and probably too close). The docs say that the map will be centered "at the greatest possible zoom level".
So to prevent this, I would choose a good zoom level for the first marker, add it, move the camera with moveCamera(...) and after that continue with your code when there are other markers left.