Monday, January 17, 2011

Add Overlay on Map

When developing Map based application, it’s very likely you want to display some point of interests on top of the map or draw some shape on the map.
The RasterMap in the Guidebee Map API actually is an image

protected void paint(Graphics g) {
            map.paint(mapGraphics);
            g.drawImage((Image) mapImage.getNativeImage(), 0, 0, 0);
       //start drawing your own sharps or images.
            ...
 }







So a simple way is to draw the POIs or shapes after you render the map on screen.
Remember to use proper coordinate system when draw on screen. The Map API uses latitude, longitude coordinate system while graphics system (screen) uses device coordinates. RasterMap has methods fromLatLngToScreenPixel to convert coordinate for latitude, longitude pair to x,y coordinate on screen.  And method fromScreenPixelToLatLng to covert x,y coordinate on screen to latitude,longitude on map.
The following example uses a better solution to draw overlay on the map: define a subclass of MapLayer. RasterMap is also a map layer container, a sub class of MapLayerContainer, which can be used to manage mulptile map layers. Think of these layers as transparencies where each layer contains a different part of the map. The layers are stacked one on top of the other and allow you to see all aspects of the map at the same time.
The following examples display several POIs, a triangle and center cross on the map.

public class MapOverlayMIDP extends MapDemoMIDP {

    OverLayMapLayer mapLayer;

    public void startApp() {
        init();
        GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
        map.setCenter(center, 9, MapType.GOOGLECHINA);
        Display.getDisplay(this).setCurrent(canvas);
        mapLayer = new OverLayMapLayer(canvas.getWidth(),
                canvas.getHeight());
        map.addMapLayer(mapLayer);
    }

    class OverLayMapLayer extends MapLayer {

        GeoLatLng pt1 = new GeoLatLng(32.345281, 118.84261);
        GeoLatLng pt2 = new GeoLatLng(32.05899, 118.62789);
        GeoLatLng pt3 = new GeoLatLng(32.011811, 118.798656);

        public OverLayMapLayer(int width, int height) {
            super(width, height);
        }

        public void paint(IGraphics graphics, int offsetX, int offsetY) {
            drawCursor(graphics);
            drawTriangle(graphics);
            drawPoint(graphics, pt1);
            drawPoint(graphics, pt2);
            drawPoint(graphics, pt3);

        }

        public void drawTriangle(IGraphics g) {
            GeoPoint ptOnScreen1 = map.fromLatLngToScreenPixel(pt1);
            GeoPoint ptOnScreen2 = map.fromLatLngToScreenPixel(pt2);
            GeoPoint ptOnScreen3 = map.fromLatLngToScreenPixel(pt3);
            g.setColor(0x0000FF);
           
            g.drawLine((int) ptOnScreen1.x, (int) ptOnScreen1.y,
                    (int) ptOnScreen2.x, (int) ptOnScreen2.y);
            g.drawLine((int) ptOnScreen2.x, (int) ptOnScreen2.y,
                    (int) ptOnScreen3.x, (int) ptOnScreen3.y);
            g.drawLine((int) ptOnScreen1.x, (int) ptOnScreen1.y,
                    (int) ptOnScreen3.x, (int) ptOnScreen3.y);
        }

        public void drawPoint(IGraphics g, GeoLatLng pt) {
            GeoPoint ptOnScreen = map.fromLatLngToScreenPixel(pt);
            int x = (int) ptOnScreen.x;
            int y = (int) ptOnScreen.y;
            g.setColor(0x00FF00);
            g.fillRect(x - 4, y - 4, 8, 8);

        }

        private void drawCursor(IGraphics g) {
            int x = getScreenWidth() / 2;
            int y = getScreenHeight() / 2;
            g.setColor(0x205020);
            g.drawRect(x - 4, y - 4, 8, 8);
            g.drawLine(x, y - 6, x, y - 2);
            g.drawLine(x, y + 6, x, y + 2);
            g.drawLine(x - 6, y, x - 2, y);
            g.drawLine(x + 6, y, x + 2, y);
        }
    }
}



Map Services -- Set Map Service Type

Guidebee Map API default uses Google Map Service, but there’s way you can choose which service you want to use. For example, if you applied CloudMade map key and you want to use CloudMade map service for your application. Or if you are in China, you want to use MapAbc map service instead of Google Map API.
Remember when use map in China, there’s an “offset” for most maps (google map china, Bing map China, MapAbc Map) used in China. You need offset rectify data to adjust the location received from GPS device and then shown on these maps.  CloudMade Map China map doesn’t have such “offset”.
The following example show how you choose map services in your application. It uses three different map services to get direction between Nanjing to Tianjing in China.

public class MapRoutingMIDP extends MapDemoMIDP implements CommandListener,
        IRoutingListener {
    private Command mapGetDirectionCommand = new Command("Get Direction", Command.OK, 1);
    public void startApp() {
        init();
        map.setCurrentMapService(DigitalMapService. MAPABC_MAP_SERVICE);
        //map.setCurrentMapService(DigitalMapService.GOOGLE_MAP_SERVICE);
        //map.setCurrentMapService(DigitalMapService.CLOUDMADE_MAP_SERVICE);
        canvas.addCommand(mapGetDirectionCommand);
        map.setRoutingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
        map.setCenter(center, 15, MapType.MICROSOFTCHINA);
       Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapGetDirectionCommand) {
            GeoLatLng latLng1=new GeoLatLng(32.0418381,118.7788905);
            GeoLatLng latLng2=new GeoLatLng(39.11643,117.180908);
            map.getDirections(new GeoLatLng[]{latLng1,latLng2});

        }
    }

    public void done(String query, MapDirection result) {
        if (result != null) {
            map.setMapDirection(result);
            map.setZoom(13);
            map.panTo(new GeoLatLng(32.0418381,118.7788905));
        }
    }
}




The above diagram display the route returned by Google Map Service, MapAbc Map Service and CloudMade MapService. You can notice there’s “offset” for CloudMade sevice returned route.
This is because the Bing China Map itself is a map with offset, the returned result by CloudMade Map service is the actual coordinates. The google map china map service also returned the latitude, longitude with offset, that’s why they are “matched” perfectly.


Map Services -- Reverse Geocoding

Reverse Geocoding is to find a detail address name based on its latitude and longitude. The following example is to find what’s the address locate in -31.948275, 115.857562. which is "109-123 James St, Northbridge WA 6003, Australia"

public class MapReverseGeocodingMIDP extends MapDemoMIDP implements CommandListener,
        IReverseGeocodingListener {

    private Command mapFindAddressCommand = new Command("Find Address", Command.OK, 1);

    public void startApp() {

        init();
        canvas.addCommand(mapFindAddressCommand);
        map.setReverseGeocodingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(-31.948275, 115.857562);
        map.setCenter(center, 13, MapType.GOOGLEMAP);        Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapFindAddressCommand) {
            map.getReverseLocations("-31.948275,115.857562");
        }
    }

    public void done(String arg0, MapPoint[] result) {
        if (result != null) {
            map.panTo(result[0].getPoint());
        }
    }
}





Remember the string format, latitude comes first, and don’t put space in between. The result is also returned as an array. Normaly only the first result is most appropriate, the rest is for bigger area, i.e the city, the state and then the country the address is in.

Map Services -- IP Search

You can also search address based on an IP address, Guidebee Map API also provide methods to query latitude, longitude base on a IP address.
For example, the following example search for IP 58.192.32.1, and the return latitude, longitude is 118.777802, 32.061699, which is Nanjing University in China.
public class MapIpSearchMIDP extends MapDemoMIDP implements CommandListener,
        IIpAddressGeocodingListener {

    private Command mapFindAddressCommand = new Command("Find Address", Command.OK, 1);

    public void startApp() {

        init();
        canvas.addCommand(mapFindAddressCommand);
        map.setIpAddressGeocodingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
        map.setCenter(center, 15, MapType.MICROSOFTCHINA);
        Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapFindAddressCommand) {
            map.getIpLocations("58.192.32.1");
        }
    }

    public void done(String query, IpAddressLocation result) {
        if (result != null && result.error.length() == 0 && result.longitude.length() > 0
                && result.longitude.length() > 0) {
            try {

                MapPoint mapPoint = new MapPoint();
                String latLng = "[" + result.longitude + "," + result.latitude + ",0]";
                mapPoint.point = DigitalMap.fromStringToLatLng(latLng);
                mapPoint.setName(result.organization);
                mapPoint.setNote(result.city + " " + result.country);
                map.panTo(mapPoint.point);
            } catch (Exception e) {

                result.error = "IP_NOT_FOUND";
            }
        }
    }
}


Note: the result is always returned in English. A more detail information for above IP address is as below:
ISP"China Education and Research Network"
Organization: "Nan Jing University"
Country: "CN"
City: "Nanjing"
You can also use “127.0.0.1” to query local address location.

Map Services -- Local Search

You can use “Local search” to search business in given area, like search for café, restaurant etc.  It has similar usage as Geocoding.
The following example searches for café around -31.948275, 115.857562.

public class MapLocalSearchMIDP extends MapDemoMIDP implements CommandListener,
        IGeocodingListener {

    private Command mapFindAddressCommand = new Command("Find Address", Command.OK, 1);

    public void startApp() {

        init();
        canvas.addCommand(mapFindAddressCommand);
        map.setGeocodingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(-31.948275, 115.857562);
        map.setCenter(center, 13, MapType.GOOGLEMAP);
        Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapFindAddressCommand) {
            String name = " cafe";
            GeoLatLng screenCenter = map.getScreenCenter();
            map.getLocations(name, 0, screenCenter, map.getScreenBounds(screenCenter));
        }
    }

    public void done(String query, MapPoint[] result) {
        if (result != null) {
            map.panTo(result[0].getPoint());
            for (int i = 0; i < result.length; i++) {
                System.out.println(result[i].objectNote);
            }
        }
    }
}





Local search method:
public void getLocations(String address,int start,GeoLatLng center,GeoBounds bound, IGeocodingListener listener);
search based on the center and boundary. Local search can return mulptile results and can be called multiple time, start gives the start index of the result. you can specify the number of each return ,for google,each time is 4.
The above example’s output is as following (maybe different in your case).
Cafe Cafe (08) 9388 9800 (08) 9388 7800
The Moon Cafe (08) 9328 7474
tiger, tiger coffee bar (08) 9322 8055
Monte Fiore Cafe Restaurant (08) 9227 9898

Map Services -- Get Directions

RasterMap’s getDirections is used to get directions between two points or transit through a list of waypoints.  The result is also returned with a callback function.
The following example returns the route between “Perth” and “Sydney”.
The result is stored in an instance of MapDirection, which contains detail information like distance, driving instructions of each route, each steps.
public class MapRoutingMIDP extends MapDemoMIDP implements CommandListener,
        IRoutingListener {
   private Command mapGetDirectionCommand = new Command("Get Direction", Command.OK, 1);
    public void startApp() {
        init();
        canvas.addCommand(mapGetDirectionCommand);
        map.setRoutingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(-31.948275, 115.857562);
        map.setCenter(center, 13, MapType.GOOGLEMAP);
        Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapGetDirectionCommand) {
            String name1 = "perth";
            String name2 = "sydney";
            map.getDirections("from: " + name1 + " to: " + name2);

        }
    }

    public void done(String query, MapDirection result) {
        if (result != null) {
            map.setMapDirection(result);
            map.resize(result.getBound());
            map.zoomOut();
        }
    }
}




For map service, there are Google Map Service, CloudMade Map Servcie and in China, MapAbc Map service to choose from. The default map service used is Google map service.
getDirections() has three overloaded methods. The example above uses the description format. If use string as the query input, CloudMade and MapAbc requires the following format “longitude1, latitude1, longitude2, latitude2”.
To avoid confusion, you can use following
public void getDirection(GeoLatLng[] waypoints, IRoutingListener listener);
Where waypoints are the transit points for the route. It may support multiple way points (>2) depends which service you use


Map Services -- Geocoding

Guidebee Map API also provides methods to query address, get directions, local search, IP search, and search by latitude and longitude etc

Address query or Geocoding, is to find an address’s latitude and longitude according to its name (like James St, Perth).
The following examples show how to use the Geocoding services with ServerMap, it querys for 7 Fairway, Crawley, Australia. And then display the map in that area.

public class MapGeocodingMIDP extends MapDemoMIDP implements CommandListener,
        IGeocodingListener {

    private Command mapFindAddressCommand = new Command("Find Address", Command.OK, 1);

    public void startApp() {

        init();
        canvas.addCommand(mapFindAddressCommand);
        map.setGeocodingListener(this);
        canvas.setCommandListener(this);
        GeoLatLng center = new GeoLatLng(-31.948275, 115.857562);
        map.setCenter(center, 13, MapType.MICROSOFTMAP);        Display.getDisplay(this).setCurrent(canvas);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == mapFindAddressCommand) {
            String name = "7 Fairway, Crawley, Australia";            map.getLocations(name);       
}
    }

    public void done(String query, MapPoint[] result) {
        if (result != null) {
            map.panTo(result[0].getPoint());
        }
    }
}


All results returned by map service asynchronously.  
Before call RasterMap.getLocation (address), a callback (listener) can be set with RasterMap.setGeocodingListener; the callback will be called when service returns.
The listener methods is defined as
public void done(String query,MapPoint[] result).
If the search has return , the result is the array of all returned result. the examples move the map to the first address found.


Map Cache

As stated previously, RasterMap has an internal cache. If the map tile is in the cache already, RasterMap get the map tile from the cache directly, this will speed up map update.
The size of cache and turn on/turn off cache can be changed with class MapConfiguration.
Also remember the cache is tempory, the cache is cleaned after the application exits. If you need to store the cache persistently, so that next time user starts the application, you can display the map last time user visits. You can use saveMapCache and restoreMapCache methods to save and restore cache in a file.