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.
Related
i'm trying to draw a circle on a static map by feeding getCircleAsPolyline with Location data and then encode with PolyUtil.encode, according to this SO answer https://stackoverflow.com/a/38100481/1520234.
unfortunately the output is not a circle but something strange like this:
can anybody kindly explain me why this happen and if it's possible to get a real circle and if so how?
thanks
EDIT
i apologize for not being very clear, i forgot to mention that actually my getCircleAsPolyline is slightly different from the one i linked since i used SphericalUtil.computeOffset to calculate the coordinates; anyway below is my getCircleAsPolyline function:
private static ArrayList<LatLng> getCircleAsPolyline(Location center, float radius) {
ArrayList<LatLng> circlePath = new ArrayList<>();
double step = 5.0;
double centerLatitude = center.getLatitude();
double centerLongitude = center.getLongitude();
for (double angle = 0.0; angle < 360.0; angle += step) {
LatLng circlePoint = SphericalUtil.computeOffset(
new LatLng(centerLatitude, centerLongitude),
radius,
(90.0 - angle + 360.0) % 360.0
);
circlePath.add(circlePoint);
}
return circlePath;
}
but result is the one showed in picture
For those who'll come here having the same problem, well I simply got it avoiding to encode the polyline: infact as long as I drop PolyUtil.encode and append points as | lat, long | I've been able to draw a perfect circle.
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.
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
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);
The code below doesn't work with Google Maps API v2. The polygons (outer and inner polygons) are drawn with the right border, but the fill color of the outer one is not drawn.
PolygonOptions polygonOptions = new PolygonOptions();
polygonOptions.add(outerCoordinates);
polygonOptions.addHole(Arrays.asList(innerCoordinates));
polygonOptions.fillColor(Color.BLUE);
polygonOptions.strokeWidth(1.0f);
Does anybody face the same problem?
Check whether there is a requirement that polygon coordinates have to be clockwise (or counterclockwise) ordered. Try to change the order.
The vertices must be added in counterclockwise order. Reference
I wrote a function to determinate if a List<LatLng> is clockwise. The code is an implementation of this answer:
public boolean isClockwise(List<LatLng> region) {
final int size = region.size();
LatLng a = region.get(size - 1);
double aux = 0;
for (int i = 0; i < size; i++) {
LatLng b = region.get(i);
aux += (b.latitude - a.latitude) * (b.longitude + a.longitude);
a = b;
}
return aux <= 0;
}
Before adding the polygon points put these three lines:
if (isClockwise(polygon)) {
Collections.reverse(polygon);
}