Google Map CameraUpdate to center a certain location [closed] - android

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I went through this documentation for CameraUpdateFactory and I am interested in using newLatLngBounds method.
What am I trying to achieve?
Should see all the locations passed in as a list on the map.
Keeping the map centered at a say 1st element of the list.
What newLatLngBounds does is that it calculates the center on its own and doesn't have a public method to explicitly set it.
(Added to address clarity)
How to generate a LatLngBounds such that when invoking
CameraUpdateFactory.newLatLngBounds(latLngBounds, padding)
the result is the minimal-bounded map display centered on a point of my choosing rather than the center being computed by the LatLngBounds.Builder.

With a few calculations you can achieve the desired result. (Reference SphericalUtil for utility functions mentioned.)
Summary
(a) Compute the bounding box of all your points (NW and SW corner points) (X).
(b) Compute the max distance from desired centered point to east-west sides of X and north-south sides.
(c) Using max distance pair to compute the new lat-lng extents - your latLngBounds (Z).
(d) Use newLatLngBounds with computed bounds (Z).
Picture
Details
So to compute the bounding box loop over your list of points and keep the northern and southern most latitude and keep the western and eastern most longitude. This is X.
To compute (A) use SphericalUtil.computeDistanceBetween to compute the distance from desired center point to Xeast and Xwest and determine max of these two distances (A). Similarly compute the distance from desired center point to Xnorth and Xsouth and determine max of these two distances (B).
Use the computed max distances (A,B) to derive new points using SphericalUtil.computeOffset using center point and max distances and either 90 (or 270) and 0 (or 180) for headings (in the example diagram 270 to get A' and 0 to get B').
You now have the new bounding box (Z) to use as the LatLngBounds which is centered on your desired point.
Snippets
Assume pts is a list of points to be displayed.
Build bounding box X
LatLng Xnw = pts.get(0);
LatLng Xse = Xnw;
for (LatLng p : pts) {
if (p.latitude > Xnw.latitude) {
Xnw = new LatLng(p.latitude,Xnw.longitude);
}
if (p.latitude < Xse.latitude) {
Xse = new LatLng(p.latitude,Xse.longitude);
}
if (p.longitude < Xnw.longitude) {
Xnw = new LatLng(Xnw.latitude, p.longitude);
}
if (p.longitude > Xse.longitude) {
Xse = new LatLng(Xse.latitude, p.longitude);
}
}
Pick an arbitrary center
LatLng c = pts.get(3);
Compute Z
double dA0 = SphericalUtil.computeDistanceBetween(c, new LatLng(c.latitude, Xse.longitude));
double dA1 = SphericalUtil.computeDistanceBetween(c, new LatLng(c.latitude, Xnw.longitude));
double dB0 = SphericalUtil.computeDistanceBetween(c, new LatLng(Xse.latitude, c.longitude));
double dB1 = SphericalUtil.computeDistanceBetween(c, new LatLng(Xnw.latitude, c.longitude));
LatLng Znw = c;
LatLng Zse = c;
// Z longitude
double dA = Math.max(dA0, dA1);
Zse = new LatLng(Zse.latitude, SphericalUtil.computeOffset(c, dA, 90).longitude);
Znw = new LatLng(Znw.latitude, SphericalUtil.computeOffset(c, dA, 270).longitude);
double dB = Math.max(dB0, dB1);
Zse = new LatLng(SphericalUtil.computeOffset(c, dB, 180).latitude, Zse.longitude);
Znw = new LatLng(SphericalUtil.computeOffset(c, dB, 0).latitude, Znw.longitude);
Create latLngBounds by adding Z along with points.
LatLngBounds.Builder llbb = LatLngBounds.builder();
for (LatLng p : pts) {
llbb.include(p);
MarkerOptions mo = new MarkerOptions().position(p);
if (p.equals(c)) {
mo.title("C");
}
mMap.addMarker(mo);
}
llbb.include(Znw);
llbb.include(Zse);
Sample (centered on 'c')
Sample (without centering)

Related

Moving and showing the driving camera view on Google maps

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.

Calculate radius of visible map user in Google map

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).

Google Maps Android API: Draw polygon based on pixels known from GroundOverlay's PNG

I'm adding a PNG file as an own floorplan on top of Google Maps with the Google Maps Android API with the following code:
GroundOverlayOptions groundOverlayOptions = new GroundOverlayOptions();
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromAsset("building-d.png");
groundOverlayOptions.image(bitmapDescriptor);
groundOverlayOptions.anchor(0, 1);
LatLng buildingSW = new LatLng(47.014815, 8.305098);
LatLng buildingNE = new LatLng(47.015148, 8.305440);
LatLng buildingNW = new LatLng(47.015168, 8.305144);
LatLng buildingSE = new LatLng(47.014792, 8.305385);
Location swLoc = locationFromLatLng(buildingSW);
Location seLoc = locationFromLatLng(buildingSE);
Location nwLoc = locationFromLatLng(buildingNW);
Location neLoc = locationFromLatLng(buildingNE);
float angle = swLoc.bearingTo(nwLoc);
groundOverlayOptions.bearing(angle);
float width = swLoc.distanceTo(seLoc);
groundOverlayOptions.position(buildingSW, width);
mMap.addGroundOverlay(groundOverlayOptions);
Now I know that in the PNG there is a room at pixel coordinates 422/301, 708/301, 422/10 and 708/10 (those are the corners). I'd like to draw a polygon over the GroundOverlay covering that room. How should I do that? Do I need to convert my pixel-coordinates to LatLng and if so, how?
And by the way: Do I really have to use PNGs for GroundOverlays and is there no other supported vector-format like eps, pdf, ...?
Having seen your comment to the other answer, let me complete with some code:
Having set the "origin" in latlng 47.014816, 8.305098, you have to convert those coordinates to mercator and you can do something similar to the below:
public boolean initializeByTwoCouplesOfCooordsAndScale(double[] coordAreal, double[] coordBreal, double[] coordAvirtual, double[] coordBvirtual, double scalingFactor) {
if (coordAreal[0] == coordBreal[0] && coordAvirtual[1] == coordBvirtual[1] && coordAreal[1] == coordBreal[1] && coordAvirtual[0] == coordBvirtual[0]) {
System.err.println("Coordinates must not be the same!");
return false;
}
// aPoint is considered the "origin" point (0,0)
aPoint = coordAreal;
bPoint = coordAvirtual;
// now calculate the angle of the Real world coordinate for the points
double deltaRy = coordBreal[1] - coordAreal[1];
double deltaRx = coordBreal[0] - coordAreal[0];
double aR = Math.atan2(deltaRy, deltaRx);
// Now calculate the angle of the virtual world coordinates
double deltaVy = coordBvirtual[1] - coordAvirtual[1];
double deltaVx = coordBvirtual[0] - coordAvirtual[0];
double aV = Math.atan2(deltaVy, deltaVx);
// Set the transformation angle as the difference between the real and the virtual angles.
mPhi= (aR - aV);
// Set the scaling factor as the provided one
mScale = (scalingFactor);//scaling factor is in function below
// Calculate the scaling factor error correction using the distances of the two systems.
return true;
}
public static double getScalingFactor(double latitude) {
return 1 / (Math.cos(Math.toRadians(latitude)));
}
So you can call the method:
initializeByTwoCouplesOfCooordsAndScale(new double[]{MERCATOR_LNG,MERCATOR_LAT},//real coordinates for point A REMEMBER: LNG,LAT = x,y!
new double[]{0d,0d}, //Virual coordinates for point A
new double[]{MERCATOR_POINT_B_LNG, MERCATOR_POINT_B_LAT},//real point B
new double[]{X_METERS,Y_METERS},//coordinates in meters of point B in virtual map
getScalingFactor(47.014816));
then you can transform with this function:
public double[] transform(double[] coord) {
double[] transCoord = new double[2];
double xscaled = (coord[0] - bPoint[0]) * mScale; // XXX bPoint is the position of origin point in the "VIRTUAL" world. [0] is the x coordinate
double yscaled = (coord[1] - bPoint[1]) * mScale;
transCoord[0] = (xscaled * Math.cos(mPhi)) - (yscaled * Math.sin(mPhi)) + aPoint[0]; //aPoint is the point with real coordinates of origin!
transCoord[1] = (xscaled * Math.sin(mPhi)) + (yscaled * Math.cos(mPhi)) + aPoint[1];
return transCoord;
}
you can find online a way to convert latlng to mercator, it just a bunch of math ;)
You should work in this way:
Your indoor map positions should be relative to a specific point (BOTTOM-LEFT is 0,0 let's say), then all the other positions will be relative to that point in meters, so you will endup in values under 100meters usually.
Having this you have to "move, rotate and scale" the indoor map with respect to the world.
Just take a map on a desktop which is not LAT/LNG and find the coordinates for the same indoor points you have (usually we get real and indoor position for bottom-left and top-right positions) so you can find where it should be in the world.
Take a look also at the scaling factor (depending on the latitude, the map must be scaled)
https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor
We calculate that value by doing something like 1/cos(latitudeINradians)
public static double getScalingFactor(double latitude) {
return 1 / (Math.cos(Math.toRadians(latitude)));
}
Let me know if you can find a way, otherwise i will search and try to strip our code

How to show only a specific region/area in google map for android api v2 [Android]

How to show only a specific region/area in google map for android api v2?.all functionalities must be available in that region like zooming,dragging etc.
the all other areas must be cropped off or invisible. Is that possible???
I am using eclipse.help me please
This trick worked for me
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
//get latlong for corners for specified place
LatLng one = new LatLng(27.700769, 85.300140);
LatLng two = new LatLng(27.800769, 85.400140);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
//add them to builder
builder.include(one);
builder.include(two);
LatLngBounds bounds = builder.build();
//get width and height to current display screen
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
// 20% padding
int padding = (int) (width * 0.20);
//set latlong bounds
mMap.setLatLngBoundsForCameraTarget(bounds);
//move camera to fill the bound to screen
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding));
//set zoom to level to current so that you won't be able to zoom out viz. move outside bounds
mMap.setMinZoomPreference(mMap.getCameraPosition().zoom);
}
To show only specific area or region then you can use Polygons.
Polygon objects are similar to Polyline objects in that they consist of a series of coordinates in an ordered sequence. However, instead of being open-ended, polygons are designed to define regions within a closed loop with the interior filled in.
You can add a Polygon to the map in the same way as you add a Polyline. First create a PolygonOptions object and add some points to it. These points will form the outline of the polygon. You then add the polygon to the map by calling GoogleMap.addPolygon(PolygonOptions) which will return a Polygon object.
Visit this page on how to use Polygon.

Google Maps SphericalUtil computeOffset

I'm using the computeOffset() from the SpericalUtil in the google maps android library. The computeOffset takes the parameters:
public static LatLng computeOffset(LatLng from, double distance, double heading)
with the original coordinates of the first two markers I can use the computeOffset to generate another line after inputing the distance and the heading. My goal is to make parallel lines at a certain distance apart. But as you can see below it looks pretty good when the lines are nearly north and south, but when you go towards east and west it looks to me like the distance between the two lines isn't the same value. The end points appear to be the right distance but offset. How can I make it so all the lines look like the first image no matter what direction?
public void onMarkerDragEnd(Marker marker) {
showDistance();
LatLng markerCPosition = SphericalUtil.computeOffset(mMarkerA.getPosition(), 50, 90);
mMarkerC = getMap().addMarker(new MarkerOptions().position(markerCPosition).draggable(true).visible(false));
LatLng markerDPosition = SphericalUtil.computeOffset(mMarkerB.getPosition(), 50, 90);
mMarkerD = getMap().addMarker(new MarkerOptions().position(markerDPosition).draggable(true).visible(false));
updatePolyline();
}
At the moment you're just shifting the points sideways using a constant angle (90 degrees). If I understand you correctly and you want both lines parallel to each other separated by some distance you need to take into account the heading of the original A-B line like so:
double heading = SphericalUtil.computeHeading(posA, posB);
LatLng markerCPosition = SphericalUtil.computeOffset(posA, 50, heading + 90);
LatLng markerDPosition = SphericalUtil.computeOffset(posB, 50, heading + 90);

Categories

Resources