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;
}
Related
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;
}
I am trying to create a maps app for a certain city that have some stored latitude and longitude for certain landmarks in the city. In the map,
you can only zoom in and zoom out within the boundaries of the city
While the app is open, when you reach a certain range of lat and long coordinates within a certain radius around the landmark, it will trigger and activity that will display details about the landmark and also a voice recording about the landmark
also, the map must also have the "directions" functionality in it, where it can show several possible ways for you to get to a certain location (like landmark) from your present location and also display the distance between two points
I've already tried a GPS program from androidhive that detects your lat and long coordinates. I'm also trying to understand how to acquire and use the google maps api. I would like to know the possible approaches in doing it since I'm still new to android.
Thanks in Advance!
you can zoom with specific mile or kmeter by this code:this is for two mile:
Display display = getWindowManager().getDefaultDisplay();
// double equatorLength = 3218; // in meters
double equatorLength = 40075004; // in meters
double widthInPixels = display.getWidth();
double metersPerPixel = equatorLength / 256;
int zoomLevel = 1;
// 2 mile=3218 mtr
while ((metersPerPixel * widthInPixels) > 3218) {
metersPerPixel /= 2;
++zoomLevel;
}
return zoomLevel;
hope this is helpfull for "you can only zoom in and zoom out within the boundaries of the city"
I'm working on an Android Application using Gooogle Maps API v2. I have markers on my map, and I'd like to circle one of them. I managed to do that easily, by using the Circle and Circle Options classes. But I'd also like my circle to keep the same size on the screen when zooming or unzooming, just like the markers do. It means that the circle must have a constant radius in terms of pixels. Sadly, we cannot set a radius in pixels in the API v2.
I have tried several solutions, but I'm not satisfied.
In the first one, I just multiply or divide the radius :
#Override
public void onCameraChange(CameraPosition position)
{
if(previousZoom > position.zoom) {
mSelectionCircle.setRadius(Math.abs(position.zoom - previousZoom)*2*mSelectionCircle.getRadius());
}
else if(previousZoom < position.zoom) {
mSelectionCircle.setRadius(Math.abs(position.zoom - previousZoom)*mSelectionCircle.getRadius()/2);
}
previousZoom = position.zoom;
}
It seemed to work at first, but produces wrong results when zooming quickly or zooming with fingers. Moreover, the scaling is clearly visible on the screen.
My second solution uses pixel-meter conversions. The idea is to recalculate the radius in meters when zooming/unzooming, so the circle has a constant size on the screen. To do that, I get the current position of the Circle on the screen:
Point p1 = mMap.getProjection().toScreenLocation(mSelectionCircle.getCenter());
Then I create another point which is on the edge of the circle:
Point p2 = new Point(p1.x + radiusInPixels, p1.y);
Where
int radiusInPixels = 40;
After that, I use a function which returns the distance between these two points in meters.
private double convertPixelsToMeters(Point point1, Point point2) {
double angle = Math.acos(Math.sin(point1.x) * Math.sin(point2.x)
+ Math.cos(point1.x) * Math.cos(point2.x) * Math.cos(point1.y- point2.y));
return angle * Math.PI * 6378100.0; // distance in meters
}
6378100 is average Earth radius. Finally, I set the new radius of the Circle :
mSelectionCircle.setRadius(convertPixelsToMeters(p1, p2));
It should work in theory but I get ridiculous radius values (10^7 m!). The conversion function may be wrong?
So is there a simpler method to do that, or if not, may you help me to understand why my second soluton doesn't work?
Thanks!
You probably don't really care about an exact pixel size, just that it looks the same for all zoom levels and device rotations.
Here is a fairly simple way to do this. Draw (and redraw if the zoom is changed) a circle whose radius is some percentage of the diagonal of the visible screen.
The Google Maps API v2 has a getProjection() function that will return the lat/long coordinates of the 4 corners of the visible screen. Then using the super convenient Location class, you can calculate the distance of the diagonal of what is visible on the screen, and use a percentage of that diagonal as the radius of your circle. Your circle will be the same size no matter what the zoom scale is or which way the device is rotated.
Here is the code in Java:
public Circle drawMapCircle(GoogleMap googleMap,LatLng latLng,Circle currentCircle) {
// get 2 of the visible diagonal corners of the map (could also use farRight and nearLeft)
LatLng topLeft = googleMap.getProjection().getVisibleRegion().farLeft;
LatLng bottomRight = googleMap.getProjection().getVisibleRegion().nearRight;
// use the Location class to calculate the distance between the 2 diagonal map points
float results[] = new float[4]; // probably only need 3
Location.distanceBetween(topLeft.latitude,topLeft.longitude,bottomRight.latitude,bottomRight.longitude,results);
float diagonal = results[0];
// use 5% of the diagonal for the radius (gives a 10% circle diameter)
float radius = diagonal / 20;
Circle circle = null;
if (currentCircle != null) {
// change the radius if the circle already exists (result of a zoom change)
circle = currentCircle;
circle.setRadius(radius);
} else {
// draw a new circle
circle = googleMap.addCircle(new CircleOptions()
.center(latLng)
.radius(radius)
.strokeColor(Color.BLACK)
.strokeWidth(2)
.fillColor(Color.LTGRAY));
}
return circle;
}
Use a custom icon for Marker instead. You can create Bitmap and Canvas, draw on the latter and use it as a Marker icon:
new MarkerOptions().icon(BitmapDescriptorFactory.fromBitmap(bitmap))...
EDIT:
My previous answer is no longer valid.
As Jean-Philippe Jodoin brought up, you can simply do that with markers and setting their anchor to 0.5/0.5. It's a way cleaner solution.
Pasting the suggested code snippet here for reference:
marker = mMap.addMarker(new MarkerOptions().position(latlng).anchor(0.5f, 0.5f));
Old answer:
I came accross the same problem and could not find a solution, so I did it myself, I will post in the hope that it is helpful to some other people.
The "marker" approach did not work for me because I wanted circles to be centered on a specific lat/lng, and you cannot do that with a marker: if you set a circle icon for your marker, the circle edge will touch the lat/lng, but the circle will not be centered on the lat/lng.
I created a function to compute what should be the size of the circle in meters given the latitude and the camera zoom level, then added a camera listener on the map to update the size of the circle each time the camera changes zoom level. The result is a circle not changing in size (to the bare eye at least).
Here is my code:
public static double calculateCircleRadiusMeterForMapCircle(final int _targetRadiusDip, final double _circleCenterLatitude,
final float _currentMapZoom) {
//That base value seems to work for computing the meter length of a DIP
final double arbitraryValueForDip = 156000D;
final double oneDipDistance = Math.abs(Math.cos(Math.toRadians(_circleCenterLatitude))) * arbitraryValueForDip / Math.pow(2, _currentMapZoom);
return oneDipDistance * (double) _targetRadiusDip;
}
public void addCircleWithConstantSize(){
final GoogleMap googleMap = ...//Retrieve your GoogleMap object here
//Creating a circle for the example
final CircleOptions co = new CircleOptions();
co.center(new LatLng(0,0));
co.fillColor(Color.BLUE);
final Circle circle = googleMap.addCircle(co);
//Setting a listener on the map camera to monitor when the camera changes
googleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
//Use the function to calculate the radius
final double radius = calculateCircleRadiusMeterForMapCircle(12, co.getCenter().latitude, googleMap.getCameraPosition().zoom);
//Apply the radius to the circle
circle.setRadius(radius);
}
});
}
As MaciejGórski suggested, it's correct and easy way to go; but if you have a lot of markers in google map, let's say 5k markers for example, it will slow down performance dramatically. Some suggestions to show this matter are:
1) Let search Marker clustering utility of Google android map API.
2) However, Marker clustering maybe not fit completely your purpose. So you can customize it by yourself. Here is the thread discussing about this matter: https://github.com/googlemaps/android-maps-utils/issues/29
I'm sorry, I did not try it, since I found using Polyline satisfies my purpose (display a path).
Hope this help,
Mttdat.
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.
I am trying to rotate map view when the user changes his direction ie if user takes left and right turns it should rotate accordingly.I am rotating map view basing on current location bearing it is rotating correctly but it was jittering.Here is the code which i used for rotation
public void onGPSUpdate(Location location)
{
boolean check=isBetterLocation(location, tempLoc);
tempLoc=location;
if(check){
showLocation(location);
}
}
isBetterLocation method is copied from google docs for better location.
private void showLocation(Location loc){
mRotateView.rotate(-loc.getBearing());
}
I registered a location updates with time interval 0 and min distance of 10 for frequent updates.Here my problem is map view is jittering always,can any one tell me how can I smoothly rotate map view like other applications like waze maps do.Thanks...
are you trying to rotate the map in a smooth way such as by one degree at a time or just have it go from degree A to degree B on location update ?
Something like
while (oldAngle != newAngle)
{
mapView.rotate(newAngle);
// this is where you would decied to add or subtract;
newAngle ++ or -- ;
}
not sure if this would work exactly as the loop would run really quickly so maybe do this as a asynctask and add a pause in there to simulate a smooth rotation.
Double angle = Math.atan2((userstartPoint.getX() - userendPoint.getX()), userstartPoint.getY() - userendPoint.getY());
angle = Math.toDegrees(angle);
map.setRotationAngle(angle);
so basically I get the start point (new location) and then the end point (old location) and do a Math.atan2 on it as you can see. Then convert that to a degree and set it to my map rotation.
Now it does not do a smooth rotation but I don't need that. Here is where you could set up your own stepper for a smooth rotate. Unless the google maps already has one.
As the bearing values of the Location are not very exact and tend to jump a little, you should use a filter for the bearing. For example, keep the last 5 bearing-values in an array and use the average of those values as the bearing to rotate the map to. Or use the filter explained in the SensorEvent docs - it's easier to use and can be tweaked better.
This will smoothen out the rotation of the map resp. keep it more stable.
EDIT:
A version of the low-pass filter:
public static float exponentialSmoothing(float input, float output, float alpha) {
output = output + alpha * (input - output);
return output;
}
use it like so:
final static float ALPHA = 0.33; // values between 0 and 1
float bearing;
// on location/bearing changed:
bearing = exponentialSmoothing(bearing, newBearing, ALPHA);
bearing would be the value to use to actually rotate the map, newBearing would be the bearing you get from every event, and with ALPHA you can control how quickly or slowly the rotation acts to a new orientation by weighting how much of the old and the new bearing is taken into account for the result. A small value weighs the old value higher, a high value weighs the new value higher.
I hope that works out better.
To change the bearing of your map, use the Camera class. You can define a new CameraPosition with the new bearing and tell the camera to move with either GoogleMap.moveCamera or GoogleMap.animateCamera if you want a smooth movement.
I have implemented this in my app. What I basically did is that I took the last and second last LatLng of my path and calculate bearing by using
public static float getRotationAngle(LatLng secondLastLatLng, LatLng lastLatLng)
{
double x1 = secondLastLatLng.latitude;
double y1 = secondLastLatLng.longitude;
double x2 = lastLatLng.latitude;
double y2 = lastLatLng.longitude;
float xDiff = (float) (x2 - x1);
float yDiff = (float) (y2 - y1);
return (float) (Math.atan2(yDiff, xDiff) * 180.0 / Math.PI);
}
Set this angle as bearing to camera position.
Note: Sometimes (rarely) it rotates map to opposite direction. i am looking for it but if anyone got reason do reply.