Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Wednesday, 13 July 2011

How to search a list of places by keyword and display them with custom icons

What I need: let the user search for a place by name and display the search results obtained on a Map.

I won't go into the details of the layout and focus on the map aspect. The interface looks like this:


The user enters the place he is looking for and the application pulls available location matching the name, displays them in a list and on the map. To link the list and the icons items, numbers are used.
There are two challenges here:
1) get the list of places matching the name
2) render the icons on the map with a number

Getting the list of places is achieved thanks to GeoCoder with this simple snippet of code:

Geocoder geoCoder = new Geocoder(this, Locale.getDefault());
        try {
            List<Address> places = geoCoder.getFromLocationName(locationName, 10);
        } catch (IOException e) {                
            e.printStackTrace();
            Utils.alert(this, "Could not retrieve locations, do you have an Internet connection running?");
        }   


We need to add some code to fill the list and add the icons to the Map.

Geocoder geoCoder = new Geocoder(this, Locale.getDefault());
        try {
            List<Address> places = geoCoder.getFromLocationName(locationName, 10);
            placesList.setAdapter(new AddressesAdapter(this, places));
            // add places to the map
         // clear previous overlays
            List<Overlay> listOfOverlays = mapView.getOverlays();
            if(listOfOverlays != null) {
                listOfOverlays.clear();
                mapView.invalidate();
            }
            // add place markers
            ArrayList<GeoPoint> points = new ArrayList<GeoPoint>();
            int index = 0;
            for(Address address : places) {
                Marker marker = new Marker(this, address, index++);
                listOfOverlays.add(marker);
            }
        } catch (IOException e) {                
            e.printStackTrace();
            Utils.alert(this, "Could not retrieve locations, do you have an Internet connection running?");
        }   

We create our own adapter to display the places in the list. Essentially this adapter builds the name of the place and appends a number to it. To build the place name, use the following:

int length = addresses.get(position).getMaxAddressLineIndex();
     String add = position + ") ";
            for (int i = 0; i < length; i++) {
             String line = addresses.get(position).getAddressLine(i);
             if(line != null) {
                 add += line;
                 if(i != length - 1) {
                  add += ", ";
                 }
             }
            }
The markers are displayed by using our own Marker class that extends the Overlay class. The draw method is overriden to use our own bitmap and draw the index number in the middle of the canvas.
@Override
    public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) 
    {
        super.draw(canvas, mapView, shadow);                   

        //---translate the GeoPoint to screen pixels---
        Point screenPts = new Point();
        mapView.getProjection().toPixels(this.geoPoint, screenPts);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setTextSize(bitmap.getHeight() / 2);
        paint.setColor(Color.DKGRAY);
        paint.setTextAlign(Align.CENTER);
        //---add the marker---
        canvas.drawBitmap(bitmap, screenPts.x - bitmap.getWidth() / 2, screenPts.y - bitmap.getHeight(), null);         
        canvas.drawText(index + "", screenPts.x, screenPts.y - bitmap.getHeight() / 2, paint);
        return true;
    }


One last word on testing. Just a small tip on how to test the location with the android avd. In Eclipse go to windows->open perspective->other->ddms. Go to the emulator control panel on the right and scroll down for location controls! Otherwise you can telnet your avd (while running) by using telnet localhost 5554, once logged in send the command geo fix 'yourlat' 'yourlong'.

This concludes the series on Compastic! Remember the full code is available @ http://code.google.com/p/compastic/ and the app is on the Android Market @ https://market.android.com/details?id=com.metaaps.mobile.compastic&feature=search_result

How to draw a great circle on a Map in Android

What I need: draw a great circle between two points on a Map.

Again in a web application using the Google Maps API this would be a no brainer, as polylines can be directly added to the map and include a great circle option. However this is unfortunately not the case with Android. Actually even drawing a straight line is more complicated. Let's start with a simple line then.

To display a line on the maps we will have to extend the Overlay Class and override the draw method. Drawing a line is quite trivial when you have your canvas. The issue here is to find out which coordinates to use for your points. Remember your points are expressed as Lat, Long coordinates, in degrees x 1E6 actually in the GeoPoint Map API. You will need to convert that to the map coordinate. Thankfully there are a couple of methods to help you achieve that goal in the MapView object.
Once you have you canvas coordinate points it is a pretty trivial task as shown in the small code below:

public class LineOverlay extends Overlay {

    private GeoPoint startGeoPoint;
    private GeoPoint stopGeoPoint;
    
    public LineOverlay(Context context, Location startLocation, Location stopLocation) {
     this.startGeoPoint = new GeoPoint((int) (startLocation.getLatitude() * 1E6), (int) (startLocation.getLongitude() * 1E6));
     this.stopGeoPoint = new GeoPoint((int) (stopLocation.getLatitude() * 1E6), (int) (stopLocation.getLongitude() * 1E6));
 }

 @Override
    public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) 
    {
        super.draw(canvas, mapView, shadow);                   

        //---translate the GeoPoint to screen pixels---
        Point startScreenPts = new Point();
        mapView.getProjection().toPixels(this.startGeoPoint, startScreenPts);
        Point stopScreenPts = new Point();
        mapView.getProjection().toPixels(this.stopGeoPoint, stopScreenPts);
        
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(3);
        paint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawLine(startScreenPts.x, startScreenPts.y, stopScreenPts.x, stopScreenPts.y, paint);         
        return true;
    }

}

If I use the code above this is the line I get:



Which is not bad but not very correct from a geographical point of view. Enters the Great Circle and some calculations algorithm.

@Override
    public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) 
    {
        super.draw(canvas, mapView, shadow);                   

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(3);
        paint.setStrokeCap(Paint.Cap.ROUND);
        // get geodetic points
        List<GeoPoint> points = getGeodeticPoints(startGeoPoint, stopGeoPoint, 20);
        Point previousPoint = null;
        for(GeoPoint point : points) {
         // convert points to map coordinates
            Point mapPoint = new Point();
            mapView.getProjection().toPixels(point, mapPoint);
            if(previousPoint != null) {
                canvas.drawLine(previousPoint.x, previousPoint.y, mapPoint.x, mapPoint.y, paint);         
            }
            previousPoint = mapPoint;
        }

        return true;
    }

 static public List<GeoPoint> getGeodeticPoints(GeoPoint p1, GeoPoint p2, int numberpoints)
    {
                // adapted from page http://maps.forum.nu/gm_flight_path.html
  ArrayList<GeoPoint> fPoints = new ArrayList<GeoPoint>();
  // convert to radians
  double lat1 = (double) p1.getLatitudeE6() / 1E6 * Math.PI / 180;
  double lon1 = (double) p1.getLongitudeE6() / 1E6 * Math.PI / 180;
  double lat2 = (double) p2.getLatitudeE6() / 1E6 * Math.PI / 180;
  double lon2 = (double) p2.getLongitudeE6() / 1E6 * Math.PI / 180;

  double d = 2*Math.asin(Math.sqrt( Math.pow((Math.sin((lat1-lat2)/2)),2) + Math.cos(lat1)*Math.cos(lat2)*Math.pow((Math.sin((lon1-lon2)/2)),2)));
  double bearing = Math.atan2(Math.sin(lon1-lon2)*Math.cos(lat2), Math.cos(lat1)*Math.sin(lat2)-Math.sin(lat1)*Math.cos(lat2)*Math.cos(lon1-lon2))  / -(Math.PI/180);
  bearing = bearing < 0 ? 360 + bearing : bearing;

  for (int n = 0 ; n < numberpoints + 1; n++ ) {
   double f = (1.0/numberpoints) * n;
   double A = Math.sin((1-f)*d)/Math.sin(d);
   double B = Math.sin(f*d)/Math.sin(d);
   double x = A*Math.cos(lat1)*Math.cos(lon1) +  B*Math.cos(lat2)*Math.cos(lon2);
   double y = A*Math.cos(lat1)*Math.sin(lon1) +  B*Math.cos(lat2)*Math.sin(lon2);
   double z = A*Math.sin(lat1)           +  B*Math.sin(lat2);

   double latN = Math.atan2(z,Math.sqrt(Math.pow(x,2)+Math.pow(y,2)));
   double lonN = Math.atan2(y,x);
   fPoints.add(new GeoPoint((int) (latN/(Math.PI/180) * 1E6), (int) (lonN/(Math.PI/180) * 1E6)));
  }

        return fPoints;
 }

}



With the above code the line is now a great circle!


The great circle calculation algorithm was adapted to Java and Android from the http://maps.forum.nu/gm_flight_path.html blog page.

In the next posts I will touch the geocoder and the custom icon display side of the application.

How to create the Map part in Compastic

There are two maps display in Compastic!

  1. The first one displays the current location and the place of interest location. Both points are connected with a great circle line.
  2. The second one is used when the user looks for a place of interest using a keyword. The results returned by the geoCoder are displayed on a map so that the user can choose which is the correct one.

Now in a javascript maps api, this would be a no brainer piece of work, 5 mns at most. With Android unfortunately things are not so simple.

To start with you will have to set up your Map API environment. There are already countless blogs on creating a map with Android. I found this one very good for learning the basics and getting quickly up to speed. I will not paraphrase this tutorial but focus on the specifics of Compastic that are not covered in the blog.

The specifics of Compastic! are basically:
- display a great circle line to connect the two points of 1)
- geo search based on a keyword for 2). I want the application to return a list of places based on keywords I enter. I want the application to display those in a list view and on the map with a little icon and label to match the list item.

In the next post I will explain how to display a great circle on a Map View.

Monday, 11 July 2011

Compastic!

There are millions of Compass applications out there on Android. They range from trivial to very elaborate, with 3D views and camera overlays. The market winner is clearly Compass by Catch.com with over 12 millions downloads.
I bet it has been on the market since its inception to get top of the "Compass" search list. It is a sad truth that the Android Market search ranking are highly correlated to the time you have been on the market. It's really hard to get started in a crowded area - unless you have a very viral concept/application like the all mighty "Angry Birds".

Compass is by all means a nice application, very professional. In my opinion it missed a little something though. I mean I have no real use of a compass, living most of the time in town or near town. However I like geography and I like knowing my bearings so I decided to add yet another application to the compass list of applications.
I named it "Compastic!" and its sole purpose is to tell you which direction to look at and how far are your places of interest.
Let's take two use cases:
1) I am in Paris and want to know where to look for the Tour Eiffel
2) I am lost in the forest and I want to find my way back to the car park.
I can a third one, a bit more geeky, I just want to know where and how far is San Francisco from my sofa?

That's for the basic concept. Compass doesn't do it, or at least is not that straightforward.
I skipped the Market Study phase. I didn't download the 100+ compass applications to see if one was actually providing this feature. I just decided that I could take a couple of days off my work and implement it.

I am no Android professional but I surely know my Java. I have released a few Android applications in the past. It's a hobby and WE work only. I do have quite a few more ideas, Mobile Application is such an exciting field, with countless possibilities.

Next post I will dive into the details of how I implemented Compastic!

By the way, my other apps:
Domino! (a prank, half a day to build, my very first one)
- Kids Stars (some pissed off customer, there was a bug, has left me with the dreaded 1 star, and a terrible rating)
- and Compastic! of course