I have a set of points coming from a webservice that need to be displayed on a map.
I have a current solution working nicely for most cases, using the well-known LatLngBounds.Builder, CameraUpdateFactory.newLatLngBounds and map.animateCamera.
I have some cases which give problems though: when the points are too far away, the map centers on max zoom level on the barycenter of those points. For example: I have 10 points in France and 2 points in Hawai. Maps centers more or less on the caribeans at min zoom level. Hence on screen I got nothing shown, the user has to scroll to actually see something is there.
So my question is:
is there a way to get the map to zoom out far enough so that I can see all points (that would be prefered)
Or: which would be the best way to filter out those cases where just a few points are very far away from the majority and pick a set of point to zoom on (in my example, I would choose to zoom on the 10 points in France and forget about the ones in Hawai).
Put all the LatLng of the markers in the list and pass them to this method and at the last line in the newLatLngBounds(bounds, 50)) the 50 represent the padding between the map edge and the most outer marker in each side
public void centerIncidentRouteOnMap(List<LatLng> copiedPoints) {
double minLat = Integer.MAX_VALUE;
double maxLat = Integer.MIN_VALUE;
double minLon = Integer.MAX_VALUE;
double maxLon = Integer.MIN_VALUE;
for (LatLng point : copiedPoints) {
maxLat = Math.max(point.latitude, maxLat);
minLat = Math.min(point.latitude, minLat);
maxLon = Math.max(point.longitude, maxLon);
minLon = Math.min(point.longitude, minLon);
}
final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
mapFragment.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
}
Spotted an error in my previous code and decided to sit down and rewrite it.
I have done something similar before where I had ~4500 markers and wanted to select those within a certain distance of a specific location. Took that code and generalized it to be used with any kind of Markers.
The code that I will post below contains two methods that you can use:
selectLowDistanceMarkers
Measures distance between each and every marker and selects only those that does not have a long distance to any of the other markers. This requires a O(n+n^2) runtime due to the comparison between every marker and an examination afterwards.
getSurroundingMarkers
If you already know a postition that you would like to zoom in to, then this method does the same as above. This method is way less CPU heavy as it only has to do a O(n) run through all the markers and compare them to the given position.
private List<Marker> selectLowDistanceMarkers(List<Marker> markers,
int maxDistanceMeters) {
List<Marker> acceptedMarkers = new ArrayList<Marker>();
if (markers == null) return acceptedMarkers;
Map<Marker, Float> longestDist = new HashMap<Marker, Float>();
for (Marker marker1 : markers) {
// in this for loop we remember the max distance for each marker
// think of a map with a flight company's routes from an airport
// these lines is drawn for each airport
// marker1 being the airport and marker2 destinations
for (Marker marker2 : markers) {
if (!marker1.equals(marker2)) {
float distance = distBetween(marker1.getPosition(),
marker2.getPosition());
if (longestDist.containsKey(marker1)) {
// possible we have a longer distance
if (distance > longestDist.get(marker1))
longestDist.put(marker1, distance);
} else {
// first distance
longestDist.put(marker1, distance);
}
}
}
}
// examine the distances collected
for (Marker marker: longestDist.keySet()) {
if (longestDist.get(marker) <= maxDistanceMeters) acceptedMarkers.add(marker);
}
return acceptedMarkers;
}
private List<Marker> getSurroundingMarkers(List<Marker> markers,
LatLng origin, int maxDistanceMeters) {
List<Marker> surroundingMarkers = surroundingMarkers = new ArrayList<Marker>();
if (markers == null) return surroundingMarkers ;
for (Marker marker : markers) {
double dist = distBetween(origin, marker.getPosition());
if (dist < getHydrantsLoadradius()) {
surroundingMarkers.add(marker);
}
}
return surroundingMarkers;
}
private float distBetween(LatLng pos1, LatLng pos2) {
return distBetween(pos1.latitude, pos1.longitude, pos2.latitude,
pos2.longitude);
}
/** distance in meters **/
private float distBetween(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 3958.75;
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
* Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double dist = earthRadius * c;
int meterConversion = 1609;
return (float) (dist * meterConversion);
}
Again, use the well known LatLngBounds to determine how much you need to zoom after using one of the filtering algorithms above.
Based on some ideas from cYrixmorten, I have simplified the problem because I know the map can accomodate at least 4000km of surface. So here is the function to build the list of ignored webcams (then I simply ignore that webcam for the camera bounds computation but still add the marker so that it is on the map if the user moves).
private List<Webcam> buildIgnoredWebcamsList(List<Webcam> webcams) {
if (webcams == null || webcams.size() < 2) return Lists.newArrayList();
int webcamCount = webcams.size();
// Number of conflicts (distance > 4000 km) for the camera at index #
float averageConflictCount = 0;
int[] conflictCount = new int[webcamCount];
Arrays.fill(conflictCount, 0);
// Find number of conflicts between camera pairs
float[] distance = new float[1];
for (int i = 0; i < webcamCount - 1; ++i) {
Webcam a = webcams.get(i);
// We don't have to start from 0, compare a and b only once
for (int j = i + 1; j < webcamCount; ++j) {
Webcam b = webcams.get(j);
Location.distanceBetween(a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude(), distance);
// We have a conflict between a and b if they are more than 4000km away
if (distance[0] > 4000 * 1000) {
conflictCount[i] += 1;
conflictCount[j] += 1;
averageConflictCount += 2;
}
}
}
averageConflictCount /= webcamCount;
// Exclude all webcams with a number of conflicts greater than the average
List<Webcam> ignoredCamerasForBounds = Lists.newArrayList();
for (int i = 0; i < webcamCount; ++i) {
if (conflictCount[i] > averageConflictCount) {
ignoredCamerasForBounds.add(webcams.get(i));
}
}
return ignoredCamerasForBounds;
}
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int ancho = size.x;
int alto =size.y;
List<LatLng> copiedPoints = new ArrayList<LatLng>();
copiedPoints.add(origin);
copiedPoints.add(dest);
centerIncidentRouteOnMap(copiedPoints, ancho, alto);
....
public void centerIncidentRouteOnMap(List<LatLng> copiedPoints, int ancho, int alto) {
double minLat = Integer.MAX_VALUE;
double maxLat = Integer.MIN_VALUE;
double minLon = Integer.MAX_VALUE;
double maxLon = Integer.MIN_VALUE;
for (LatLng point : copiedPoints) {
maxLat = Math.max(point.latitude, maxLat);
minLat = Math.min(point.latitude, minLat);
maxLon = Math.max(point.longitude, maxLon);
minLon = Math.min(point.longitude, minLon);
}
final LatLngBounds bounds = new LatLngBounds.Builder().include(new LatLng(maxLat, maxLon)).include(new LatLng(minLat, minLon)).build();
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds,ancho, alto, 50));
}
Related
I am trying to generate random points on the map near a given location. I have a circle shape which surrounds the user location with a radius of 100, and I would like to generate random LatLng coordinates within this circle area. I have come up with the following function so far, but the point markers are still appearing outside the circle range.
double lat = location.getLatitude();
double lon = location.getLongitude();
for (int i = 0; i < markers.size(); i++) {
Marker mrk = markers.get(i);
Random random = new Random();
double radiusInDegrees =mCircle.getRadius();
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
double x = w * Math.cos(t);
double y = w * Math.sin(t);
// Adjust the x-coordinate for the shrinking of the east-west distances
double new_x = x / Math.cos(lat);
double newLongitude = new_x + lon;
double newLatitude = y + lat;
mrk.setPosition(new LatLng(newLatitude,newLongitude));
}
with the help of this
https://gis.stackexchange.com/a/68275
I am able to make a function which generate random LatLng point within certain radius, where radius is in meter.
public LatLng getRandomLocation(LatLng point, int radius) {
List<LatLng> randomPoints = new ArrayList<>();
List<Float> randomDistances = new ArrayList<>();
Location myLocation = new Location("");
myLocation.setLatitude(point.latitude);
myLocation.setLongitude(point.longitude);
//This is to generate 10 random points
for(int i = 0; i<10; i++) {
double x0 = point.latitude;
double y0 = point.longitude;
Random random = new Random();
// Convert radius from meters to degrees
double radiusInDegrees = radius / 111000f;
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
double x = w * Math.cos(t);
double y = w * Math.sin(t);
// Adjust the x-coordinate for the shrinking of the east-west distances
double new_x = x / Math.cos(y0);
double foundLatitude = new_x + x0;
double foundLongitude = y + y0;
LatLng randomLatLng = new LatLng(foundLatitude, foundLongitude);
randomPoints.add(randomLatLng);
Location l1 = new Location("");
l1.setLatitude(randomLatLng.latitude);
l1.setLongitude(randomLatLng.longitude);
randomDistances.add(l1.distanceTo(myLocation));
}
//Get nearest point to the centre
int indexOfNearestPointToCentre = randomDistances.indexOf(Collections.min(randomDistances));
return randomPoints.get(indexOfNearestPointToCentre);
}
The purpose of for loop is just to ensure to get nearest random point, as I have seen points were getting out of circle as I am increasing the radius. You may remove loop.
This answer should help. It looks like what you have execpt for converting the radiusfrom meters to degrees.
// Convert radius from meters to degrees
double radiusInDegrees = radius / 111000f;
See link here.
I'm trying to figure out how best to do this, I have a map with one Polygon drawn on it. Since it doesn't seem as though the Google Maps API V2 has a touch detection on a Polygon. I was wonder if it is possible to detect whether the touch point is inside the Polygon? If so then how, my main goal is to outline a state on a map and when the user taps that state it will show more details inside a custom view. As of now I am able to capture the MapOnClick of the map but when the user taps inside the Polygon I want the polygon.getID() set on the Toast. I am a newbie so I apologize if I am not clear enough.
googleMap.setOnMapClickListener(new OnMapClickListener()
{
public void onMapClick(LatLng point)
{
boolean checkPoly = true;
Toast.makeText(MainActivity.this,"The Location is outside of the Area", Toast.LENGTH_LONG).show();
}
});
}
}
catch (Exception e) {
Log.e("APP","Failed", e);
}
Ok this is what I have semi-working so far
private boolean rayCastIntersect(LatLng tap, LatLng vertA, LatLng vertB) {
double aY = vertA.latitude;
double bY = vertB.latitude;
double aX = vertA.longitude;
double bX = vertB.longitude;
double pY = tap.latitude;
double pX = tap.longitude;
if (aY > bY) {
aX = vertB.longitude;
aY = vertB.latitude;
bX = vertA.longitude;
bX = vertA.latitude;
}
System.out.println("aY: "+aY+" aX : "+aX);
System.out.println("bY: "+bY+" bX : "+bX);
if (pX < 0) pX += 360;
if (aX < 0) aX += 360;
if (bX < 0) bX += 360;
if (pY == aY || pY == bY) pY += 0.00000001;
if ((pY > bY || pY < aY) || (pX > Math.max(aX, bX))) return false;
if (pX < Math.min(aX, bX))
return true;
// }
double m = (aX != bX) ? ((bY - aY) / (bX - aX)) : aX;
double bee = (aX != pX) ? ((pY - aY) / (pX - aX)) : aX;
double x = (pY - bee) / m;
return x > pX;
}
}
The issue that I am having is the touch is true to the left of each polygon until it reaches another one. What's wrong with my algorithm that would cause this issue? Any help would be appreciated.
The problem you're trying to solve is the Point in Polygon test.
To help visualize the concept of Ray Casting:
Draw a Polygon on a piece of paper. Then, starting at any random point, draw a straight line to the right of the page. If your line intersected with your polygon an odd number of times, this means your starting point was inside the Polygon.
So, how do you do that in code?
Your polygon is comprised of a list of vertices: ArrayList<Geopoint> vertices. You need to look at each Line Segment individually, and see if your Ray intersects it
private boolean isPointInPolygon(Geopoint tap, ArrayList<Geopoint> vertices) {
int intersectCount = 0;
for(int j=0; j<vertices.size()-1; j++) {
if( rayCastIntersect(tap, vertices.get(j), vertices.get(j+1)) ) {
intersectCount++;
}
}
return (intersectCount%2) == 1); // odd = inside, even = outside;
}
private boolean rayCastIntersect(Geopoint tap, Geopoint vertA, Geopoint vertB) {
double aY = vertA.getLatitude();
double bY = vertB.getLatitude();
double aX = vertA.getLongitude();
double bX = vertB.getLongitude();
double pY = tap.getLatitude();
double pX = tap.getLongitude();
if ( (aY>pY && bY>pY) || (aY<pY && bY<pY) || (aX<pX && bX<pX) ) {
return false; // a and b can't both be above or below pt.y, and a or b must be east of pt.x
}
double m = (aY-bY) / (aX-bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!
return x > pX;
}
The Google Maps Support library now has a static method that does this check for you:
PolyUtil.containsLocation(LatLng point, List<LatLng>polygon, boolean geodesic);
Although the docs don't mention it explicitly in the guide the method is there
Maps Support Library docs
With the release of Google Play Services 8.4.0, the Maps API has included support for adding an OnPolygonClickListener to Polygons. Both polygons, polylines and overlays support similar events.
You just need to call GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener) to set it up, and correspondingly for the other listeners (setOnPolylineClickListener, &c):
map.setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() {
#Override
public void onPolygonClick(Polygon polygon) {
// Handle click ...
}
});
Although a bit late, it solves this use case quite nicely.
Though user1504495 has answered in short as I have used it. But instead of using whole Map Utility Library Use this methods.
From your activity class pass params accordingly:
if (area.containsLocation(Touchablelatlong, listLatlong, true))
isMarkerINSide = true;
else
isMarkerINSide = false;
and put following in a Separate class :
/**
* Computes whether the given point lies inside the specified polygon.
* The polygon is always cosidered closed, regardless of whether the last point equals
* the first or not.
* Inside is defined as not containing the South Pole -- the South Pole is always outside.
* The polygon is formed of great circle segments if geodesic is true, and of rhumb
* (loxodromic) segments otherwise.
*/
public static boolean containsLocation(LatLng point, List<LatLng> polygon, boolean geodesic) {
final int size = polygon.size();
if (size == 0) {
return false;
}
double lat3 = toRadians(point.latitude);
double lng3 = toRadians(point.longitude);
LatLng prev = polygon.get(size - 1);
double lat1 = toRadians(prev.latitude);
double lng1 = toRadians(prev.longitude);
int nIntersect = 0;
for (LatLng point2 : polygon) {
double dLng3 = wrap(lng3 - lng1, -PI, PI);
// Special case: point equal to vertex is inside.
if (lat3 == lat1 && dLng3 == 0) {
return true;
}
double lat2 = toRadians(point2.latitude);
double lng2 = toRadians(point2.longitude);
// Offset longitudes by -lng1.
if (intersects(lat1, lat2, wrap(lng2 - lng1, -PI, PI), lat3, dLng3, geodesic)) {
++nIntersect;
}
lat1 = lat2;
lng1 = lng2;
}
return (nIntersect & 1) != 0;
}
/**
* Wraps the given value into the inclusive-exclusive interval between min and max.
* #param n The value to wrap.
* #param min The minimum.
* #param max The maximum.
*/
static double wrap(double n, double min, double max) {
return (n >= min && n < max) ? n : (mod(n - min, max - min) + min);
}
/**
* Returns the non-negative remainder of x / m.
* #param x The operand.
* #param m The modulus.
*/
static double mod(double x, double m) {
return ((x % m) + m) % m;
}
/**
* Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment
* (lat1, lng1) to (lat2, lng2).
* Longitudes are offset by -lng1; the implicit lng1 becomes 0.
*/
private static boolean intersects(double lat1, double lat2, double lng2,
double lat3, double lng3, boolean geodesic) {
// Both ends on the same side of lng3.
if ((lng3 >= 0 && lng3 >= lng2) || (lng3 < 0 && lng3 < lng2)) {
return false;
}
// Point is South Pole.
if (lat3 <= -PI/2) {
return false;
}
// Any segment end is a pole.
if (lat1 <= -PI/2 || lat2 <= -PI/2 || lat1 >= PI/2 || lat2 >= PI/2) {
return false;
}
if (lng2 <= -PI) {
return false;
}
double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2;
// Northern hemisphere and point under lat-lng line.
if (lat1 >= 0 && lat2 >= 0 && lat3 < linearLat) {
return false;
}
// Southern hemisphere and point above lat-lng line.
if (lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat) {
return true;
}
// North Pole.
if (lat3 >= PI/2) {
return true;
}
// Compare lat3 with latitude on the GC/Rhumb segment corresponding to lng3.
// Compare through a strictly-increasing function (tan() or mercator()) as convenient.
return geodesic ?
tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) :
mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3);
}
/**
* Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0.
* See http://williams.best.vwh.net/avform.htm .
*/
private static double tanLatGC(double lat1, double lat2, double lng2, double lng3) {
return (tan(lat1) * sin(lng2 - lng3) + tan(lat2) * sin(lng3)) / sin(lng2);
}
/**
* Returns mercator Y corresponding to latitude.
* See http://en.wikipedia.org/wiki/Mercator_projection .
*/
static double mercator(double lat) {
return log(tan(lat * 0.5 + PI/4));
}
/**
* Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0.
*/
private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) {
return (mercator(lat1) * (lng2 - lng3) + mercator(lat2) * lng3) / lng2;
}
Here's a full working example to know if a touch happened on a polygon. Some of the answers are more complicated than they need to be. This solution uses the "android-maps-utils"
// compile 'com.google.maps.android:android-maps-utils:0.3.4'
private ArrayList<Polygon> polygonList = new ArrayList<>();
private void addMyPolygons() {
PolygonOptions options = new PolygonOptions();
// TODO: make your polygon's however you want
Polygon polygon = googleMap.addPolygon(options);
polygonList.add(polygon);
}
#Override
public void onMapClick(LatLng point) {
boolean contains = false;
for (Polygon p : polygonList) {
contains = PolyUtil.containsLocation(point, p.getPoints(), false);
if (contains) break;
}
Toast.makeText(getActivity(), "Click in polygon? "
+ contains, Toast.LENGTH_SHORT).show();
}
#Override
protected void onMapReady(View view, Bundle savedInstanceState) {
googleMap.setOnMapClickListener(this);
addMyPolygons();
}
I know I am posting this very late but I had some issue with the answer posted here, so I studied both the top answers and an article (which I think is the origin of this method) and modified Matt Answer to compile something that works best for me.
Problem with Matt Answer: It doesn't calculate the last line of polygon (i.e. one created by the last vertex and the first vertex)
Problem with Dwill Answer: It seems complex and daunting especially when you are already frustrated on how to make things work
Other checks I have added:
Checked if a polygon is actually created
Checked if any side of polygon is parallel to y-axis
I have tried to comment and explain as much I as I could hope this would be helpful for someone
One more thing, this is written in Dart and mainly focused on finding if current position is inside a geofence.
Future<bool> checkIfLocationIsInsideBoundary({
required LatLng positionToCheck,
required List<LatLng> boundaryVertices,
}) async {
// If there are less than 3 points then there will be no polygon
if (boundaryVertices.length < 3) return false;
int intersectCount = 0;
// Check Ray-cast for lines created by all the vertices in our List
for (int j = 0; j < boundaryVertices.length - 1; j++) {
if (_rayCastIntersect(
positionToCheck,
boundaryVertices[j],
boundaryVertices[j + 1],
)) {
intersectCount++;
}
}
// Check for line created by the last vertex and the first vertex of the List
if (_rayCastIntersect(
positionToCheck,
boundaryVertices.last,
boundaryVertices.first,
)) {
intersectCount++;
}
// If our point is inside the polygon they will always intersect odd number of
// times, else they will intersect even number of times
return (intersectCount % 2) == 1; // odd = inside, even = outside
}
bool _rayCastIntersect(LatLng point, LatLng vertA, LatLng vertB) {
final double aY = vertA.latitude;
final double bY = vertB.latitude;
final double aX = vertA.longitude;
final double bX = vertB.longitude;
final double pY = point.latitude;
final double pX = point.longitude;
// If vertices A and B are both above our point P then obviously the line made
// by A and B cannot intersect with ray-cast of P. Note: Only y-coordinates of
// each points can be used to check this.
if (aY > pY && bY > pY) return false;
// If vertices A and B are both below our point P then obviously the line made
// by A and B cannot intersect with ray-cast of P. Note: Only y-coordinates of
// each points can be used to check this.
if (aY < pY && bY < pY) return false;
// Since we will be casting ray on east side from our point P, at least one of
// the vertex (either A or B) must be east of P else line made by A nd B
// cannot intersect with ray-cast of P. Note: Only x-coordinates of each
// points can be used to check this.
if (aY < pY && bY < pY) return false;
// If line made by vertices is parallel to Y-axis then we will get
// 'Divided by zero` exception when calculating slope. In such case we can
// only check if the line is on the east or the west relative to our point. If
// it is on the east we count is as intersection. Note: we can be sure our
// ray-cast will intersect the line because it is a vertical line, our
// ray-cast is horizontal and finally we already made sure that both the
// vertices are neither above nor below our point. Finally, since `aX == bX`
// we can check if either aX or bX is on the right/east of pX
if (aX == bX) return aX > pX;
// Calculate slope of the line `m` made by vertices A and B using the formula
// `m = (y2-y1) / (x2-x1)`
final double m = (aY - bY) / (aX - bX); // Rise over run
// Calculate the value of y-intersect `b` using the equation of line
final double b = aY - (aX * m); // y = mx + b => b = y - mx
// Now we translate our point P along X-axis such that it intersects our line.
// This means we can pluck y-coordinate of our point P into the equation of
// our line and calculate a new x-coordinate
final double x = (pY - b) / m; // y = mx + b => x = (y - b) / m
// Till now we have only calculated this new translated point but we don't
// know if this point was translated towards west(left) of towards
// east(right). This can be determined in the same way as we have done above,
// if the x-coordinate of this new point is greater than x-coordinate of our
// original point then it has shifted east, which means it has intersected our
// line
return x > pX;
}
Just for consistency - onMapClick is not called when user taps on a polygon (or other overlay), and it's mentioned in javadoc.
I made a workaround to intercept taps events before MapFragment handles them, and project point to map coordinates and check if the point is inside any polygon, as suggested in other answer.
See more details here
I am new to programming android and having a problem with an map application I am working on. The app allows for circles to be place on the map through a click and if the current location is inside the circle a message is displayed, also if outside the circle a different message is displayed. The problem is during the onStart check of the circles it only detects inside or outside of the last created circle instead of all available ones. I am not sure what is causing this problem. Code Snippet follows:
// Opening the sharedPreferences object
sharedPreferences = getSharedPreferences("location", 0);
// Getting number of locations already stored
locationCount = sharedPreferences.getInt("locationCount", 0);
// Getting stored zoom level if exists else return 0
//String zoom = sharedPreferences.getString("zoom", "0");
// If locations are already saved
if(locationCount!=0){
String lat = "";
String lng = "";
// Iterating through all the locations stored
for(int i=0;i<locationCount;i++){
// Getting the latitude of the i-th location
lat = sharedPreferences.getString("lat"+i,"0");
// Getting the longitude of the i-th location
lng = sharedPreferences.getString("lng"+i,"0");
double latitude = Double.parseDouble(lat);
double longitude = Double.parseDouble(lng);
startCircle = googleMap.addCircle(new CircleOptions().center(new LatLng (latitude, longitude)).radius(CIRCLE_RADIUS).fillColor(0x55888888));
}
}
public void onStart(){
super.onStart();
//Create a criteria object to retrieve provider
Criteria criteria = new Criteria();
// Set accuracy of criteria to address level
criteria.setAccuracy(Criteria.ACCURACY_FINE);
//Get the name of the best provider
String provider = locationManager.getBestProvider(criteria, true);
//Get Current Location
Location myLocation = locationManager.getLastKnownLocation(provider);
double lat = myLocation.getLatitude();
double lon = myLocation.getLongitude();
LatLng latlng = new LatLng(lat,lon);
if(startCircle == null){
return;
}
else{
float[] distance = new float[2];
marker = googleMap.addMarker(new MarkerOptions().position(latlng).visible(false));
myLocation.distanceBetween( marker.getPosition().latitude, marker.getPosition().longitude,
startCircle.getCenter().latitude, startCircle.getCenter().longitude, distance);
if( distance[0] < startCircle.getRadius()){
Toast.makeText(getBaseContext(), "Inside", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getBaseContext(), "Outside", Toast.LENGTH_LONG).show();
}
}
}
The problem is during the onStart check of the circles it only detects inside or outside of the last created circle instead of all available ones. I am not sure what is causing this problem.
the problem is when you create a circle you are overwriting the last circle created so the startCircle is always the last one created. You will need to keep a list of all the circles you plot on the map;
to look how to check if a point is inside an object I would check out this link as it prooved very useful to me when I needed to do this
How can I determine whether a 2D Point is within a Polygon?
in Javascript I used:
google.maps.geometry.poly.containsLocation(pos, selectedPolygon);
This method available in google-maps API 3, (Sounds like you can't)
But try to write some math function based on circle area.
it really easy. Calculate distance between 2 coordinates:
public static float distFrom2LocationsInMeter() {
double lat1 = ...;
double lng1 = ...;
double lat2 = ...;
double lng2 =...;
//lat1 /= 1000000; // sometimes Android returns location in 10^6 form
//lng1 /= 1000000;
//lat2 /= 1000000;
// lng2 /= 1000000;
double earthRadius = 3958.75;
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
int meterConversion = 1609;
return Float.valueOf((float)(dist * meterConversion ));
}
After just check your radius:
if R > distFrom2LocationsInMeter() // you inside
if R < distFrom2LocationsInMeter() // you outside
Toast.makeText(MapActivity.this,String.valueOf(isInside(latitude_location_place, longitude_location_place,0.002,map.getMyLocation().getLatitude(),map.getMyLocation().getLongitude())) , Toast.LENGTH_SHORT).show();
public boolean isInside(double circle_x, double circle_y,
double rad, double x, double y) {
map.addCircle(new CircleOptions()
.center(new LatLng(circle_x, circle_y))
.radius(200)
//its about 20 meter
// you most set rad=0.002 for 20 meter
.strokeColor(Color.RED)
.fillColor(R.color.color_circle));
map.addMarker(new MarkerOptions().position(new LatLng(x, y)));
if ((x - circle_x) * (x - circle_x) +
(y - circle_y) * (y - circle_y) <= rad*rad)
return true;
else
return false;
}
Example:
Toast.makeText(MapActivity.this,String.valueOf(isInside(latitude_location_place, longitude_location_place,0.002,map.getMyLocation().getLatitude(),map.getMyLocation().getLongitude())) , Toast.LENGTH_SHORT).show();
public boolean isInside(double circle_x, double circle_y,
double rad, double x, double y) {
map.addCircle(new CircleOptions()
.center(new LatLng(circle_x, circle_y))
.radius(200)
//its about 20 meter
// you most set rad=0.002 for 20 meter
.strokeColor(Color.RED)
.fillColor(R.color.color_circle));
map.addMarker(new MarkerOptions().position(new LatLng(x, y)));
if ((x - circle_x) * (x - circle_x) +
(y - circle_y) * (y - circle_y) <= rad*rad)
return true;
else
return false;
}
I have lots of data to display on the Google Map but what I want is only put those elements which falls into current visible space on the Google Map.
How to do this? Provide some useful tutorial links.
Any help would be greatly appreciated.
Please check this link : https://github.com/ketankpatel/android/tree/master/PinDropAnimation
It drops pin in the visible area.
Snippet is here:
private void putEventOverlay() {
if (mapData.getZoomLevel() < 3) {
mapData.getController().setZoom(3);
}
final MyLocationOverlay myLocationOverlay = new MyLocationOverlay(HomeActivity.this, mapData);
mapData.getOverlays().add(myLocationOverlay);
myLocationOverlay.enableMyLocation();
GeoPoint center = mapData.getMapCenter();
Integer latspan = mapData.getLatitudeSpan();
Integer lonspan = mapData.getLongitudeSpan();
Integer maxLat = center.getLatitudeE6() + (latspan / 2);
Integer maxLon = center.getLongitudeE6() + (lonspan / 2);
Integer minLat = center.getLatitudeE6() - (latspan / 2);
Integer minLon = center.getLongitudeE6() - (lonspan / 2);
double maxLatitude, minLatitude, maxLongitude, minLongitude;
maxLatitude = maxLat / 1E6;
minLatitude = minLat / 1E6;
maxLongitude = maxLon / 1E6;
minLongitude = minLon / 1E6;
Drawable marker = getResources().getDrawable(R.drawable.marker);
EventDetailModel tempDetailModel = null;
ArrayList<EventDetailModel> tempDetailList = dbHelper.getMapSortedEventData(minLatitude, maxLatitude, minLongitude,maxLongitude);
if (tempDetailList.size() != 0) {
mapData.getOverlays().clear();
}
for (int i = 0; i < tempDetailList.size(); i++) {
tempDetailModel = (EventDetailModel) tempDetailList.get(i);
MapItemizedOverlay itemizedOverlay = new MapItemizedOverlay(marker);
itemizedOverlay.addOverlay(HomeActivity.this, tempDetailModel);
mapData.getOverlays().add(itemizedOverlay);
}
mapData.invalidate();
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
putEventOverlay();
}
return super.dispatchTouchEvent(ev);
}
Vandit
I'm assuming by 'lots of data' you mean some markers to plot. Get the current map bounds. As you're looping over your data, adding markers, use bounds.contains() to see if the marker's position would be visible. If so, add it to the map.
I am drawing the circle on map with specifying the radius and it'll draw the circle successfully. But when I change the size of circle using seekbar I need to feet the circle in screen and zoom the map of that level, I have not idea about this, need your guideline thank you.
We can also get the zoom level for map from the drawn circle
Circle circle = googleMap.addCircle(circleOptions);
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
circleOptions.getCenter(), getZoomLevel(circle)
// Methode for zoomlevel
public int getZoomLevel(Circle circle) {
int zoomLevel = 11;
if (circle != null) {
double radius = circle.getRadius() + circle.getRadius() / 2;
double scale = radius / 500;
zoomLevel = (int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel;
}
build.gradle:
dependencies {
compile 'com.google.maps.android:android-maps-utils:0.4+'
}
public static LatLngBounds getLatLngBoundsFromCircle(Circle circle){
if(circle != null){
return new LatLngBounds.Builder()
.include(SphericalUtil.computeOffset(circle.getCenter(), circle.getRadius() * Math.sqrt(2), 45))
.include(SphericalUtil.computeOffset(circle.getCenter(), circle.getRadius() * Math.sqrt(2), 225))
.build();
}
return null;
}
map.animateCamera( CameraUpdateFactory
.newLatLngBounds(MapUtils.getLatLngBoundsFromCircle(mapCircle),20) );
After long time I found the solution from somewhere.
here is the method which was giving me the min lat/lng and max lat/lng.
Based on this I have getting the latspan and longspan.
public void boundingCoordinates(double distance, double radius) {
if (radius < 0d || distance < 0d)
throw new IllegalArgumentException();
// angular distance in radians on a great circle
double radDist = distance / radius;
double radLat = Math.toRadians(gp.getLatitudeE6()/1e6); // here is your single point latitude gp.getLatitude
double radLon = Math.toRadians(gp.getLongitudeE6()/1e6); // here is your single point longitude gp.getlongitude
double minLat = radLat - radDist;
double maxLat = radLat + radDist;
double minLon, maxLon;
if (minLat > MIN_LAT && maxLat < MAX_LAT) {
double deltaLon = Math.asin(Math.sin(radDist) /Math.cos(radLat));
minLon = radLon - deltaLon;
if (minLon < MIN_LON)
minLon += 2d * Math.PI;
maxLon = radLon + deltaLon;
if (maxLon > MAX_LON)
maxLon -= 2d * Math.PI;
} else {
// a pole is within the distance
minLat = Math.max(minLat, MIN_LAT);
maxLat = Math.min(maxLat, MAX_LAT);
minLon = MIN_LON;
maxLon = MAX_LON;
}
minLat = Math.toDegrees(minLat);
minLon = Math.toDegrees(minLon);
maxLat = Math.toDegrees(maxLat);
maxLon = Math.toDegrees(maxLon);
minGeo = new GeoPoint((int)(minLat*1e6),(int)(minLon*1e6));
maxGeo = new GeoPoint((int)(maxLat*1e6),(int)(maxLon*1e6));
}
now you pass the distance in any unit as per that you have to pass the radius of earth for example if you pass 2 km then the radius of earth is in km say 6370.997.
you can try it, its cool thing
In my code I am adding a transparent circle around a marker which has dynamic radius and zooming the map camera so it fit to screen.
it is working 100% fine in my project.
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.setOnMarkerClickListener(this);
MapsInitializer.initialize(Objects.requireNonNull(getContext()));
// Add a marker on Property location
LatLng propertyLatlng = new LatLng(getLatitude(), getLongitude());
// draw transparent blue circle around marker
try {
CircleOptions circleOptions = new CircleOptions()
.center(propertyLatlng)
.radius(Double.parseDouble(radius) / 0.00062137)
.strokeColor(BLUE_TRANSPARENT)
.strokeWidth(0)
.fillColor(BLUE_TRANSPARENT);
Circle circle = mMap.addCircle(circleOptions);
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
circleOptions.getCenter(), getZoomLevel(circle)));
} catch (Exception e) {
AppLogger.logError(TAG, e.getMessage());
}
}
/**
* #param circle : circle
* #return : return zoom level according to circle radius
*/
public float getZoomLevel(Circle circle) {
int zoomLevel = 11;
if (circle != null) {
double radius = circle.getRadius() + circle.getRadius() / 2;
double scale = radius / 500;
zoomLevel = (int) (16 - Math.log(scale) / Math.log(2));
}
return zoomLevel+.4f;
}
double getZoomLevel() {
double zoomLevel = 0.0;
double newRadius = radiusOfCircle + radiusOfCircle / 2;
double scale = newRadius / 500;
zoomLevel = (6 - log(scale) / log(2));
return zoomLevel;
}
This worked for me
Thanks to #Anand Tiwari