Monday 14 November 2011

Thursday 11 August 2011

ArcGIS Android - Persisting map state

ESRI documentation describes how to use onRetainNonConfigurationInstance for handling map rotation so that the scale, centre and layer state of the map is saved when onCreate is called (e.g http://help.arcgis.com/en/arcgismobile/10.0/apis/android/help/#/My_first_application/011900000005000000/) While this works fine I would like my application to remember map setting not only when the phone is rotated, but also when the user closes and re-enters the application. For this to work I save the map state values to the PreferenceManager like this when app is paused:
@Override
protected void onPause() {
        super.onPause();
        SharedPreferences settings = getSharedPreferences("mapPreference", 0);
        SharedPreferences.Editor ed = settings.edit();
        ed.putString("mapstate", map.retainState());
        ed.commit();
    }
 
And then retrieve the map settings in the onCreate method:
@Override
public void onCreate(Bundle savedInstanceState) {    	
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

// get the map settings
SharedPreferences settings = getSharedPreferences("mapPreference", 0);
         String mapState = settings.getString("mapstate", null);
         if (mapState != null){
        	map.restoreState(mapState);
         } 

Tuesday 9 August 2011

ArcGIS - Cascading WMS is a No-Go

So I wanted to add geographic information to the geological map. I have access to a long series of a nice WMS at http://www.kortforsyningen.dk/ which would be an excellent option. But alas, it is not possible with the ArcGIS server to publish a WMS based on a mxd-file that includes a WMS: http://support.esri.com/en/knowledgebase/techarticles/detail/35903.

This is pretty annoying - Cascading a WMS would be no problem if I had used MapServer. Well, now I am married to ESRI so I have to suffer the consequences...

Instead I found a georeferenced map image that I put on a transparent layer and add it on top of the geological map layer. Result is not as nice as it would have been if I could have used WMS from kortforsyningen but it is usable:


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