WMS On Android tile loading issue - android

I'm using Google Maps v2 to display tiles from a wms. I referred this
site. Problem in loading tiles, they are loading multiple times i dont konw y? Can any1 help me.
Here is my code
package com.example.testgooglemaps;
import android.app.Activity;
import android.os.Bundle;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
public class Lanch extends Activity {
// Google Map
private GoogleMap googleMap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lanch);
try {
// Loading map
initilizeMap();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* function to load map. If map is not created it will create it for you
* */
private void initilizeMap() {
if (googleMap == null) {
googleMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
// check if map is created successfully or not
if (googleMap != null) {
setUpMap();
}
}
}
#Override
protected void onResume() {
super.onResume();
initilizeMap();
}
private void setUpMap() {
TileProvider wmsTileProvider = TileProviderFactory.getOsgeoWmsTileProvider();
googleMap.addTileOverlay(new TileOverlayOptions().tileProvider(wmsTileProvider));
// to satellite so we can see the WMS overlay.
googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
}
}
TileProvider class...
package com.example.testgooglemaps;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import android.util.Log;
public class TileProviderFactory {
public static WMSTileProvider getOsgeoWmsTileProvider() {
final String OSGEO_WMS = "http://localhost/geoserver/magnamaps/wms?service=WMS&version=1.1.0&request=GetMap&layers=magnamaps:bang_apartments&styles=&bbox=%f,%f,%f,%f&width=256&height=256&crs=EPSG:4326&format=image/png&transparent=true";
WMSTileProvider tileProvider = new WMSTileProvider(256, 256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, OSGEO_WMS, bbox[MINX], bbox[MINY], bbox[MAXX], bbox[MAXY]);
Log.d("WMSDEMO", s);
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
};
return tileProvider;
}
}
WMSTileProvider class...
package com.example.testgooglemaps;
import java.net.URLEncoder;
import com.google.android.gms.maps.model.UrlTileProvider;
public abstract class WMSTileProvider extends UrlTileProvider {
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = { -20037508.34789244, 20037508.34789244 };
// array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
// cql filters
private String cqlString = "";
// Construct with tile size in pixels, normally 256, see parent class.
public WMSTileProvider(int x, int y) {
super(x, y);
}
protected String getCql() {
return URLEncoder.encode(cqlString);
}
public void setCql(String c) {
cqlString = c;
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
EDIT : While initializing map itself, zoom level is set to 3, Inside this method getTileUrl(int x, int y, int zoom)

In WMSTileProvider.getBoundingBox you are computing the bounding box in units of the Web Mercator projection, which are meters. In your OSGEO_WMS URL string, you are specifying that the bbox units are in EPSG:4326 (degrees). It's likely that the query for each tile is incorrect as a result.
See the WMS reference for bbox and srs:
bbox: Bounding box for map extent. Value is minx,miny,maxx,maxy in
units of the SRS.
Try replacing your srs value with EPSG:3857 (WebMercator)

Related

How to crop Google Map [duplicate]

I've added GroundOverlay to map and want to limit scrolling and zooming within this area.
How to limit scrolling within some bounds on android google maps?
Is it possible to get instantly motion points from MapFragment?
Please, help me.
Constraining the camera has (finally!) been added as a feature as part of the release of Google Play Services 9.4 -- you can call setLatLngBoundsForCameraTarget(LatLngBounds bounds) to set the allowed panning area.
// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
You can find a thorough explanation in the documentation: Restricting the user's panning to a given area and a sample activity in GitHub.
May be it is too late, but here is my solution:
Disabled built-in GoogleMap's gestures.
Added custom gestures (for scrolling, fling and scaling).
Checking for allowed area when processing events.
Set bounds/zoom manually with standard Map's functions.
And here is my example:
[UPDATED]
There was a problem - when touch events were recieved before map was initialized.
check null values in onInterceptTouchEvent
Also I have discovered that my solution is slightly slowly than build-in function.
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;
public class RestrictedMapView extends MapView {
public static float MAX_ZOOM = 20;
public static float MIN_ZOOM = 5;
public static float MIN_ZOOM_FOR_FLING = 7;
public static double MAX_LONGITUDE = 183.61;
public static double MIN_LONGITUDE = 159.31;
public static double MAX_LATITUDE = -32.98;
public static double MIN_LATITUDE = -53.82;
public static double DEF_LATITUDE = -41.78;
public static double DEF_LONGITUDE = 173.02;
public static float DEF_ZOOM = 7;
private Handler mHandler = new Handler();
private Context mContext;
private VisibleRegion mLastCorrectRegion = null;
private boolean mIsInAnimation = false;
public RestrictedMapView(Context c, AttributeSet a, int o) {
super(c, a, o);
init(c);
}
public RestrictedMapView(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public RestrictedMapView(Context c) {
super(c);
init(c);
}
public RestrictedMapView(Context c, GoogleMapOptions o) {
super(c, o);
init(c);
}
private GestureDetector mGestureDetector = null;
private GestureDetector.SimpleOnGestureListener mGestudeListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, 0);
return true;
}
#Override
public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
if (zoom < MIN_ZOOM_FOR_FLING)
return false;
int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (velocity < 500) return false;
double k1 = 0.002d; /*exipemental*/
double k2 = 0.002d;/*exipemental*/
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);
return true;
}
};
private ScaleGestureDetector mScaleGestureDetector = null;
private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale (ScaleGestureDetector detector) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
double k = 1d / detector.getScaleFactor();
int x = (int) detector.getFocusX();
int y = (int) detector.getFocusY();
LatLng mapFocus = map.getProjection().
fromScreenLocation(new Point(x, y));
LatLng target = map.getCameraPosition().target;
zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
if (zoom < MIN_ZOOM)
if (zoom == MIN_ZOOM) return false;
else zoom = MIN_ZOOM;
if (zoom > MAX_ZOOM)
if (zoom == MAX_ZOOM) return false;
else zoom = MAX_ZOOM;
double dx = norm(mapFocus.longitude) - norm(target.longitude);
double dy = mapFocus.latitude - target.latitude;
double dk = 1d - 1d / k;
LatLng newTarget = new LatLng(target.latitude - dy * dk,
norm(target.longitude) - dx * dk);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);
tryUpdateCamera(update, 0);
return true;
}
};
private void tryUpdateCamera(CameraUpdate update, int animateTime) {
GoogleMap map = getMap();
final VisibleRegion reg = map.getProjection().getVisibleRegion();
if (animateTime <= 0) {
map.moveCamera(update);
checkCurrentRegion(reg);
} else {
mIsInAnimation = true;
map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
#Override
public void onCancel() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
});
}
}
private void checkCurrentRegion(VisibleRegion oldReg) {
GoogleMap map = getMap();
VisibleRegion regNew = map.getProjection().getVisibleRegion();
if (checkBounds(regNew)) {
mLastCorrectRegion = regNew;
} else {
if (mLastCorrectRegion != null)
oldReg = mLastCorrectRegion;
mIsInAnimation = true;
map.animateCamera(CameraUpdateFactory.newLatLngBounds(
oldReg.latLngBounds, 0),
200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
}
#Override
public void onCancel() {
mIsInAnimation = false;
}
});
}
}
/**
*
*
* #param lonVal
* #return
*/
private double norm(double lonVal) {
while (lonVal > 360d) lonVal -= 360d;
while (lonVal < -360d) lonVal += 360d;
if (lonVal < 0) lonVal = 360d + lonVal;
return lonVal;
}
private double denorm(double lonVal) {
if (lonVal > 180d) lonVal = -360d + lonVal;
return lonVal;
}
private boolean checkBounds(VisibleRegion reg) {
double left = Math.min(
Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double right = Math.max(
Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double top = Math.max(
Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.max(reg.farRight.latitude, reg.nearRight.latitude));
double bottom = Math.min(
Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.min(reg.farRight.latitude, reg.nearRight.latitude));
boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
bottom < MIN_LATITUDE || top > MAX_LATITUDE;
return !limitBounds;
}
private void init(Context c) {
try {
MapsInitializer.initialize(c);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
mContext = c;
mHandler.post(new Runnable() {
#Override
public void run() {
GoogleMap map = getMap();
if (map != null) {
getMap().getUiSettings().setZoomControlsEnabled(false);
map.getUiSettings().setAllGesturesEnabled(false);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
mLastCorrectRegion = map.getProjection().getVisibleRegion();
mGestureDetector = new GestureDetector(mContext, mGestudeListener);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
} else mHandler.post(this);
}
});
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
}
Definition within my xml-layout of fragment:
<com.package....RestrictedMapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In xml file it is possible to define custom zoom/location buttons and set click listeners for manual manipulating camera (in this case you have to check MAX_ZOOM and MIN_ZOOM, and check if the current location is within the allowed bounds).
Too bad that google doesn't let us intercept and block the user, i found that MaciejGórski's answer is the one that suits best to my needs.
I wanted to share my solution (Based on his answer) with you.
First i defined the bounds and max/min zoom:
private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;
Then i created this little function to test if current camera bounds are outside of max bounds and return the difference in latitude and longitude.
/**
* Returns the correction for Lat and Lng if camera is trying to get outside of visible map
* #param cameraBounds Current camera bounds
* #return Latitude and Longitude corrections to get back into bounds.
*/
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
double latitude=0, longitude=0;
if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
}
if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
}
if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
}
if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
}
return new LatLng(latitude, longitude);
}
Then the Handler that controls the overscroll (And overzoom) limiting it every 100ms.
/**
* Bounds the user to the overlay.
*/
private class OverscrollHandler extends Handler {
#Override
public void handleMessage(Message msg) {
CameraPosition position = mMap.getCameraPosition();
VisibleRegion region = mMap.getProjection().getVisibleRegion();
float zoom = 0;
if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
LatLng correction = getLatLngCorrection(region.latLngBounds);
if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
zoom = (zoom==0)?position.zoom:zoom;
double lat = position.target.latitude + correction.latitude;
double lon = position.target.longitude + correction.longitude;
CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
mMap.moveCamera(update);
}
/* Recursively call handler every 100ms */
sendEmptyMessageDelayed(0,100);
}
}
This handler must be defined as a field inside current class (I did this in a class that extends SupportMapFragment)
private OverscrollHandler mOverscrollHandler = new OverscrollHandler();
And lastly it must be called for the first time, i did it at the end of onActivityCreated to be sure that map exists.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mMap = getMap();
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
mMap.moveCamera(upd);
mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}
Hope you will find it useful!
Instead of using new and shiny push technology which is onCameraChange, you may try to use old poll technology: map.getCameraPosition() or map.getProjection().getVisibleRegion(). You can then check if returned value is what you like and if not, map.moveCamera(...).
Basically you need a Handler, which will get the value of camera position in handleMessage and you send messages to this handler every 10ms or so. Inside handleMessage do sendEmptyMessageDelayed.
You can also use Runnable instead of Message (but that's a matter of taste).
limit zoom you can user this code
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
In maps API v2 there's a Min/MaxZoomLevel on the GoogleMap class, but I don't know if you can set it in any way.
Another approach would be to add a
GoogleMap.OnCameraChangeListener
to your map and by implementing
public void onCameraChange(CameraPosition cameraPosition);
To restrict the visible area, using GoogleMap.moveCamera(cameraPosition)
That is if you wish the user to be able to scroll or zoom "some".
You can also totally deactivate the scroll/zoom events via GoogleMapOptions

Android change google map tile with custom tiles

Is it possible to change google map API v3 standart map to my own custom map coming from url? I know that OSMdroid provide it but i want work with google map API. Is it possible?
it is indeed possible by using WMS services (if you don't know what they are, please google it).
Here is some code you can use:
The WMSTile provider is used by GoogleMapsAPI to set the map provider:
public abstract class WMSTileProvider extends UrlTileProvider {
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = { -20037508.34789244, 20037508.34789244 };
// array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
// cql filters
private String cqlString = "";
// Construct with tile size in pixels, normally 256, see parent class.
public WMSTileProvider(int x, int y) {
super(x, y);
}
#SuppressWarnings("deprecation")
protected String getCql() {
try {
return URLEncoder.encode(cqlString, Charset.defaultCharset().name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return URLEncoder.encode(cqlString);
}
}
public void setCql(String c) {
cqlString = c;
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
And you can instantiate a custom one from your URL in such a way:
public static WMSTileProvider getWMSTileProviderByName(String layerName) {
final String OSGEO_WMS = "http://YOURWMSSERVERURL?"
+ "LAYERS=" + layerName
+ "&FORMAT=image/png8&"
+ "PROJECTION=EPSG:3857&"
+ "TILEORIGIN=lon=-20037508.34,lat=-20037508.34&"
+ "TILESIZE=w=256,h=256"
+ "&MAXEXTENT=-20037508.34,-20037508.34,20037508.34,20037508.34&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG:3857"
+ "&BBOX=%f,%f,%f,%f&WIDTH=256&HEIGHT=256";
return new WMSTileProvider(256, 256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
final double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, OSGEO_WMS, bbox[MINX], bbox[MINY], bbox[MAXX], bbox[MAXY]);
try {
return new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
};
}
Add to your map:
TileProvider tileProvider = getWMSTileProviderByName("MYLAYERNAME");
TileOverlay tileOverlay = myMap.addTileOverlay(new TileOverlayOptions()
.tileProvider(tileProvider));
You should also set the map type to MAP_NONE when using a custom tile provider (if it is not transparent), so you avoid to load gmaps tiles that are hidden behind your custom map.

Google Maps Android API v2 limit panning [duplicate]

I've added GroundOverlay to map and want to limit scrolling and zooming within this area.
How to limit scrolling within some bounds on android google maps?
Is it possible to get instantly motion points from MapFragment?
Please, help me.
Constraining the camera has (finally!) been added as a feature as part of the release of Google Play Services 9.4 -- you can call setLatLngBoundsForCameraTarget(LatLngBounds bounds) to set the allowed panning area.
// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
You can find a thorough explanation in the documentation: Restricting the user's panning to a given area and a sample activity in GitHub.
May be it is too late, but here is my solution:
Disabled built-in GoogleMap's gestures.
Added custom gestures (for scrolling, fling and scaling).
Checking for allowed area when processing events.
Set bounds/zoom manually with standard Map's functions.
And here is my example:
[UPDATED]
There was a problem - when touch events were recieved before map was initialized.
check null values in onInterceptTouchEvent
Also I have discovered that my solution is slightly slowly than build-in function.
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;
public class RestrictedMapView extends MapView {
public static float MAX_ZOOM = 20;
public static float MIN_ZOOM = 5;
public static float MIN_ZOOM_FOR_FLING = 7;
public static double MAX_LONGITUDE = 183.61;
public static double MIN_LONGITUDE = 159.31;
public static double MAX_LATITUDE = -32.98;
public static double MIN_LATITUDE = -53.82;
public static double DEF_LATITUDE = -41.78;
public static double DEF_LONGITUDE = 173.02;
public static float DEF_ZOOM = 7;
private Handler mHandler = new Handler();
private Context mContext;
private VisibleRegion mLastCorrectRegion = null;
private boolean mIsInAnimation = false;
public RestrictedMapView(Context c, AttributeSet a, int o) {
super(c, a, o);
init(c);
}
public RestrictedMapView(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public RestrictedMapView(Context c) {
super(c);
init(c);
}
public RestrictedMapView(Context c, GoogleMapOptions o) {
super(c, o);
init(c);
}
private GestureDetector mGestureDetector = null;
private GestureDetector.SimpleOnGestureListener mGestudeListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, 0);
return true;
}
#Override
public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
if (zoom < MIN_ZOOM_FOR_FLING)
return false;
int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (velocity < 500) return false;
double k1 = 0.002d; /*exipemental*/
double k2 = 0.002d;/*exipemental*/
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);
return true;
}
};
private ScaleGestureDetector mScaleGestureDetector = null;
private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale (ScaleGestureDetector detector) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
double k = 1d / detector.getScaleFactor();
int x = (int) detector.getFocusX();
int y = (int) detector.getFocusY();
LatLng mapFocus = map.getProjection().
fromScreenLocation(new Point(x, y));
LatLng target = map.getCameraPosition().target;
zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
if (zoom < MIN_ZOOM)
if (zoom == MIN_ZOOM) return false;
else zoom = MIN_ZOOM;
if (zoom > MAX_ZOOM)
if (zoom == MAX_ZOOM) return false;
else zoom = MAX_ZOOM;
double dx = norm(mapFocus.longitude) - norm(target.longitude);
double dy = mapFocus.latitude - target.latitude;
double dk = 1d - 1d / k;
LatLng newTarget = new LatLng(target.latitude - dy * dk,
norm(target.longitude) - dx * dk);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);
tryUpdateCamera(update, 0);
return true;
}
};
private void tryUpdateCamera(CameraUpdate update, int animateTime) {
GoogleMap map = getMap();
final VisibleRegion reg = map.getProjection().getVisibleRegion();
if (animateTime <= 0) {
map.moveCamera(update);
checkCurrentRegion(reg);
} else {
mIsInAnimation = true;
map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
#Override
public void onCancel() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
});
}
}
private void checkCurrentRegion(VisibleRegion oldReg) {
GoogleMap map = getMap();
VisibleRegion regNew = map.getProjection().getVisibleRegion();
if (checkBounds(regNew)) {
mLastCorrectRegion = regNew;
} else {
if (mLastCorrectRegion != null)
oldReg = mLastCorrectRegion;
mIsInAnimation = true;
map.animateCamera(CameraUpdateFactory.newLatLngBounds(
oldReg.latLngBounds, 0),
200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
}
#Override
public void onCancel() {
mIsInAnimation = false;
}
});
}
}
/**
*
*
* #param lonVal
* #return
*/
private double norm(double lonVal) {
while (lonVal > 360d) lonVal -= 360d;
while (lonVal < -360d) lonVal += 360d;
if (lonVal < 0) lonVal = 360d + lonVal;
return lonVal;
}
private double denorm(double lonVal) {
if (lonVal > 180d) lonVal = -360d + lonVal;
return lonVal;
}
private boolean checkBounds(VisibleRegion reg) {
double left = Math.min(
Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double right = Math.max(
Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double top = Math.max(
Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.max(reg.farRight.latitude, reg.nearRight.latitude));
double bottom = Math.min(
Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.min(reg.farRight.latitude, reg.nearRight.latitude));
boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
bottom < MIN_LATITUDE || top > MAX_LATITUDE;
return !limitBounds;
}
private void init(Context c) {
try {
MapsInitializer.initialize(c);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
mContext = c;
mHandler.post(new Runnable() {
#Override
public void run() {
GoogleMap map = getMap();
if (map != null) {
getMap().getUiSettings().setZoomControlsEnabled(false);
map.getUiSettings().setAllGesturesEnabled(false);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
mLastCorrectRegion = map.getProjection().getVisibleRegion();
mGestureDetector = new GestureDetector(mContext, mGestudeListener);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
} else mHandler.post(this);
}
});
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
}
Definition within my xml-layout of fragment:
<com.package....RestrictedMapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In xml file it is possible to define custom zoom/location buttons and set click listeners for manual manipulating camera (in this case you have to check MAX_ZOOM and MIN_ZOOM, and check if the current location is within the allowed bounds).
Too bad that google doesn't let us intercept and block the user, i found that MaciejGórski's answer is the one that suits best to my needs.
I wanted to share my solution (Based on his answer) with you.
First i defined the bounds and max/min zoom:
private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;
Then i created this little function to test if current camera bounds are outside of max bounds and return the difference in latitude and longitude.
/**
* Returns the correction for Lat and Lng if camera is trying to get outside of visible map
* #param cameraBounds Current camera bounds
* #return Latitude and Longitude corrections to get back into bounds.
*/
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
double latitude=0, longitude=0;
if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
}
if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
}
if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
}
if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
}
return new LatLng(latitude, longitude);
}
Then the Handler that controls the overscroll (And overzoom) limiting it every 100ms.
/**
* Bounds the user to the overlay.
*/
private class OverscrollHandler extends Handler {
#Override
public void handleMessage(Message msg) {
CameraPosition position = mMap.getCameraPosition();
VisibleRegion region = mMap.getProjection().getVisibleRegion();
float zoom = 0;
if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
LatLng correction = getLatLngCorrection(region.latLngBounds);
if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
zoom = (zoom==0)?position.zoom:zoom;
double lat = position.target.latitude + correction.latitude;
double lon = position.target.longitude + correction.longitude;
CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
mMap.moveCamera(update);
}
/* Recursively call handler every 100ms */
sendEmptyMessageDelayed(0,100);
}
}
This handler must be defined as a field inside current class (I did this in a class that extends SupportMapFragment)
private OverscrollHandler mOverscrollHandler = new OverscrollHandler();
And lastly it must be called for the first time, i did it at the end of onActivityCreated to be sure that map exists.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mMap = getMap();
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
mMap.moveCamera(upd);
mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}
Hope you will find it useful!
Instead of using new and shiny push technology which is onCameraChange, you may try to use old poll technology: map.getCameraPosition() or map.getProjection().getVisibleRegion(). You can then check if returned value is what you like and if not, map.moveCamera(...).
Basically you need a Handler, which will get the value of camera position in handleMessage and you send messages to this handler every 10ms or so. Inside handleMessage do sendEmptyMessageDelayed.
You can also use Runnable instead of Message (but that's a matter of taste).
limit zoom you can user this code
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
In maps API v2 there's a Min/MaxZoomLevel on the GoogleMap class, but I don't know if you can set it in any way.
Another approach would be to add a
GoogleMap.OnCameraChangeListener
to your map and by implementing
public void onCameraChange(CameraPosition cameraPosition);
To restrict the visible area, using GoogleMap.moveCamera(cameraPosition)
That is if you wish the user to be able to scroll or zoom "some".
You can also totally deactivate the scroll/zoom events via GoogleMapOptions

Using getTileURL in Android Application with GeoServer

We are just starting to work with Google Maps on Android and have a GeoServer set up to provide tiles which we would like to add as overlay on the map. So far, I have followed a few tutorials and references to get started.
For getting MyLocation
Setting up WMS on Android
WMS Reference
The problem: While the url that I am generating in the getTileUrl function in the TileProviderFactory does indeed return a png image when I set a breakpoint and copy and paste the url into a browser, it does not load onto the map as an overlay on the Android device. There are no errors being thrown from what I can see and after reading this I am not sure if there will be any as they have indicated that the errors are being muted.
What I am wondering is if you can see any immediate issues in my code or have any suggestions for debugging where I will be able to tell if the application is actually communicating with my GeoServer to retrieve the image or not. I've looked at the log on the GeoServer and it seems as though only my browser requests are going through and it's not receiving any requests from Android (it's a bit difficult to tell because we have other applications using the server as well). The Android phone is connected by both wifi and cell and has gps enabled. As a last resort I have tried changing the tile overlay zIndex and setting it to visible but this didn't seem to make any difference.
EDIT: Android device is definitely NOT communicating with GeoServer at this point.
EDIT 2: Able to load static images from websites
(like this) as overlays and found that I am getting the following exception on testing out an HTTP Request to the formed URL:
W/System.err(10601): java.net.SocketException: The operation timed out
W/System.err(10601): at org.apache.harmony.luni.platform.OSNetworkSystem
.connectStreamWithTimeoutSocketImpl(Native Method)
W/System.err(10601): at org.apache.harmony.luni.net.PlainSocketImpl
.connect(PlainSocketImpl.java:244)
W/System.err(10601): at org.apache.harmony.luni.net.PlainSocketImpl
.connect(PlainSocketImpl.java:533)
W/System.err(10601): at java.net.Socket
.connect(Socket.java:1074)
W/System.err(10601): at org.apache.http.conn.scheme.PlainSocketFactory
.connectSocket(PlainSocketFactory.java:119)
Thanks.
MapTestActivity
public class MapTestActivity extends FragmentActivity
implements LocationListener, LocationSource{
private GoogleMap mMap;
private OnLocationChangedListener mListener;
private LocationManager locationManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map_test);
setupLocationManager();
setupMapIfNeeded();
}
private void setupLocationManager() {
this.locationManager =
(LocationManager) getSystemService(LOCATION_SERVICE);
if (locationManager != null) {
boolean gpsIsEnabled = locationManager.isProviderEnabled(
LocationManager.GPS_PROVIDER);
boolean networkIsEnabled = locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER);
if(gpsIsEnabled) {
this.locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 5000L, 10F, this);
}
else if(networkIsEnabled) {
this.locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 5000L, 10F, this);
}
else {
//Show an error dialog that GPS is disabled...
}
}
else {
// Show some generic error dialog because
// something must have gone wrong with location manager.
}
}
private void setupMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the
// map.
if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map)).getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
setUpMap();
}
mMap.setLocationSource(this);
}
}
private void setUpMap() {
// TODO Auto-generated method stub
mMap.setMyLocationEnabled(true);
TileProvider geoServerTileProvider = TileProviderFactory
.getGeoServerTileProvider();
TileOverlay geoServerTileOverlay = mMap.addTileOverlay(
new TileOverlayOptions()
.tileProvider(geoServerTileProvider)
.zIndex(10000)
.visible(true));
}
// Non-relevant listener methods removed
}
TileProviderFactory
public class TileProviderFactory {
public static GeoServerTileProvider getGeoServerTileProvider() {
String baseURL = "mytileserver.com";
String version = "1.3.0";
String request = "GetMap";
String format = "image/png";
String srs = "EPSG:900913";
String service = "WMS";
String width = "256";
String height = "256";
String styles = "";
String layers = "wtx:road_hazards";
final String URL_STRING = baseURL +
"&LAYERS=" + layers +
"&VERSION=" + version +
"&SERVICE=" + service +
"&REQUEST=" + request +
"&TRANSPARENT=TRUE&STYLES=" + styles +
"&FORMAT=" + format +
"&SRS=" + srs +
"&BBOX=%f,%f,%f,%f" +
"&WIDTH=" + width +
"&HEIGHT=" + height;
GeoServerTileProvider tileProvider =
new GeoServerTileProvider(256,256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
try {
double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, URL_STRING, bbox[MINX],
bbox[MINY], bbox[MAXX], bbox[MAXY]);
Log.d("GeoServerTileURL", s);
URL url = null;
try {
url = new URL(s);
}
catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
catch (RuntimeException e) {
Log.d("GeoServerTileException",
"getTile x=" + x + ", y=" + y +
", zoomLevel=" + zoom +
" raised an exception", e);
throw e;
}
}
};
return tileProvider;
}
}
GeoServerTileProvider
public abstract class GeoServerTileProvider extends UrlTileProvider{
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN =
{-20037508.34789244, 20037508.34789244};
//array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MINY = 1;
protected static final int MAXX = 2;
protected static final int MAXY = 3;
public GeoServerTileProvider(int width, int height) {
super(width, height);
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x+1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y+1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
This turned out to be a network issue and completely unrelated to my implementation which is "correct". I guess this question will serve well as an example for others who are getting started with Android + GeoServer implementation so I will leave it up.

limit scrolling and zooming Google Maps Android API v2

I've added GroundOverlay to map and want to limit scrolling and zooming within this area.
How to limit scrolling within some bounds on android google maps?
Is it possible to get instantly motion points from MapFragment?
Please, help me.
Constraining the camera has (finally!) been added as a feature as part of the release of Google Play Services 9.4 -- you can call setLatLngBoundsForCameraTarget(LatLngBounds bounds) to set the allowed panning area.
// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));
// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
You can find a thorough explanation in the documentation: Restricting the user's panning to a given area and a sample activity in GitHub.
May be it is too late, but here is my solution:
Disabled built-in GoogleMap's gestures.
Added custom gestures (for scrolling, fling and scaling).
Checking for allowed area when processing events.
Set bounds/zoom manually with standard Map's functions.
And here is my example:
[UPDATED]
There was a problem - when touch events were recieved before map was initialized.
check null values in onInterceptTouchEvent
Also I have discovered that my solution is slightly slowly than build-in function.
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;
public class RestrictedMapView extends MapView {
public static float MAX_ZOOM = 20;
public static float MIN_ZOOM = 5;
public static float MIN_ZOOM_FOR_FLING = 7;
public static double MAX_LONGITUDE = 183.61;
public static double MIN_LONGITUDE = 159.31;
public static double MAX_LATITUDE = -32.98;
public static double MIN_LATITUDE = -53.82;
public static double DEF_LATITUDE = -41.78;
public static double DEF_LONGITUDE = 173.02;
public static float DEF_ZOOM = 7;
private Handler mHandler = new Handler();
private Context mContext;
private VisibleRegion mLastCorrectRegion = null;
private boolean mIsInAnimation = false;
public RestrictedMapView(Context c, AttributeSet a, int o) {
super(c, a, o);
init(c);
}
public RestrictedMapView(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public RestrictedMapView(Context c) {
super(c);
init(c);
}
public RestrictedMapView(Context c, GoogleMapOptions o) {
super(c, o);
init(c);
}
private GestureDetector mGestureDetector = null;
private GestureDetector.SimpleOnGestureListener mGestudeListener =
new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, 0);
return true;
}
#Override
public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
if (zoom < MIN_ZOOM_FOR_FLING)
return false;
int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (velocity < 500) return false;
double k1 = 0.002d; /*exipemental*/
double k2 = 0.002d;/*exipemental*/
LatLng target = map.getCameraPosition().target;
Point screenPoint = map.getProjection().toScreenLocation(target);
Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
mapNewTarget,map.getCameraPosition().zoom);
tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);
return true;
}
};
private ScaleGestureDetector mScaleGestureDetector = null;
private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
#Override
public boolean onScale (ScaleGestureDetector detector) {
if (mIsInAnimation) return false;
GoogleMap map = getMap();
double zoom = map.getCameraPosition().zoom;
double k = 1d / detector.getScaleFactor();
int x = (int) detector.getFocusX();
int y = (int) detector.getFocusY();
LatLng mapFocus = map.getProjection().
fromScreenLocation(new Point(x, y));
LatLng target = map.getCameraPosition().target;
zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
if (zoom < MIN_ZOOM)
if (zoom == MIN_ZOOM) return false;
else zoom = MIN_ZOOM;
if (zoom > MAX_ZOOM)
if (zoom == MAX_ZOOM) return false;
else zoom = MAX_ZOOM;
double dx = norm(mapFocus.longitude) - norm(target.longitude);
double dy = mapFocus.latitude - target.latitude;
double dk = 1d - 1d / k;
LatLng newTarget = new LatLng(target.latitude - dy * dk,
norm(target.longitude) - dx * dk);
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);
tryUpdateCamera(update, 0);
return true;
}
};
private void tryUpdateCamera(CameraUpdate update, int animateTime) {
GoogleMap map = getMap();
final VisibleRegion reg = map.getProjection().getVisibleRegion();
if (animateTime <= 0) {
map.moveCamera(update);
checkCurrentRegion(reg);
} else {
mIsInAnimation = true;
map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
#Override
public void onCancel() {
mIsInAnimation = false;
checkCurrentRegion(reg);
}
});
}
}
private void checkCurrentRegion(VisibleRegion oldReg) {
GoogleMap map = getMap();
VisibleRegion regNew = map.getProjection().getVisibleRegion();
if (checkBounds(regNew)) {
mLastCorrectRegion = regNew;
} else {
if (mLastCorrectRegion != null)
oldReg = mLastCorrectRegion;
mIsInAnimation = true;
map.animateCamera(CameraUpdateFactory.newLatLngBounds(
oldReg.latLngBounds, 0),
200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
mIsInAnimation = false;
}
#Override
public void onCancel() {
mIsInAnimation = false;
}
});
}
}
/**
*
*
* #param lonVal
* #return
*/
private double norm(double lonVal) {
while (lonVal > 360d) lonVal -= 360d;
while (lonVal < -360d) lonVal += 360d;
if (lonVal < 0) lonVal = 360d + lonVal;
return lonVal;
}
private double denorm(double lonVal) {
if (lonVal > 180d) lonVal = -360d + lonVal;
return lonVal;
}
private boolean checkBounds(VisibleRegion reg) {
double left = Math.min(
Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double right = Math.max(
Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
double top = Math.max(
Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.max(reg.farRight.latitude, reg.nearRight.latitude));
double bottom = Math.min(
Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
Math.min(reg.farRight.latitude, reg.nearRight.latitude));
boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
bottom < MIN_LATITUDE || top > MAX_LATITUDE;
return !limitBounds;
}
private void init(Context c) {
try {
MapsInitializer.initialize(c);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
mContext = c;
mHandler.post(new Runnable() {
#Override
public void run() {
GoogleMap map = getMap();
if (map != null) {
getMap().getUiSettings().setZoomControlsEnabled(false);
map.getUiSettings().setAllGesturesEnabled(false);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
mLastCorrectRegion = map.getProjection().getVisibleRegion();
mGestureDetector = new GestureDetector(mContext, mGestudeListener);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
} else mHandler.post(this);
}
});
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
}
Definition within my xml-layout of fragment:
<com.package....RestrictedMapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In xml file it is possible to define custom zoom/location buttons and set click listeners for manual manipulating camera (in this case you have to check MAX_ZOOM and MIN_ZOOM, and check if the current location is within the allowed bounds).
Too bad that google doesn't let us intercept and block the user, i found that MaciejGórski's answer is the one that suits best to my needs.
I wanted to share my solution (Based on his answer) with you.
First i defined the bounds and max/min zoom:
private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;
Then i created this little function to test if current camera bounds are outside of max bounds and return the difference in latitude and longitude.
/**
* Returns the correction for Lat and Lng if camera is trying to get outside of visible map
* #param cameraBounds Current camera bounds
* #return Latitude and Longitude corrections to get back into bounds.
*/
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
double latitude=0, longitude=0;
if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
}
if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
}
if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
}
if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
}
return new LatLng(latitude, longitude);
}
Then the Handler that controls the overscroll (And overzoom) limiting it every 100ms.
/**
* Bounds the user to the overlay.
*/
private class OverscrollHandler extends Handler {
#Override
public void handleMessage(Message msg) {
CameraPosition position = mMap.getCameraPosition();
VisibleRegion region = mMap.getProjection().getVisibleRegion();
float zoom = 0;
if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
LatLng correction = getLatLngCorrection(region.latLngBounds);
if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
zoom = (zoom==0)?position.zoom:zoom;
double lat = position.target.latitude + correction.latitude;
double lon = position.target.longitude + correction.longitude;
CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
mMap.moveCamera(update);
}
/* Recursively call handler every 100ms */
sendEmptyMessageDelayed(0,100);
}
}
This handler must be defined as a field inside current class (I did this in a class that extends SupportMapFragment)
private OverscrollHandler mOverscrollHandler = new OverscrollHandler();
And lastly it must be called for the first time, i did it at the end of onActivityCreated to be sure that map exists.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mMap = getMap();
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
mMap.moveCamera(upd);
mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}
Hope you will find it useful!
Instead of using new and shiny push technology which is onCameraChange, you may try to use old poll technology: map.getCameraPosition() or map.getProjection().getVisibleRegion(). You can then check if returned value is what you like and if not, map.moveCamera(...).
Basically you need a Handler, which will get the value of camera position in handleMessage and you send messages to this handler every 10ms or so. Inside handleMessage do sendEmptyMessageDelayed.
You can also use Runnable instead of Message (but that's a matter of taste).
limit zoom you can user this code
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
In maps API v2 there's a Min/MaxZoomLevel on the GoogleMap class, but I don't know if you can set it in any way.
Another approach would be to add a
GoogleMap.OnCameraChangeListener
to your map and by implementing
public void onCameraChange(CameraPosition cameraPosition);
To restrict the visible area, using GoogleMap.moveCamera(cameraPosition)
That is if you wish the user to be able to scroll or zoom "some".
You can also totally deactivate the scroll/zoom events via GoogleMapOptions

Categories

Resources