Android Google Map v2 draw static grids - android

I'm trying to draw grids on map only when zoom level is 18. I created grids with below code successfully. But i have one problem when I move map, new grids are created and there position change. You can see that in screenshot in right image I swiped map to left and grid lines are not same. I want fixed grids drawn. Code posted below images.
I'm calling below code from onCameraChange listener of GoogleMap.
double squareSize = 5.0d; //5.0d == 50feet
final double LONGITUDE_180 = 180.0d;
final double LATITUDE_90 = 90.0d;
final double PI = 3.141592653589793d;
Code removed its confidential.
drawPolyline function to draw single line
private void drawPolyline(LatLng latLng, LatLng latLng2) {
PolylineOptions polylineOptions = new PolylineOptions();
polylineOptions.add(latLng, latLng2);
polylineOptions.color(Color.argb(50, 0, 0, 100));
polylineOptions.width(3.5f);
polylineOptions.visible(true);
polylineOptions.geodesic(true);
Polyline polyline = googleMap.addPolyline(polylineOptions);
this.polylines.add(polyline);
}

double squareSize = 5.0d; //5.0d == 50feet
That is not square Size change it to.
double squareSize = 1.0d;
Then it will work fine but grid size will 3 meter as in what3words.
To make it 15 meters which is close to 50 feet divide 1546.0d and 37104.0d with 5.
37104.0d is equal 1546.0d * 24.
Better to extract all these values in variable and use them as.
double var1 = 1546.0d / 5; // replace with value 1546.0
double var2 = var1 * 24; // replace with value 37104.0d
Hope this will help you.

Related

Can I draw a curved dashed line in Google Maps Android?

In Google Maps from browser which has the curved dashed line look like this:
But when I implement Google Maps in my own Android project, it didn't show this line
How can I draw this line?
You can implement the curved dashed polyline between two points. For this purpose you can use Google Maps Android API Utility Library that has SphericalUtil class and apply some math in your code to create a polyline.
You have to include the utility library in your gradle as
compile 'com.google.maps.android:android-maps-utils:0.5'.
Please have a look at my sample Activity and function showCurvedPolyline (LatLng p1, LatLng p2, double k) that constructs dashed curved polyline between two points. The last parameter k defines curvature of the polyline, it can be >0 and <=1. In my example I used k=0.5
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private LatLng sydney1;
private LatLng sydney2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.getUiSettings().setZoomControlsEnabled(true);
// Add a marker in Sydney and move the camera
sydney1 = new LatLng(-33.904438,151.249852);
sydney2 = new LatLng(-33.905823,151.252422);
mMap.addMarker(new MarkerOptions().position(sydney1)
.draggable(false).visible(true).title("Marker in Sydney 1"));
mMap.addMarker(new MarkerOptions().position(sydney2)
.draggable(false).visible(true).title("Marker in Sydney 2"));
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney1, 16F));
this.showCurvedPolyline(sydney1,sydney2, 0.5);
}
private void showCurvedPolyline (LatLng p1, LatLng p2, double k) {
//Calculate distance and heading between two points
double d = SphericalUtil.computeDistanceBetween(p1,p2);
double h = SphericalUtil.computeHeading(p1, p2);
//Midpoint position
LatLng p = SphericalUtil.computeOffset(p1, d*0.5, h);
//Apply some mathematics to calculate position of the circle center
double x = (1-k*k)*d*0.5/(2*k);
double r = (1+k*k)*d*0.5/(2*k);
LatLng c = SphericalUtil.computeOffset(p, x, h + 90.0);
//Polyline options
PolylineOptions options = new PolylineOptions();
List<PatternItem> pattern = Arrays.<PatternItem>asList(new Dash(30), new Gap(20));
//Calculate heading between circle center and two points
double h1 = SphericalUtil.computeHeading(c, p1);
double h2 = SphericalUtil.computeHeading(c, p2);
//Calculate positions of points on circle border and add them to polyline options
int numpoints = 100;
double step = (h2 -h1) / numpoints;
for (int i=0; i < numpoints; i++) {
LatLng pi = SphericalUtil.computeOffset(c, r, h1 + i * step);
options.add(pi);
}
//Draw polyline
mMap.addPolyline(options.width(10).color(Color.MAGENTA).geodesic(false).pattern(pattern));
}
}
You can download a sample project with complete code from GitHub
https://github.com/xomena-so/so43305664
Just replace my API key with yours in the app/src/debug/res/values/google_maps_api.xml
Thanks to #xomena for the great answer. But it has just one little bug. Sometimes, it's arc becoming like a circle. I made a few debugging and see that, we are always using h + 90.0 for heading value at the 12. line of the method. We can solve this by changing that line like this:
LatLng c = SphericalUtil.computeOffset(p, x, h > 40 ? h + 90.0 : h - 90.0);
From now, you probably not encounter this problem again.
I had the same problem of crooked curved line when I am drawing curve in solid line. After few hours of searching on the internet and trying the different solution. Finally, I came up with the solution (NOT a proper solution but target can be achieved) by using Polygon instead of Polyline. I have modified the above method showCurvedPolyline() to draw a smooth curve and the curve direction will always be in upward direction. Below screenshots are the final result of my modified version.
fun drawCurveOnMap(googleMap: GoogleMap, latLng1: LatLng, latLng2: LatLng) {
//Adding marker is optional here, you can move out from here.
googleMap.addMarker(
MarkerOptions().position(latLng1).icon(BitmapDescriptorFactory.defaultMarker()))
googleMap.addMarker(
MarkerOptions().position(latLng2).icon(BitmapDescriptorFactory.defaultMarker()))
val k = 0.5 //curve radius
var h = SphericalUtil.computeHeading(latLng1, latLng2)
var d = 0.0
val p: LatLng?
//The if..else block is for swapping the heading, offset and distance
//to draw curve always in the upward direction
if (h < 0) {
d = SphericalUtil.computeDistanceBetween(latLng2, latLng1)
h = SphericalUtil.computeHeading(latLng2, latLng1)
//Midpoint position
p = SphericalUtil.computeOffset(latLng2, d * 0.5, h)
} else {
d = SphericalUtil.computeDistanceBetween(latLng1, latLng2)
//Midpoint position
p = SphericalUtil.computeOffset(latLng1, d * 0.5, h)
}
//Apply some mathematics to calculate position of the circle center
val x = (1 - k * k) * d * 0.5 / (2 * k)
val r = (1 + k * k) * d * 0.5 / (2 * k)
val c = SphericalUtil.computeOffset(p, x, h + 90.0)
//Calculate heading between circle center and two points
val h1 = SphericalUtil.computeHeading(c, latLng1)
val h2 = SphericalUtil.computeHeading(c, latLng2)
//Calculate positions of points on circle border and add them to polyline options
val numberOfPoints = 1000 //more numberOfPoints more smooth curve you will get
val step = (h2 - h1) / numberOfPoints
//Create PolygonOptions object to draw on map
val polygon = PolygonOptions()
//Create a temporary list of LatLng to store the points that's being drawn on map for curve
val temp = arrayListOf<LatLng>()
//iterate the numberOfPoints and add the LatLng to PolygonOptions to draw curve
//and save in temp list to add again reversely in PolygonOptions
for (i in 0 until numberOfPoints) {
val latlng = SphericalUtil.computeOffset(c, r, h1 + i * step)
polygon.add(latlng) //Adding in PolygonOptions
temp.add(latlng) //Storing in temp list to add again in reverse order
}
//iterate the temp list in reverse order and add in PolygonOptions
for (i in (temp.size - 1) downTo 1) {
polygon.add(temp[i])
}
polygon.strokeColor(Color.BLUE)
polygon.strokeWidth(12f)
polygon.strokePattern(listOf(Dash(30f), Gap(50f))) //Skip if you want solid line
googleMap.addPolygon(polygon)
temp.clear() //clear the temp list
}
Why are we adding temp list again in reverse order in PolygonOptions?
If we do not add LatLng again in PolygonOptions in reverse order, the googleMap.addPolygon() will close the path and the final result will be look like below.
TIPS:
If you want the curve is more in circular shape, increase the value of k. like k = 0.75
Thanks #xomena for the solution above. It works beautifully in most cases. But there needs some improvement:
When k == 1, x will be 0 and midpoint (p) will be the same as mid curve point (c). That means it should be a straight line, but then when you calculate the step, it's not Zero so the final result is a half-circle curve, which is ambiguous with the above condition.
When the curve is long enough, let say LIMIT = 1000km, each calculation in h1 + i * step inside the loop make a tiny error to the correct value (due to java double calculation error I guess). Then the start and end points of the polyline not exactly match with start and end coordinations. Moreover, the curvature of the polyline is unpredictable, base on my research, the reason can be the curvature of the Earth's surface that can make your calculation base on heading not correct.
My quick fix is to reset the step to 0 if k == 1 to make it a straight line. For the second problem, if the distance between 2 points is greater than a LIMIT of 1000km, drawing a straight line with k = 1 will be a safer choice to me.

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

adjust map to make 2 points visible

I have 2 LatLng, usually a few kilometers apart, and I want to zoom map such that both points are visible. My code below sometimes works, sometimes not (map is zoomed to level 3 then). I think the reason is that the loop is too fast for the map to make the necessary adjustments in time.
Is there a way to wait for the map to update before making the test again? To note that process runs on UI thread
double x1=from.latitude;
double x2= to.latitude;
double x1half=(x1+x2)/2;
x1=from.longitude;
x2= to.longitude;
double x2half=(x1+x2)/2;
LatLng pp=new LatLng(x1half, x2half);
map.moveCamera(CameraUpdateFactory.newLatLng(pp));//3.0
//now zoom map so that both points are in
double zoom=21;//V3.3
while(zoom>2){//
map.animateCamera( CameraUpdateFactory.zoomTo( (float) zoom ) );
if(isCurrentLocationVisible(from,map)&&isCurrentLocationVisible(to,map)) return;
/now we know map is still not visible. give it a bit more room
zoom--;
}
I'd advice you to use the newLatLngBounds() instead..You can create your bounds from your two geo points with the LatLngBounds.builder().
To the second part of your question..You could do somethink like map.post(new Runnable..) which could solve the concurrency issue.
You can use LatLngBounds like the following:
LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(locationOne);
builder.include(locationTwo);
LatLngBounds bounds = builder.build();
int padding = 0; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds,screen width, screen height, padding);
map.moveCamera(cu);

How to center the camera so that marker is at the bottom of screen? (Google map api V2 Android)

When a marker is clicked, the default behavior for the camera is to center it on screen, but because I usually have long text description in the info window, it's more convenient to actually change the camera position so that the marker is on the bottom of screen(making the info window in the center of screen). I think I should be able to do that by overriding onMarkerClick function like below (the default behavior is cancelled when this function return true)
#Override
public boolean onMarkerClick(final Marker marker) {
// Google sample code comment : We return false to indicate that we have not
// consumed the event and that we wish
// for the default behavior to occur (which is for the camera to move
// such that the
// marker is centered and for the marker's info window to open, if it
// has one).
marker.showInfoWindow();
CameraUpdate center=
CameraUpdateFactory.newLatLng(new LatLng(XXXX,
XXXX));
mMap.moveCamera(center);//my question is how to get this center
// return false;
return true;
}
Edit:
Problem solved using accepted answer's steps, codes below:
#Override
public boolean onMarkerClick(final Marker marker) {
//get the map container height
LinearLayout mapContainer = (LinearLayout) findViewById(R.id.map_container);
container_height = mapContainer.getHeight();
Projection projection = mMap.getProjection();
LatLng markerLatLng = new LatLng(marker.getPosition().latitude,
marker.getPosition().longitude);
Point markerScreenPosition = projection.toScreenLocation(markerLatLng);
Point pointHalfScreenAbove = new Point(markerScreenPosition.x,
markerScreenPosition.y - (container_height / 2));
LatLng aboveMarkerLatLng = projection
.fromScreenLocation(pointHalfScreenAbove);
marker.showInfoWindow();
CameraUpdate center = CameraUpdateFactory.newLatLng(aboveMarkerLatLng);
mMap.moveCamera(center);
return true;
}
Thanks for helping ^ ^
I might edit this answer later to provide some code, but what I think could work is this:
Get LatLng (LatLng M) of the clicked marker.
Convert LatLng M to a Point (Point M) using the Projection.toScreenLocation(LatLng) method. This gives you the location of the marker on the device's display (in pixels).
Compute the location of a point (New Point) that's above Point M by half of the map's height.
Convert the New Point back to LatLng and center the map on it.
Look here for my answer on how to get the map's height.
// googleMap is a GoogleMap object
// view is a View object containing the inflated map
// marker is a Marker object
Projection projection = googleMap.getProjection();
LatLng markerPosition = marker.getPosition();
Point markerPoint = projection.toScreenLocation(markerPosition);
Point targetPoint = new Point(markerPoint.x, markerPoint.y - view.getHeight() / 2);
LatLng targetPosition = projection.fromScreenLocation(targetPoint);
googleMap.animateCamera(CameraUpdateFactory.newLatLng(targetPosition), 1000, null);
I prefer Larry McKenzie's answer which it doesn't depend on screen projection (i.e. mProjection.toScreenLocation()), my guess is the projection resolution will go poor when the map zoom level is low, it made me sometimes couldn't get an accurate position. So, calculation based on google map spec will definitely solve the problem.
Below is an example code of moving the marker to 30% of the screen size from bottom.
zoom_lvl = mMap.getCameraPosition().zoom;
double dpPerdegree = 256.0*Math.pow(2, zoom_lvl)/170.0;
double screen_height = (double) mapContainer.getHeight();
double screen_height_30p = 30.0*screen_height/100.0;
double degree_30p = screen_height_30p/dpPerdegree;
LatLng centerlatlng = new LatLng( latlng.latitude + degree_30p, latlng.longitude );
mMap.animateCamera( CameraUpdateFactory.newLatLngZoom( centerlatlng, 15 ), 1000, null);
If you don't care about the map zooming in and just want the marker to be at the bottom see below, I think it's a simpler solution
double center = mMap.getCameraPosition().target.latitude;
double southMap = mMap.getProjection().getVisibleRegion().latLngBounds.southwest.latitude;
double diff = (center - southMap);
double newLat = marker.getPosition().latitude + diff;
CameraUpdate centerCam = CameraUpdateFactory.newLatLng(new LatLng(newLat, marker.getPosition().longitude));
mMap.animateCamera(centerCam);
I had the same issue, I tried the following perfectly working solution
mMap.setOnMarkerClickListener(new OnMarkerClickListener()
{
#Override
public boolean onMarkerClick(Marker marker)
{
int yMatrix = 200, xMatrix =40;
DisplayMetrics metrics1 = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics1);
switch(metrics1.densityDpi)
{
case DisplayMetrics.DENSITY_LOW:
yMatrix = 80;
xMatrix = 20;
break;
case DisplayMetrics.DENSITY_MEDIUM:
yMatrix = 100;
xMatrix = 25;
break;
case DisplayMetrics.DENSITY_HIGH:
yMatrix = 150;
xMatrix = 30;
break;
case DisplayMetrics.DENSITY_XHIGH:
yMatrix = 200;
xMatrix = 40;
break;
case DisplayMetrics.DENSITY_XXHIGH:
yMatrix = 200;
xMatrix = 50;
break;
}
Projection projection = mMap.getProjection();
LatLng latLng = marker.getPosition();
Point point = projection.toScreenLocation(latLng);
Point point2 = new Point(point.x+xMatrix,point.y-yMatrix);
LatLng point3 = projection.fromScreenLocation(point2);
CameraUpdate zoom1 = CameraUpdateFactory.newLatLng(point3);
mMap.animateCamera(zoom1);
marker.showInfoWindow();
return true;
}
});
I also faced this problem and fixed it in a hacky way. Let's declare a double field first. You need to adjust the value of it based on your requirement but I recommend you keep it between 0.001~0.009 otherwise you can miss your marker after the zoom animation.
double offset = 0.009
/*You can change it based on your requirement.
For left-right alignment please kindly keep it between 0.001~0.005 */
For bottom-centered:
LatLng camera = new LatLng(marker.getPosition().latitude+offset , marker.getPosition().longitude);
//Here "marker" is your target market on which you want to focus
For top-centered:
LatLng camera = new LatLng(marker.getPosition().latitude-offset , marker.getPosition().longitude);
For left-centered:
LatLng camera = new LatLng(marker.getPosition().latitude, marker.getPosition().longitude+offset);
For right-centered:
LatLng camera = new LatLng(marker.getPosition().latitude-offset , marker.getPosition().longitude-offset);
Then finally call the -
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(camera, yourZoom));
I did a little research and according to the documentation the map is square and at zero zoom level the width and height is 256dp and +/- 85 degrees N/S. The map width increases with zoom level so that width and height = 256 * 2N dp. Where N is the zoom level. So in theory you can determine the new location by getting the map height and dividing it by 170 total degrees to get dp per degree. Then get the screen height (or mapview height) in dp divided it by two and convert half view size to degrees of latitude. Then set your new Camera point that many degrees of latitude south. I can add code if you need it but I'm on a phone at the moment.
I have been trying out all the solutions proposed here, and came with a combined implementation of them. Considering, map projection, tilt, zoom and info window height.
It doesn't really place the marker at the bottom of the "camera view", but I think it accommodates the info window and the marker centre pretty well in most cases.
#Override
public boolean onMarkerClick(Marker marker) {
mIsMarkerClick = true;
mHandler.removeCallbacks(mUpdateTimeTask);
mLoadTask.cancel(true);
getActivity().setProgressBarIndeterminateVisibility(false);
marker.showInfoWindow();
Projection projection = getMap().getProjection();
Point marketCenter = projection.toScreenLocation(marker.getPosition());
float tiltFactor = (90 - getMap().getCameraPosition().tilt) / 90;
marketCenter.y -= mInfoWindowAdapter.getInfoWindowHeight() / 2 * tiltFactor;
LatLng fixLatLng = projection.fromScreenLocation(marketCenter);
mMap.animateCamera(CameraUpdateFactory.newLatLng(fixLatLng), null);
return true;
}
And then, your custom adapter would have to keep an instance of the info window inflated view, to be able to fetch its height.
public int getInfoWindowHeight(){
if (mLastInfoWindoView != null){
return mLastInfoWindoView.getMeasuredHeight();
}
return 0;
}
Anyone who's still looking to center the camera according to location coordinates
CameraPosition cameraPosition = new CameraPosition.Builder().target(new LatLng(Lat, Lon))
.zoom(15)
.bearing(0)
.tilt(45)
.build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
Credits
After some experiences i've implemented the solution that fine for me.
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
Point targetPoint = new Point(metrics.widthPixels / 2, metrics.heightPixels - metrics.heightPixels / 9);
LatLng targetLatlng = map.getProjection().fromScreenLocation(targetPoint);
double fromCenterToTarget = SphericalUtil.computeDistanceBetween(map.getCameraPosition().target, targetLatlng);
LatLng center = SphericalUtil.computeOffset(new LatLng(location.latitude, location.longitude), fromCenterToTarget/1.2, location.bearing);
CameraUpdate camera = CameraUpdateFactory.newLatLng(center);
map.animateCamera(camera, 1000, null);
Here. First, we pick the physical point on the screen where the marker should be moved. Then, convert it to LatLng. Next step - calculate distance from current marker position (in center) to target. Finally, we move the center of map straight from the marker to calculated distance.
I needed something similar, but with also zoom, tilt and bearing in the equation.
My problem is more complex, but the solution is a sort of generalization so it could be applied also to the problem in the question.
In my case, I update programmatically the position of a marker; the camera can be rotated, zoomed and tilted, but I want the marker always visible at a specific percentage of the View height from the bottom. (similar to the car marker position in the Maps navigation)
The solution:
I first pick the map location on the center of the screen and the location of a point that would be visible at a percentage of the View from the bottom (using map projection); I get the distance between these two points in meters, then I calculate a position, starting from the marker position, moving for the calculated distance towards the bearing direction; this new position is my new Camera target.
The code (Kotlin):
val movePointBearing =
if (PERCENTAGE_FROM_BOTTOM > 50) {
(newBearing + 180) % 360
} else newBearing
val newCameraTarget = movePoint(
markerPosition,
distanceFromMapCenter(PERCENTAGE_FROM_BOTTOM),
markerBearing)
with the movePoint method copied from here:
https://stackoverflow.com/a/43225262/2478422
and the distanceFromMapCenter method defined as:
fun distanceFromMapCenter(screenPercentage: Int): Float {
val screenHeight = mapFragment.requireView().height
val screenWith = mapFragment.requireView().width
val projection = mMap.projection
val center = mMap.cameraPosition.target
val offsetPointY = screenHeight - (screenHeight * screenPercentage / 100)
val offsetPointLocation = projection.fromScreenLocation(Point(screenWith / 2, offsetPointY))
return distanceInMeters(center, offsetPointLocation)
}
then just define a distanceInMeters method (for example using android Location class)
I hope the idea is clear without any further explanations.
One obvious limitation: it applies the logic using the current zoom and tilt, so it would not work if the new camera position requires also a different zoom_level and tilt.

How to zoom a MapView so it always includes two geopoints?

I have two geo points that vary intermittently, and want the MapView to resize and translate to make sure both points are always visible. I can easily re-centre the map on the point mid-way between the two, but how do I set the zoom level to ensure my two points are visible?
Check out his answer in a different post:
Google Map V2 how to set ZoomToSpan?
It solves it without much hassle. Note that you can only use that function AFTER the map has been loaded at least once. If not use function with specified map size on screen
LatLngBounds bounds = new LatLngBounds.Builder()
.include(new LatLng(CURRENTSTOP.latitude, CURRENTSTOP.longitude))
.include(new LatLng(MYPOSITION.latitude, MYPOSITION.longitude)).build();
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, displaySize.x, 250, 30));
Works like a charm and very dynamic!
Try this and see:
double latitudeSpan = Math.round(Math.abs(firstLat -
secLat));
double longitudeSpan = Math.round(Math.abs(firstLong -
secLong));
double currentLatitudeSpan = (double)mapView.getLatitudeSpan();
double currentLongitudeSpan = (double)mapView.getLongitudeSpan();
double ratio = currentLongitudeSpan/currentLatitudeSpan;
if(longitudeSpan < (double)(latitudeSpan+2E7) * ratio){
longitudeSpan = ((double)(latitudeSpan+2E7) * ratio);
}
mapController.zoomToSpan((int)(latitudeSpan*2), (int)(longitudeSpan*2));
mapView.invalidate();
in the new Maps API (v2) you can do it this way:
LatLng southwest = new LatLng(Math.min(laglng1.latitude, laglng2.latitude), Math.min(laglng1.longitude, laglng2.longitude));
LatLng northeast = new LatLng(Math.max(laglng1.latitude, laglng2.latitude), Math.max(laglng1.longitude, laglng2.longitude));
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(new LatLngBounds(southwest, northeast),500,500, 0));

Categories

Resources