At present I am using circles to display the path of an object of a certain width in meters on a map. This is problematic because at the rate of even 15 mph the circle method would have to be taken every few hundred milliseconds so that the circles overlap so you don't get gaps between the circles. After an hour you end up with countless circles and no memory.
CircleOptions circleOptions = new CircleOptions()
.center(new LatLng(location.getLatitude(), location.getLongitude()));
circleOptions.radius(radi); // In meters
Currently I can't seem to find where the google maps android api v2 supports using a polyline with set radius in meters, but only in pixel width. With varying zoom levels and screen conditions and the projection of the map this sounds rather complicated to basically highlight a path of certain width on the map with a polyline.
Has anyone seen any alternative to using the circle radius method in the api?
Using android-maps-utils this becomes relatively simple:
public static PolygonOptions getPolygonOptions(LatLng point1, LatLng point2, float widthInMeters) {
double heading = SphericalUtil.computeHeading(point1, point2);
double distance = SphericalUtil.computeDistanceBetween(point1, point2);
LatLng corner1 = SphericalUtil.computeOffset(point2, widthInMeters / 2, heading + 90);
LatLng corner2 = SphericalUtil.computeOffset(point2, widthInMeters / 2, heading - 90);
LatLng corner3 = SphericalUtil.computeOffset(corner2, distance, heading + 180);
LatLng corner4 = SphericalUtil.computeOffset(corner3, widthInMeters, heading + 90);
return new PolygonOptions().add(corner1, corner2, corner3, corner4);
}
And then add polygons connecting your points:
map.addPolygon(
getPolygonOptions(point1, point2, 50)
.fillColor(Color.BLACK)
);
map.addPolygon(
getPolygonOptions(point2, point3, 50)
.fillColor(Color.BLACK)
);
Related
I have added current location via google map routing with
Routing routing = new Routing.Builder()
.travelMode(Routing.TravelMode.DRIVING)
.key(getResources().getString(R.string.google_maps_api))
.withListener(this)
.waypoints(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()), site_location)
.alternativeRoutes(false)
.build();
routing.execute();
#Override
public void onRoutingSuccess(ArrayList<Route> route, int shortestRouteIndex) {
if (polylines.size() > 0) {
for (Polyline poly : polylines) {
poly.remove();
}
}
polylines = new ArrayList<>();
//add route(s) to the map.
for (int i = 0; i < route.size(); i++) {
//In case of more than 5 alternative routes
int colorIndex = i % COLORS.length;
PolylineOptions polyOptions = new PolylineOptions();
polyOptions.color(getResources().getColor(COLORS[colorIndex]));
polyOptions.width(10 + i * 13);
polyOptions.addAll(route.get(i).getPoints());
Polyline polyline = googleMap.addPolyline(polyOptions);
polylines.add(polyline);
int distance = route.get(i).getDistanceValue();
if (distance < 1000){
totalKm.setText( distance+" Metres");
}else {
totalKm.setText( (distance/1000) +" km");
}
}
LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()));
builder.include(site_marker.getPosition());
LatLngBounds bounds = builder.build();
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 100);
googleMap.animateCamera(cu);
}
THis displays a driving directions like
But am trying to display the default google map driving icon with zoomed view like
How do i proceed to add such a map while still retaining the polylines to show driving view.
Jinesh Francis totally right in his answer: you should either run the default map Google Maps application through intent or modify the standard MapView (or MapFragment).
TLDR;
If you chose the second way - easiest approach is to use standard classes of Android Google Maps API to create view like in your example (other way is to create MapView-based custom view).
At first read carefully p 3.2.4 Restrictions Against Misusing the Services (d) of Google Maps Platform Terms of Service:
(d) No Re-Creating Google Products or Features. Customer will not use
the Services to create a product or service with features that are
substantially similar to or that re-create the features of another
Google product or service. Customer’s product or service must contain
substantial, independent value and features beyond the Google products
or services. For example, Customer will not: (i) re-distribute the
Google Maps Core Services or pass them off as if they were Customer’s
services; (ii) create a substitute of the Google Maps Core Services,
Google Maps, or Google Maps mobile apps, or their features; (iii) use
the Google Maps Core Services in a listings or directory service or to
create or augment an advertising product; (iv) combine data from the
Directions API, Geolocation API, and Maps SDK for Android to create
real-time navigation functionality substantially similar to the
functionality provided by the Google Maps for Android mobile app.
and if you not violate Terms of Service you can do what you want with that steps/tasks:
1) get user current location;
2) get a route path segment nearest to user current location (because user location rarely exactly on road);
3) get a azimuth (bearing) of this segment;
4) show map with route path and user current position marker with appropriate tilt and rotation according path segment bearing.
Task 1 can be solved like in this answer of Axxiss:
private final LocationListener mLocationListener = new LocationListener() {
#Override
public void onLocationChanged(final Location location) {
//your code here
}
};
Task 2 can be solved via PolyUtil.isLocationOnPath() like in that answer:
private LatLng getMarkerProjectionOnSegment(LatLng carPos, List<LatLng> segment, Projection projection) {
LatLng markerProjection = null;
Point carPosOnScreen = projection.toScreenLocation(carPos);
Point p1 = projection.toScreenLocation(segment.get(0));
Point p2 = projection.toScreenLocation(segment.get(1));
Point carPosOnSegment = new Point();
float denominator = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
// p1 and p2 are the same
if (Math.abs(denominator) <= 1E-10) {
markerProjection = segment.get(0);
} else {
float t = (carPosOnScreen.x * (p2.x - p1.x) - (p2.x - p1.x) * p1.x
+ carPosOnScreen.y * (p2.y - p1.y) - (p2.y - p1.y) * p1.y) / denominator;
carPosOnSegment.x = (int) (p1.x + (p2.x - p1.x) * t);
carPosOnSegment.y = (int) (p1.y + (p2.y - p1.y) * t);
markerProjection = projection.fromScreenLocation(carPosOnSegment);
}
return markerProjection;
}
Task 3 can be solved with code like that:
private float getBearing(LatLng begin, LatLng end) {
double dLon = (end.longitude - begin.longitude);
double x = Math.sin(Math.toRadians(dLon)) * Math.cos(Math.toRadians(end.latitude));
double y = Math.cos(Math.toRadians(begin.latitude))*Math.sin(Math.toRadians(end.latitude))
- Math.sin(Math.toRadians(begin.latitude))*Math.cos(Math.toRadians(end.latitude)) * Math.cos(Math.toRadians(dLon));
double bearing = Math.toDegrees((Math.atan2(x, y)));
return (float) bearing;
}
where begin and end is begin and end of current route path segment.
Task 4 can be solved with code like that:
as marker you can use vector drawable of north oriented arrow like that:
ic_up_arrow_circle.xml (also you can adjust transparency and colors):
<vector android:height="24dp" android:viewportHeight="93.934"
android:viewportWidth="93.934"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#8fFF0000"
android:pathData="m0,46.9666c0,25.939 21.028,46.967 46.967,46.967c25.939,-0 46.967,-21.028 46.967,-46.967c0,-25.939 -21.027,-46.967 -46.967,-46.967c-25.939,-0 -46.967,21.028 -46.967,46.967zM78.262,67.4396l-31.295,-16.845l-31.295,16.845l31.295,-51.614l31.295,51.614z"
/>
<path
android:fillColor="#FFFFFF"
android:pathData="M78.262,67.4396l-31.295,-16.845l-31.295,16.845l31.295,-51.614l31.295,51.614z"
/>
</vector>
and you can place it on map with code like that:
public Marker addDirectionMarker(LatLng latLng, float angle) {
Drawable circleDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_up_arrow_in_circle);
BitmapDescriptor markerIcon = getMarkerIconFromDrawable(circleDrawable, 150, 150);
return mGoogleMap.addMarker(new MarkerOptions()
.position(latLng)
.anchor(0.5f, 0.5f)
.rotation(angle)
.flat(true)
.icon(markerIcon)
);
}
where 150 is marker size in pixels. NB! You need flat marker for its rotation and tilt with map and 0.5f for move marker anchor exactly on its center point.
then you can show all of this on map:
...
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(userPosition)
.tilt(tilt)
.zoom(zoom)
.bearing(bearing)
.build();
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
...
But if you do only that marker of user current position appeared in the center of screen (because GoogleMap.moveCamera() sets the center exactly at .target()). So, to avoid it you need to shift down the map slightly - in that case user location marker should be appeared at the bottom of screen. For map center shift you need get current map center screen coordinates, then change y coordinate and get new screen center. Something like that:
...
LatLng mapCenter = mGoogleMap.getCameraPosition().target;
Projection projection = mGoogleMap.getProjection();
Point centerPoint = projection.toScreenLocation(mapCenter);
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
centerPoint.y = centerPoint.y - (int) (displayHeight / 4.5); // move center down for approx 22%
LatLng newCenterPoint = projection.fromScreenLocation(centerPoint);
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(newCenterPoint, zoom));
...
And with all of this stuff, for your route (with zoom = 15 and tilt = 50) you should get something like that:
As you can see, the route path is not exactly on road, so you need to get route path points more precisely than Directions API response. You can get that e.g. via 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 that answer. If your route path has more than points you need to split in into 100-points portions and process them separately (also Snap to Road API has 2500 request per day per user (IP) and 10 requests per sec. restriction).
And as Jaswant Singh answered you:
need to set a custom marker (with icon same as that blue arrow) on
your current location and move it to the new location every time there
is onLocationChanged() callback is called (Also animate the camera to
that new location).
Also, you need to select zoom and tilt properties according, for example, current user speed: when user drives faster tilt -> 0. And so on. It's not a simple task.
In addition to Jinesh’s answer,
If you still want to add that marker for development, you need to set a custom marker (with icon same as that blue arrow) on your current location and move it to the new location every time there is onLocationChanged() callback is called (Also animate the camera to that new location).
And tilt the map a little to get the exact look of the google maps navigation view, though you won’t get to use all the functionalities but it’s worth to give it a try.
I have the sample entries for
lat: 12.962899
lng 77.622330
I have drawn a circle in the map of a radius of 10000
Circle circle = map.addCircle(new CircleOptions()
.center(new LatLng(lat, lng))
.radius(10000)
.strokeColor(Color.RED)
.fillColor(Color.BLUE));
What I am trying to do: If i am given a new lat,lng value, I want to find out if the new position is within the circle radius or not.
How to achieve this
You can find out distance between 2 lat longs using this function
private float calculateLocationDifference(LatLng lastLocation, LatLng firstLocation) {
float[] dist = new float[1];
Location.distanceBetween(lastLocation.latitude, lastLocation.longitude, firstLocation.latitude, firstLocation.longitude, dist);
return dist[0];
}
pass center point of circle and new point to it . if distance is < 10000 then location is with in the circle
You can look at it as a coordinate system, let's say that you are putting the first point on the map.
You are adding a new point and want to check if it's inside your circle, because your circle has a radius of 10000 you can compare the distance between your points (new and old) and if this distance is > 10000 your new point is not inside the circle.
If you are using google maps you can use distanceBetween method to find the distance:
Location.distanceBetween(FirstLatitude, FirstLongitude, SecondLatitude, SecondLongitude, results);
You can use the following function:
It will return a boolean true or false
point = {lat: 12.962899, lng: 77.622330}
or you can get the points from click event
function pointInCircle(point, radius, center) {
return (google.maps.geometry.spherical.computeDistanceBetween(point, center) <= radius)
}
When sending a request I use the parameter radius=300 which should show places within a radius of 300 meters from my location:
StringBuilder stringBuilder = new StringBuilder("https://maps.googleapis.com/maps/api/place/nearbysearch/json?");
stringBuilder.append("location=").append(mLatitude).append(",").append(mLongitude);
stringBuilder.append("&keyword=пятерочка | магнит");
stringBuilder.append("&language=ru");
stringBuilder.append("&radius=300");
stringBuilder.append("&sensor=true");
The same Circle at a value of 300 meters from my location:
Circle circle = mMap.addCircle(new CircleOptions()
.center(latLng)
.radius(300).strokeColor(Color.argb(50, 255, 0, 0))
.fillColor(Color.argb(50, 255, 0, 0)));
The documentation says that the values are returned in meters that is in the request of 300 meters and in the Circle 300 meters but in fact I get this:
image from my device
Is it possible to make the radius displayed by the circle match the radius of the request?
P.S. I'm sorry for my bad english
This documentation says that -
Results inside of this region will be ranked higher than results
outside of the search circle; however, prominent results from outside
of the search radius may be included.
So, the conclusion is that some of the prominent location might be shown outside your defined radius. However, you can exclude those outside your boundary by implementing a loop after getting all results from the API.
Include Map-utils inside your dependencies -
implementation 'com.google.maps.android:android-maps-utils:0.5+'
Calculate distances from your centre for each place and place markers for those which are inside the boundary
for (Place place: allPlaces) {
float distance = (float) SphericalUtil.computeDistanceBetween(centreLatLng, place.getLatLng());
if (distance <= 300) {
Marker placeMarker = googleMap.addMarker(new MarkerOptions()
.position(place.getLatLng())
.title(place.getName())
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
.anchor(0.5 f, 1.0 f));
}
}
What I want to do is calculate a circle's radius by which the user is viewing the map.
I've written the solution so far as follows (which is true):
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
String TAG = AppController.TAG;
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
LatLng target = cameraPosition.target;
LatLng northEast = bounds.northeast;
LatLng southEast = bounds.southwest;
float[] results1 = new float[1];
float[] results2 = new float[1];
Location.distanceBetween(target.latitude, target.longitude, northEast.latitude, northEast.longitude, results1);
Location.distanceBetween(target.latitude, target.longitude, southEast.latitude, southEast.longitude, results2);
double distance = results1[0] > results2[0] ? results1[0] : results2[0];
Log.d(TAG, "onCameraChange:" + results1[0] + " " + results2[0]);
}
});
I'm facing two questions here:
1- First of all why the distane between the center and north east isn't equal to south west?
2- Is there any built in method to achieve the same result?
as you can see from the map above, the horizontal line (latitude) gets shorter the further away you get from the equator.
on the screen, the map is 'distorted' to flatten the curved surface of the earth, with +/- the same amount of longitude and latitude from the center of the view.
so unless you are taking measurements directly at the equator, you will have slight differences in the radius. for small distances, this is negligible.
if accuracy is not important, you can try using the smaller radius (if you want to draw a circle on the view that will not be partially hidden), or use the bigger radius (if you want to do calculations for places within that cicle).
I have a GoogleMap in my project. It's set in zoom level 18. I want to draw a line that is 1 meter in length. I saw and use a code Like this:
googleMap.addCircle(new CircleOptions()
.center(latLng1)
.radius(5)
.fillColor(Color.BLUE));
I gave it's radius in meters. how can I do it with a line?(polyLine doesn't have this options) a line with specific LatLng and specific direction(for example: Heading from north) and specific length?
I can specify direction by sin and cos.. but what can I do for length of the line?
For given point there is only one circle with given radius. But with lines the situation is a bit different. For given point there are infinite number of lines starting from this points and given length. Therefore you can't simple draw such line.
One way to do it is to pick a point on the circle with radius 1 meter and center your point. Here is a good example how to calculate point on a circle with given radius. Than just draw a line between the two points.
UPDATE:
This may help you how to find the LatLng points on the circle LatLng Points on circle on Google Map V2 in Android
To compute line end I use:
SphericalUtil.computeOffset(LatLng,lenght,heading);
To compute width in meters I use this:
public Double calcw(GoogleMap map,int ancho,LatLng p) {
float tilt = map.getCameraPosition().tilt;
CameraPosition old=map.getCameraPosition();
if(tilt!=0){
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(old.target) // Sets the center of the map to Mountain View
.zoom(old.zoom) // Sets the zoom
.bearing(old.bearing) // Sets the orientation of the camera to east
.tilt(0) // Sets the tilt of the camera to 30 degrees
.build();
map.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
Point g1 =map.getProjection().toScreenLocation(p);
Point g2=map.getProjection().toScreenLocation(SphericalUtil.computeOffset(p,ancho/10,0));
Double result=(distance(g1,g2));
//Log.e("PROJ1",Double.toString(distance(g1,g2)));
map.moveCamera(CameraUpdateFactory.newCameraPosition(old));
return result;
}
public double distance(Point a, Point b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
Use polyline to draw line something like below,
private ArrayList<LatLng> mLatlngs ;
PolylineOptions mPolylineOptions = new PolylineOptions();
mPolylineOptions.color(Color.RED);
mPolylineOptions.width(3);
mPolylineOptions.addAll(mLatlngs);
mGoogleMap.addPolyline(mPolylineOptions);