Friday 22 July 2011

ArcGIS for Android

As per previous post, I was playing around with Google Map and overlays. I found this link to put geometries on top of Google map in an AsyncTask: http://www.curious-creature.org/2009/03/01/android-layout-tricks-3-optimize-part-1/, but it seems much more difficult to add WMS on top of Google maps using an AsyncTask....

Alright, so I found out that ESRI is developing an API for Android: http://resources.arcgis.com/content/arcgis-android/api. The samples look nice and I decided to give it a go, especially since my work place uses ESRI products. The presentation DS2011: Introduction to ArcGIS API for Android gives nice and detailed information on how to install the eclipse plugin and get started using the API. After a days work, I feel pretty sure that I will base my application on the ESRI API.

Here is how far I have gotten:

First of all I published a new WMS on our ArcGis Server: http://geuswebgis01.geus.dk:8399/arcgis/rest/services/Danmarksgeologi/j200/MapServer. The WMS renders the geological map of Denmark at scale 1:200.000.

Here is how to add the WMS in the layout file (lines 7-13):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    
 <com.esri.android.map.MapView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/map"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
		<com.esri.android.map.ags.ArcGISDynamicMapServiceLayer url="http://geuswebgis01.geus.dk:8399/arcgis/rest/services/Danmarksgeologi/j200/MapServer"/>
</com.esri.android.map.MapView>

    <RelativeLayout 
        android:id="@+id/InnerRelativeLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true" >
        
        <Button 
        	android:text="Settings"        	
        	android:id="@+id/settingsbutton"
        	android:layout_height="wrap_content"
        	android:layout_width="wrap_content">
        </Button>
        
        <Button 
        	android:text="Nyheder"
        	android:id="@+id/nyhederbutton"
        	android:layout_toRightOf="@+id/settingsbutton"
        	android:layout_height="wrap_content"        	
        	android:layout_width="wrap_content">
        </Button>
        
    </RelativeLayout>
 
</RelativeLayout>


Panning and zooming works really well. Now it is time to explore the remaining parts of the ESRI Android API...



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