OpenStreetMap Android - overlays and slow redrawing during scrolling - android

I've got a problem with my osm app. I get Set of points from my server side application and than I want to visualise it as some area. So I create a polygon from this points and add it to my mapView object. It works but as the area becomes bigger there occures a problem with scrolling which just lags until I reach the area without my Overlays.
How can I improve map redrawing performance?
I thought about deviding my area polygon into smaller polygons but than they just intersect with each other and the color isn't uniform.
View of my map
Here is part of my code. Normally I call it everytime I get message with new data from the server in AsyncTask and invalidate mapView on postExecute method but it doesn't really matter since the problem is not with just drawing but scrolling, does it?
List<Overlay> mapOverlays = new ArrayList<>();
Drawable icon = getIcon();
Marker marker = new Marker(mapView);
marker.setPosition(new GeoPoint(newPosition));
marker.setTitle(title);
marker.setIcon(icon);
mapOverlays.add(marker);
List<GeoPoint> listWithAreaPoints = new ArrayList<>();
listWithAreaPoints.addAll(setWithAreaPoints);
Polygon searchedArea = new Polygon(activity.getApplicationContext());
searchedArea.setPoints(listWithAreaPoints);
searchedArea.setFillColor(0x12121212);
searchedArea.setStrokeColor(0x12121212);
searchedArea.setStrokeWidth(0);
mapOverlays.add(searchedArea);
mapView.getOverlays().clear();
mapView.getOverlays().addAll(mapOverlays);
mapView.invalidate();
Thanks for any advice.

I've found out what was wrong.
The data which I was receiving from the server contained a lot of points (hundreds maybe thousands) and I was giving them to Polygon object's method setPoints() in chaotic sequence. In consequence the polygon method draw() drew successive lines without any order what can be seen on my printscreen (blank areas). As the number of points increased the number of polygon's "sides" increased also and redrawing performance decreased.
The solution was to sort List of points by distance so it would represent List of successive corners of the polygon and remove repetitions before giving them as argument to method Polygon.setPoints().
Maybe it will help someone in the future so I'll live here my method to sort GeoPoints as successive polygon's points:
private List<GeoPoint> sortGeoPointsListByDistance(List<GeoPoint> searchedArea){
List<GeoPoint> orderedSearchedArea = new ArrayList<>();
orderedSearchedArea.add(searchedArea.remove(0));
while (searchedArea.size() > 0) {
GeoPoint point = orderedSearchedArea.get(orderedSearchedArea.size() - 1);
int nearestPointIndex = findNearestPointIndex(point, searchedArea);
GeoPoint nearestPoint = searchedArea.get(nearestPointIndex);
if(nearesPointIsTheSamePoint(point, nearestPoint)){
searchedArea.remove(nearestPointIndex);
} else {
orderedSearchedArea.add(searchedArea.remove(nearestPointIndex));
}
}
return orderedSearchedArea;
}
private int findNearestPointIndex(GeoPoint point, List<GeoPoint> listToSearch) {
int index =0;
double dist = 0;
for(int i=0;i<listToSearch.size();i++){
GeoPoint currentPoint = listToSearch.get(i);
double currentPointDist = distFrom( point.getLatitude(), point.getLongitude(), currentPoint.getLatitude(), currentPoint.getLongitude());
if(i==0){
index = i;
dist = currentPointDist;
} else if(currentPointDist<dist){
index = i;
dist = currentPointDist;
}
}
return index;
}
private boolean nearesPointIsTheSamePoint(GeoPoint point, GeoPoint nearestPoint){
if(point.getLatitude()==nearestPoint.getLatitude() && point.getLongitude()==nearestPoint.getLongitude()){
return true;
} else{
return false;
}
}
private double distFrom(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 6371000; //meters
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);
return dist;
}

Related

get notification when user comes near to a marker - osmdroid

I'm currently trying to build an app where the user gets a notification when he is near to a marker or in my case I'm using the ItemizedOverlay of Osmdroid and I was wondering if there is a way to do that also for several hundreds of markers without emptying the battery in a few minutes.
I saw a few methods but there all were just for the case, when you have just a few markers.
I would be very happy if someone could help me.
To complement #Barns answer, 2 remarks:
when using osmdroid, you don't have LatLng, you have GeoPoint instead,
and you don't need to write your own getDistanceMeters because GeoPoint already has
this method:
GeoPoint.distanceToAsDouble(final IGeoPoint other)
If the Markers have static locations you won't need to load them repeatedly.
If all you want is to calculate the distance between two points on the earth and you have the coordinates in latitude and longitude then there is no need for any Google API's Maps or other libraries. That just costs extra overhead and maintenance. Just make a static method like this:
public static double getDistanceMeters(LatLng pt1, LatLng pt2){
double distance = 0d;
try{
double theta = pt1.longitude - pt2.longitude;
double dist = Math.sin(Math.toRadians(pt1.latitude)) * Math.sin(Math.toRadians(pt2.latitude))
+ Math.cos(Math.toRadians(pt1.latitude)) * Math.cos(Math.toRadians(pt2.latitude)) * Math.cos(Math.toRadians(theta));
dist = Math.acos(dist);
dist = Math.toDegrees(dist);
distance = dist * 60 * 1853.1596;
}
catch (Exception ex){
System.out.println(ex.getMessage());
}
return distance;
}
Then you can do this:
public static boolean checkDistanceIsClose(LatLng pt1, LatLng pt2, double distance){
boolean isInDistance = false;
try{
double calcDistance = getDistanceMeters(pt1, pt2)
if(distance <= calcDistance){
isInDistance = true;
}
}
catch (Exception ex){
System.out.println(ex.getMessage());
}
return isInDistance;
}
Same algorithm will work for any platform. Just translate it to the appropriate program language.

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

Google Places API LatLngBounds from LatLng and Radius

I am using the new Android place Api to get autocomplete predictions while the user type.
From what I saw so far the API takes a LatLngBounds object which is created using two locations.
Is there a way to generate a LatLngBounds object using one LatLng as a center point and a radius?
Thank you.
As promised Distwo, here is how I am creating a viewport based on a single latitude and longitude. The reason for me wanting to do this is that my store locator script searches from the viewport returned by the Places API, and so if a viewport isn't returned (rare but it happens) I go ahead and create one from the lat and lng returned by places. Please note that I am not using the android app, this is for regular web viewing.
console.log("no viewport found so lets spherically compute the bounds");
var sw = google.maps.geometry.spherical.computeOffset(place.geometry.location, 1609, 225);
var ne = google.maps.geometry.spherical.computeOffset(place.geometry.location, 1609, 45);
swlat = sw.lat().toFixed(6);
swlng = sw.lng().toFixed(6);
nelat = ne.lat().toFixed(6);
nelng = ne.lng().toFixed(6);
Not sure this is of use to you. In my case the radius is fixed, but in your situation it sounds though it's a variable and hence you'll have to refer to it as a variable.
OK so a little edit: I've changed 1423 to 1000, which represents a lilometer radius. If you're using kilometers then 1000 is the number to use, but if you're using miles then use "1609.3440006" instead.
Thanks to #luke_mclachlan, I found the method I was looking for on the google map github.
https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/SphericalUtil.java
Here is the method I was looking for:
public static LatLng computeOffset(LatLng from, double distance, double heading) {
distance /= EARTH_RADIUS;
heading = toRadians(heading);
// http://williams.best.vwh.net/avform.htm#LL
double fromLat = toRadians(from.latitude);
double fromLng = toRadians(from.longitude);
double cosDistance = cos(distance);
double sinDistance = sin(distance);
double sinFromLat = sin(fromLat);
double cosFromLat = cos(fromLat);
double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading);
double dLng = atan2(
sinDistance * cosFromLat * sin(heading),
cosDistance - sinFromLat * sinLat);
return new LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng));
}
Note that Heart radius here is expected in meters.
Using this method, I find the southwest (heading = 225) and northeast (heading = 45) coordinates and then create my LatLongBounds object.

JSONException: Index 0 out of range [0..0)

I'm trying to calculate the route between two locations with a code that you already know.
In my case the first location is MY location and the second location is the nearest LatLng
of a path.
This code below calculates the nearest LatLng:
private LatLng nearestLatLng(Location mCurrentLocation) {
LatLng latLngCurrentLocation = new LatLng(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());
double nearestLatitude = 0;
double nearestLongitude = 0;
float minDistance = 1000; //I don't know how to initialize
float currentDistance;
for(int i=0; i<mLatLngGpxList.size(); i++) {
LatLng currentTrackLatLng = mLatLngGpxList.get(i);
currentDistance = getDistance(latLngCurrentLocation, currentTrackLatLng);
if(currentDistance <= minDistance) {
minDistance = currentDistance;
nearestLatitude = currentTrackLatLng.latitude;
nearestLongitude = currentTrackLatLng.longitude;
}
}
//mGoogleMap.addMarker(new MarkerOptions().position(new LatLng(nearestLatitude, nearestLongitude)));
return new LatLng(nearestLatitude,nearestLongitude);
}
//Calcola la distanza tra due LatLng
private float getDistance(LatLng first, LatLng second) {
float [] dist = new float[1];
Location.distanceBetween(first.latitude, first.longitude, second.latitude, second.longitude, dist);
return dist[0];
}
If i draw a path in my country, i can also draw the path for reach it, but if i draw a path
away from my country, i get this:
org.json.JSONException: Index 0 out of range [0..0)
at org.json.JSONArray.get(JSONArray.java:263)
at org.json.JSONArray.getJSONObject(JSONArray.java:480)
etc.
and consequently no path to my path is drawn. Why? :(
Ps: i DON'T get "error_message" : "You have exceeded your daily request quota for this API."
Update: the error is in the code above. Advice? :/
Second Update: float minDistance = 1000; --> became float minDistance = 100000000
and now works. It remains a logical problem but i'll see.. :/

few locations not showing up on google map

I have list of stores and when i click on any one depending on their latitude and longitude values which i get from a web server i show them on the map. some of them show up but some wont...i know this is not a good question....but i am just expecting if someone who might have experienced the same problem might help.
here is the code:
Double Storelat = (FeedListViewActivity.lat);
Double Storelng = (FeedListViewActivity.lng);
storeLocation = new GeoPoint((int) (Storelat * 1E6), (int) (Storelng * 1E6));
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, shadow);
Point locationPoint1 = new Point();
Projection projection1 = mapView.getProjection();
projection1.toPixels(storeLocation, locationPoint1);
Paint containerPaint = new Paint();
containerPaint.setAntiAlias(true);
int containerX1 = locationPoint1.x;
int containerY1 = locationPoint1.y;
if (shadow) {
containerX1 += CONTAINER_SHADOW_OFFSET;
containerY1 += CONTAINER_SHADOW_OFFSET;
containerPaint.setARGB(90, 0, 0, 0);
canvas.drawCircle(containerX1, containerY1, CONTAINER_RADIUS,
containerPaint);
} else {
containerPaint.setColor(Color.RED);
canvas.drawCircle(containerX1, containerY1, CONTAINER_RADIUS,
containerPaint);
}
}
and these are the values i get from the webserver:
working:
lat = 18.5170002
lng = 73.858078
not working:
lat = 18.618679
lng = 73.8037491
your answer is in your question itself
working:
lat = 18.5170002 lng = 73.858078
not working:
lat = 18.618679 lng = 73.8037491
as per above provided thing you can see the precision difference i have also faced the same problem be 6 months, and i have made that 6 precision to 8 precision and was working well then.
if you pass 18.618679 then it will treat it as 0.18618679 for confirmation try to zoom out up-to level 2 or 3 you will get your point at unexpected place.

Categories

Resources