Create adaptive grid on the map for clusterization - android

In our application we use google map APIs v1.
I wrote grid-based clusterization for markers (total amount up to few thousands). Everything works fine - good performance, etc...
The only problem is that I calculate grid depending on currently viewed area
private void createCluster2DArray() {
double cwidth = (cachedrightLongitude - cachedleftLongitude) / clustersXnum;
double cheight = (cachedtopLatitude - cachedbottomLatitude) / clustersYnum;
for (int i = 0; i < clustersXnum; i++) {
for (int j = 0; j < clustersYnum; j++) {
Cluster cluster;
if (clusters[i][j] == null) {
cluster = new Cluster();
clusters[i][j] = cluster;
} else {
cluster = clusters[i][j];
cluster.list.clear();
}
//calculate dimensions
cluster.left = cachedleftLongitude + i * cwidth;
cluster.right = cluster.left + cwidth;
cluster.bottom = cachedbottomLatitude + j * cheight;
cluster.top = cluster.bottom + cheight;
cluster.calculateCenter(mMapView);
}
}
}
cachedrightLongitude, cachedrightLongitude, cachedrightLongitude, cachedrightLongitude are borders of device screen area in degrees.
The problem, you can see, is that cluster borders changing every time when user changes visible area (change zoom level, or just slide the screen). This leads to clusters recalculation and markers redistribution over them.
The only solution I see is to create some kind of static screen-independent clusters greed for each zoom level(for example at zoom level 5 size of cluster will be 10milli degrees and at level 6 it will be 2milli degrees, so only border-clusters will dynamicaly change their size and outer borders). Am i right?
Is there any other suggestions?

For android maps API v1 there is a clustering library here: https://github.com/damianflannery/Polaris. This is a fork of Cyril Mottier's Polaris library, but the discussion on pull request suggest it won't be merged back into original. See here. I haven't looked at the source, so I can't tell you if they use grid clustering.
As for your question, I think using static screen-independent cluster grid is the way to go. I'd only suggest changing the values of millidegrees. For zoom level that is different by 1, millidegs should be divided (or multiplied) by 2.
Also note that with latitude you can't use degrees value directly, but you have to push it through a Mercator projection. This is to make grid consist of squares instead of having them look like rectangles with height few times greater than width closer to the north and south poles.
This is basically what I do in Android Maps Extensions for maps API v2.
I assumed 180 degrees grid size on zoom level 0, so 90 degrees on zoom level 1, 45 on 2, etc. and about 85 microdegrees on zoom 21. The value can be changed in the API.
To you the most useful parts of the code from Extensions lib would be: SphericalMercator to convert latitude and some portions from GridClusteringStrategy.

Related

How to use only a portion of GoogleMaps map?

i want to use only a small portion of Google Maps, like only an area of 4 kmĀ².
I have tried looking for any post with same interests, i tried in google, and SO, but found nothing related to what i have in mind. maybe i didn't know what to look for exactly.
Also, will i be able to make the map and all, while being out of the map zone ?
( i dunno how to explain this, but say, you're in city A, and the zone you want to add in your app, is in city B, would it be possible ? ).
The zoom level 1 will map earth equator to 256 pixels. Now every subsequent zoom level magnifies by a level of 2 so half the meters per pixel for every zoom level. So you have to add a function that calculate zoom level such that it will display 4km area.
Add below function in your code to display map area ~4km.
private int getZoomLevel(int screenWidth) {
double equatorLength = 40075004; //Length is in meters
double width = screenWidth;
double meters = equatorLength / 256;
int zoomLevel = 1;
while ((meters * width) > 4000) {
meters /= 2;
++zoomLevel;
}
return zoomLevel;
}

Touch detection on polyline in Google Maps Android API v2

I want to implement a touchListener on a polyline displayed with Google Maps V2 Android API.
Zoom level:
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(lat_Lng, 5);
I tried the following polyline touch code:
boolean onpoly = false;
for (Polyline polyline : mPolylines) {
for (LatLng polyCoords : polyline.getPoints()) {
float[] results = new float[1];
Location.distanceBetween(points.latitude, points.longitude, polyCoords.latitude, polyCoords.longitude, results);
if (results[0] < 100 ) {
onpoly = true;
Marker mark = map.addMarker(new MarkerOptions().position(points).title("AddEvent")
.snippet("" + addressaddexpense).icon(BitmapDescriptorFactory.fromResource(R.drawable.addicon)));
UtilsClass.dropPinEffect(mark);
}// end if..
} // end for loop
}// end for loop
if (onpoly == true) {
Toast.makeText(getActivity(), "Poly line detected", Toast.LENGTH_SHORT).show();
}// end if
It worked but not perfectly.
it will not detect the touch event unless i zoom in, sometimes forcing me to tap the map more than 5 times before zooming to achieve detection.
I then changed the aforementioned if condition from if (results[0] < 100 ) to if (results[0] < 150 * (22 - map.getCameraPosition().zoom)) and the functionality improved but it still does not work all the time.
Note: I want to detect polyline touch at any zoom level.
try this
final List<LatLng> latLngList; // Extract polyline coordinates and put them on this list
private GoogleMap map;
for(int i = 0; i < latLngList.size(); i++){
MarkerOptions mar = new MarkerOptions();
mar.position(new LatLng(latLngList.get(i).latitude, latLngList.get(i).longitude)).icon(BitmapDescriptorFactory.fromResource(R.drawable.trasparent_image)); //this image should be very small in size and transparent
map.addMarker(mar);
}
map.setOnMarkerClickListener(new OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker arg0) {
for(int i = 0; i < latLngList.size(); i++){
if(latLngList.get(i).latitude == arg0.getPosition().latitude && latLngList.get(i).longitude == arg0.getPosition().longitude){
Toast.makeText(MainActivity.this, "PolyLineClick ", Toast.LENGTH_SHORT).show();
break;
}
}
return false;
}
});
Until the questions in my comments are answered i thought i'll try to make them redundant by suggesting the usage of a 3rd party library; android-maps-utils
To do what i think you might be trying to do simply integrate the library and use the following line:
PolyUtil.isLocationOnPath(point, polyline.getPoints(), isGeodesic, tolerance);
For more information you can also look into this thread which seems applicable.
Goodluck.
I think your approach is correct. The only thing that fails is the distance check. And this is because os the touch and the zoom level:
You know that when you tap on the screen, the screen point that is passed to the applications is the center of your finger surface, that is in touch with the screen. This means, that even if it seems, that your finger is exactly over the PolyLine, it can be displaced with some pixels...
Now is time for the Zoom level, and depending in its current value, the distance between the point passed to the application and the PolyLine, can vary very much.
As a result, the if clause fails, and you have to tap several times, until some of your taps is near enough to the PolyLine. And of course it gets better with higher zoom level.
You should include the zoom level as you have done in you edited code, but with some extras: Check the "delta" that you will allow to enter the if, but on the max zoom level. It should be a small value. Then you have to just multiply it by the current zoom level, and calculate how this delta changes. Use this zoom dependant value in your if comparison.To enhance it, you can make a more complex calculation and get this delta, starting from pixel distance. Lets say, a tap that at 50px or less to the PolyLine will be accepted. Calculate this pixel distance in meters, again on the max zoom level and use it multiplied by the current zoom...To enhance it even more, you can get this pixel distance, to be dependant on the screen resolution and density.
Here, you can find how to calculate screen pixels to meters: https://stackoverflow.com/a/13635952/4776540
I implemented a similar thing in the following way:
Convert all locations to the screen coordinates using map.getProjection().toScreenLocation()
Use standard distance formula to determine distance from point to point (or from point to segment, if you want to detect clicks on line segments too) and if this distance is less than some threshold - click is detected.
The key point here is to use map projection to get screen coordinates. This way the decision depends on the real pixel distance on the screen, and does not depend on a zoom level.

I put the zoom to get my mobile screen focused to 5 km from my current location

I am creating an android app where I am displaying current location. I just want to know what should I put the zoom to get my mobile screen focused to 5 km from my current location. Thank You in advance.
That depends on the width of your map view element.
According to CameraPosition.Builder.zoom:
Sets the zoom level of the camera. Zoom level is defined such that at
zoom level 0, the whole world is approximately 256dp wide (assuming
that the camera is not tilted). Increasing the zoom level by 1 doubles
the width of the world on the screen. Hence at zoom level N, the width
of the world is approximately 256 * 2 N dp, i.e., at zoom level 2, the
whole world is approximately 1024dp wide.
When changing the camera position for a map, the zoom level of the
camera is restricted to a certain range depending on various factors
including location, map type and map size. Use
GoogleMap.getMinZoomLevel and GoogleMap.getMaxZoomLevel to find the
restrictions. Note that the camera zoom need not be an integer value.
You could try something like this:
private int calculateZoomLevel(int mapViewWidthInDp)
{
double equatorLength = 40075004; // in meters
double widthInDp = mapViewWidthInDp;
double metersPerDp = equatorLength / 256;
int zoomLevel = 0;
while ((metersPerDp * widthInDp) > 10000) {
metersPerDp /= 2;
++zoomLevel;
}
return zoomLevel;
}

Drawing dotted line on google maps v2 instead of solid

Instead of having a polygon with a solid line surrounding it I want to create one with a dotted line, is this possible?
I know you could do this when you override the onDraw method of the overlay in v1 but the Overlay class does not exist anymore so how else can I achieve this?
It's currently not possible, but you may upvote this enhancement here: http://code.google.com/p/gmaps-api-issues/issues/detail?id=4633
UPDATE
Recently, Google implemented this feature for polylines and polygons in Google Maps Android API v2 and marked issue 4633 as Fixed.
See information about stroke patterns in the Shapes Guide. See an example in the Polylines and Polygons tutorial.
You can also read the corresponding blog post here:
https://maps-apis.googleblog.com/2017/02/styling-and-custom-data-for-polylines.html
First of all, take a look on the API
https://developers.google.com/maps/documentation/android/reference/com/google/android/gms/maps/model/Polyline
it is not yet possible with v2, but on v3 javascript API, it already is, look here:
https://developers.google.com/maps/documentation/javascript/overlays#PolylineSymbols
But it seems that it's possible to use this v3 javascript API in an android app, look here:
https://developers.google.com/maps/articles/android_v3
Maybe, this will help you
Find a LatLng at a distance of radius units from center LatLng on Map
now convert both these LatLngs to screenCoordinates
Use the formula used to construct a cirle x = Rsin(theta) , y = Rcos(theta)
you divide the circle into N segments and then draw polylines(drawn on map) on the circumference of the circle converting the screen coordinates to LatLngs
more the number of N more it looks like a circle , I have used N = 120 according the zoom level ,I am using 13.
private void addDottedCircle(double radius) {//radius is in kms
clearDottedCircle();
LatLng center,start,end;
Point screenPosCenter,screenPosStart,screenPosEnd;
Projection p = mMap.getProjection();
center = searchCenterMarker.getPosition();
start = new LatLng(center.latitude + radius/110.54,center.longitude);
// radius/110.54 gives the latitudinal delta we should increase so that we have a latitude at radius distance
// 1 degree latitude is approximately 110.54 kms , so the above equation gives you a rough estimate of latitude at a distance of radius distance
screenPosCenter = p.toScreenLocation(center);
screenPosStart = p.toScreenLocation(start);
double R = screenPosCenter.y - screenPosStart.y;
int N = 120;//N is the number of parts we are dividing the circle
double T = 2*Math.PI/N;
double theta = T;
screenPosEnd = new Point();
screenPosEnd.x = (int)(screenPosCenter.x-R*Math.sin(theta));
screenPosEnd.y = (int) (screenPosCenter.y-R*Math.cos(theta));
end = p.fromScreenLocation(screenPosEnd);
for(int i =0;i<N;i++){
theta+=T;
if(i%2 == 0){
//dottedCircle is a hashmap to keep reference to all the polylines added to map
dottedCircle.add(mMap.addPolyline(new PolylineOptions().add(start,end).width(5).color(Color.BLACK)));
screenPosStart.x = (int) (screenPosCenter.x-R*Math.sin(theta));
screenPosStart.y = (int) (screenPosCenter.y-R*Math.cos(theta));
start = p.fromScreenLocation(screenPosStart);
}
else{
screenPosEnd.x = (int)(screenPosCenter.x-R*Math.sin(theta));
screenPosEnd.y = (int) (screenPosCenter.y-R*Math.cos(theta));
end = p.fromScreenLocation(screenPosEnd);
}
}
}
If you are still looking for an answer have a look at this :
How to draw dashed polyline with android google map sdk v2?

Android: MapView.GetLatitudeSpan(), GetLongitudeSpan() Anomaly?

I'm working on a mapping app that plots pins on a MapView based on a user's query. I'm trying to scale the map to fit all the results pins, but I've run into a seemingly strange situation.
I have two variables set up:
latSpan is the difference between the maximum latitude and minimum latitude of any of the results points
lonSpan is the difference between the maximum longitude and minimum longitude of any of the results points
This method
while ((mapView.getLatitudeSpan()) < latSpan) || (mapView.getLongitudeSpan() < lonSpan)){
mapController.zoomOut();
}//end of while loop
is supposed to zoom out to make sure all the pins fit on the viewable map screen.
But I'm experiencing something rather strange. The results of mapView.getLatitudeSpan() and mapView.getLongitudeSpan() are routinely greater than my latSpan and lonSpan values, so the MapController doesn't zoom out enough.
My map is zoomed in pretty far--level 15 or higher.
As an example, one set of search results gave the following values:
latSpan = 17928
lonSpan = 11636
mapView.getLatitudeSpan() = 21933
mapView.getLongitudeSpan() = 20598
Based on these numbers, you wouldn't think that the MapController would need to zoom out. Yet there are pins plotted both above the top and below the bottom of the screen. I changed my WHILE loop to read
while ((mapView.getLatitudeSpan() - 6000) < latSpan...
and that helps, but the right query will still cause issues.
But the real question is, why is this happening?
I'm not sure why you're code isn't working from the snippet provided. Its possible that you are not converting your latSpan and lonSpan to microDegrees (as shown below) and this would cause some issues.
Also if you're trying to make sure your mapView is showing all of the results, there's not much point trying to determine if it needs to zoom before zooming, just zoom it every time. If it turns out that it doesn't need to zoom then nothing will appear to happen and if it does then it does.
You can set a map up to encompass all of your points and move to the centroid of the points as follows:
GeoPoint max = new GeoPoint(maxLatitude, maxLongitude);
GeoPoint min = new GeoPoint(minLatitude, minLongitude);
int maxLatMicro = max.getLatitudeE6();
int maxLonMicro = max.getLongitudeE6();
int minLatMicro = min.getLatitudeE6();
int minLonMicro = min.getLongitudeE6();
GeoPoint center = new GeoPoint((maxLatMicro+minLatMicro)/2,(maxLonMicro + minLonMicro)/2);
controller.zoomToSpan(maxLatMicro - minLatMicro, maxLonMicro - minLonMicro);
controller.animateTo(center);

Categories

Resources