Wednesday 20 July 2011

Adding a WMS to google maps

Here is my first working version of an Android application in which the user can see and interact with a Web Map Service (WMS), that shows geological information for Denmark. The WMS is described here: http://geusjuptest.geus.dk/OneGeologyEurope/

The geological WMS is shown using Google Maps.
Here is the Activity class that displays the google map as well as the WMS (i.e. the myOverlay object)
package dk.geus;

import java.util.List;

import android.os.Bundle;
import android.preference.PreferenceManager;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

public class Map01 extends MapActivity {
	private MapView mapView;
	
	 @Override
	  public void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState); 
	    setContentView(R.layout.map);
	    mapView = (MapView)findViewById(R.id.map_view);
	    MapController mapController = mapView.getController();	    
	    mapController.setCenter(new GeoPoint(55700000, 12600000));
	    mapController.setZoom(10);
	    mapView.setClickable(true);
	    mapView.setEnabled(true);
	    mapView.setBuiltInZoomControls(true);	    
/*	    MyLocationOverlay myLocOverlay = new MyLocationOverlay(this, mapView);
	    myLocOverlay.enableMyLocation();
	    mapView.getOverlays().add(myLocOverlay);*/    
	    List overlays = mapView.getOverlays();
	    WMSOverlay myOverlay = new WMSOverlay();
	    boolean lithologyClicked = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lithologyClicked", true);
	    if (lithologyClicked){
	    	overlays.add(myOverlay);
	    }
	    mapView.postInvalidate();
	 }

	@Override
	protected boolean isRouteDisplayed() {
		// TODO Auto-generated method stub
		return false;
	}
}




The WMS overlay extends the com.google.android.maps.Overlay Class. In the draw method the WMS is defined (i.e. the WMSLoader class):

>package dk.geus;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

public class WMSOverlay extends Overlay {	
		  @Override
		  public void draw(Canvas canvas, MapView mapView, boolean shadow) {
			   super.draw(canvas, mapView, shadow);
			   WMSLoader wmsclient = new WMSLoader();
			   GeoPoint[] cornerCoords = MapUtils.getCornerCoordinates(mapView.getProjection(), canvas);		   
			   Bitmap image = wmsclient.loadMap(canvas.getWidth(), canvas.getHeight(), cornerCoords[0], cornerCoords[1]);
			   Paint semitransparent = new Paint();
			   semitransparent.setAlpha(0x888);
			   canvas.drawBitmap(image, 0, 0, semitransparent);
		  }		  
}



The only missing piece is the WMSLoader class - Here it is:

package dk.geus;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.google.android.maps.GeoPoint;

public class WMSLoader {
 public static String TAG = "WMSLoader Exception";
 
 public Bitmap loadMap(int width, int height, GeoPoint ul, GeoPoint lr) {
  URL url = null;

  try {
	  
//	  http://geusjuptest.geus.dk/oneGEconnector/?service=WMS&version=1.1.1&styles=default&request=GetMap&layers=OGE_1M_surface_GeologicUnit&width=2000&height=2000&bbox=7,54,15.5,58&format=image/png&srs=epsg:4326
   url = new URL(String.format("http://geusjuptest.geus.dk/oneGEconnector/?"  +
		     "LAYERS=OGE_1M_surface_GeologicUnit&TRANSPARENT=true&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=default&EXCEPTIONS=application/vnd.ogc.se_inimage&SRS=EPSG:4326" + "" +
		     "&BBOX=%f,%f,%f,%f&WIDTH=%d&HEIGHT=%d", 
		     MapUtils.longitude(ul), MapUtils.latitude(lr),
		     MapUtils.longitude(lr), MapUtils.latitude(ul), width, height));
  
  } catch (MalformedURLException e) {
   Log.e(TAG, e.getMessage());
  }
  InputStream input = null;
  try {
   input = url.openStream();
  } catch (IOException e) {
   Log.wtf(TAG, "****************** Error in WMSLoader: " + e.getMessage());
  }
  return BitmapFactory.decodeStream(input);
 }
} 


And then finally the little MapUtils helper class:
package dk.geus;

import android.graphics.Canvas;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.Projection;

public class MapUtils {
	
	static float longitude(GeoPoint point){
		return (float) (point.getLongitudeE6()/1e6);
	}
	
	static float latitude(GeoPoint point){
		return (float) (point.getLatitudeE6()/1e6);
	}
	
	static  GeoPoint[] getCornerCoordinates(Projection projection,Canvas canvas){
		GeoPoint[] geoPoint = new GeoPoint[2];
		geoPoint[0]= projection.fromPixels(0, 0);
		geoPoint[1]= projection.fromPixels(canvas.getWidth() - 1, canvas.getHeight() - 1); 
		return geoPoint;
		
	}

}




The application works, but has some major limitations. Main problem is that Panning and Zooming is too slow - Next step will be to retrieve the WMS overlay in an  AsyncActivity

5 comments:

  1. thanks, that works, but wow panning is slow ^^

    ReplyDelete
  2. Hello,

    I already made lots of tests, but I didn't get your tutorial running. Now I found in the LogCat the following message:
    android.os.NetworkOnMainThreadException
    "The exception that is thrown when an application attempts to perform a networking operation on its main thread."
    Which Version did you use? I used Ice Cream Sandwich 4.x.

    Did you write an AsyncActivity? I would be interested in that.

    Nevertheless, thank you for that tutorial.
    Cheers,
    Christoph

    ReplyDelete
    Replies
    1. If you are using ICS add the following line:

      StrictMode.setThreadPolicy(new ThreadPolicy.Builder().permitNetwork().build());

      It's not a good solution, but you won't get an NetworkOnMainThreadException

      Delete
  3. For my application, this example does not work. I immediately turned off.

    ReplyDelete
  4. Thanks for nice example. Surely, should be done by some async loading. At least, you don't need to perform the same twice when wms map rendering itself is required and a shadow rendering is required. Just return draw() method when shadow is true.

    ReplyDelete