Overview

Welcome to the first Beta release of the deCarta Mobile API!

The deCarta Mobile API provides a framework for rapid development of mobile J2ME mapping applications. This framework uses Object Oriented J2ME to provide modular fluid maps, panning, zooming, geocoding, reverse geocoding, routing and pin overlays.

We ask that you participate in the forum on the deCarta Developer Zone (developer.decarta.com) to report bugs and share ideas. Use the links on the left frame of this page to see javadocs for the API.

Since mobile development requires a lot of fine tuning for different phones, we have decided to release the full source code of the API. You can report bugs and contribute bug fixes on the developers forum on this site. We are hoping that the development of this API becomes a community process. The focus of our immediate future development at deCarta will be improving performance.

Authentication

The deCarta Mobile API requires authentication with the clientName and clientPassword you received when registering on the deCarta Developer Zone. These are distinct from the email address and password that you use to login to the deCarta Developer Zone. The examples below check the included CONFIG.java file for your authentication information.
	public static String clientName="";
	public static String clientPassword="";
This code will not run unless you have these fields configured with valid credentials.

Getting Started

We recommend that you get the mobility kit for your favorite IDE. Both Eclipse and Netbeans have great plugins for mobile development which include emulators for testing the code. You should get the emulator for your target platform and begin development and testing. The source for the API comes with a basic demo midlet. We developed the API on the Nokia N95 as the development platform.

Code Examples

All of the following code snippets are included with the basic demo midlet app included with the API source.

Map Canvas

The core of the API is the Map class which extends javax.microedition.lcdui.Canvas. In order to customize the Map you will probably want to extend this class or at least override the commandListeners. Because the centerOnPosition method makes a network connection you will embedd this code into a new Thread.
	Map map=new ExtendedMap();
	Position pos = new Position("37.78192 -122.40381");
	map.setZoomLevel(17);
	map.centerOnPosition(pos);
	midlet.display.setCurrent(map);

Zooming Map

Because the zoom operations method makes a network connection you will embed this code into a new Thread. You can zoom to new levels (allowed values are 1-21 where 1 is the whole world on one tile and 21 is the lowest street level). Or you can use the convienence methods:

map.zoomIn(); // zooms in one level
map.zoomOut(); // zooms out one level
	Thread t = new Thread((new Runnable() {
		public void run() {
			try {
				map.zoomTo(newLevel); // call in new thread
			} catch (Exception e) {
				//handle 
			}
		}}));
	t.start();

Geocoding

For many reasons, mainly performance, the underlying geospatial engine requires information in latitude and longitude coordinates. But, human beings don't think in terms of latitude and longitude. You've most likely never heard someone say, "Hey Bob, meet me at 37.1232, -121.43566 for a beer at 5 o'clock." Instead, human beings reference locations by address. Geocoding is the act of taking an address (example: "4 N Second St., San Jose, CA 95113") and translating it into a pair of coordinates on a map.

Many times a user will put in an incomplete address, misspell a street name, or might only have a partial address available to input. Example: The distance between 2000 N. Main St., Anytowne USA and 2000 S. Main St., Anytowne USA might be miles; providing an address of 2000 Main St., Anytowne USA, without the directional before the street name, has a chance of inconveniently misplacing a user. As you can see, to assume your users will impeccably enter addresses is not wise.

To provide effective address translation, use the Geocoder object. The Geocoder object has a method geocode that takes a FreeFormAddress and returns a Vector of GeocodeResponse. By using the Geocoder object, you'll be able to let the user choose the best match for vague inputs.

	Thread t = new Thread((new Runnable() {
		public void run() {
			try {
				Geocoder geo = new Geocoder();
				resp = geo.geocode(new FreeFormAddress("4 N 2nd Street 95113"));
				if (resp.isEmpty()) { // unable to geocodeCmd
					// report to user
				} else if (resp.size() > 0) {  // present choices
					for (int i = 0; i < resp.size(); i++) {
						// present geocode response candidates to user
					}
				}
			} catch (Exception e) {
				// handle
			}
		}
	}));
	t.start();

You can also make a geocoding request using StructuredAddress instead of FreeFormAddress, we will internally transform the correct request to the server. And you can indicate the returning Address Type to FreeFormAddress for geocoding, the default returning type is StructuredAddress.
	Thread t = new Thread(new Runnable() {
		public void run() {
			try {
				Geocoder geo = new Geocoder();
				geo.setReturnFreeFormAddress(true); //set the returning type for geocode
				StructuredAddress structuredAddress = new StructuredAddress();
								
				structuredAddress.setBuildingNumber("1");
				structuredAddress.setStreet("Post Street");
				structuredAddress.setMunicipality("san jose");
				structuredAddress.setCountrySubdivision("ca");
				structuredAddress.setPostalCode("95113");
				
				resp = geo.geocode(structuredAddress); //geocode with StructuredAddress
				if (resp.isEmpty()) { // unable to geocodeCmd
					// report to user
				} else if (resp.size() > 0) {  // present choices
					for (int i = 0; i < resp.size(); i++) {
						// present geocode response candidates to user
						//the returning type now is FreeFormAddress
						FreeFormAddress address = (FreeFormAddress)((GeocodeResponse) resp.elementAt(i)).getAddress(); 
						

					}
				}
			} catch (Exception e) {
				
			}
	}
	});
	t.start();

Reverse Geocoding

There may be times when you need to translate a coordinate (example: 37.786505, -122.3862) into a human understandable street address. Most often this is needed in tracking applications where you receive a GPS feed from the device or asset and wish to know what address the point is located.

The Geocoder object has a reverseGeocode method. reverseGeocode takes a Position type (instead of an address) and returns a StructuredAddress type. This example alerts the returned address. The address found will be an approximate, interpolated address. It will throw an Exception if the position cannot be pinned to an address.

	Thread t = new Thread((new Runnable() {
		public void run() {
		try {
			Geocoder geo = new Geocoder();
			StructuredAddress resp = geo.reverseGeocode(getCenterPosition());
			Alert alert  = new Alert("reverse geocoding");
			alert.setTimeout(50000);
			alert.setString("You are located at:\n"+getCenterPosition().toString()+
				"\nwhich is:\n"+resp.toString());
			alert.addCommand(new Command("CANCEL", Command.CANCEL, 2));
			app.display.setCurrent(alert);
		}catch(Exception e){
			// handle
		}
		}}));
	t.start();

Local Search / POI Requests

The POIQuery object can be used to search for POIs (Points of Interest) that are in the base map data. All base map data contains a relatively diverse set of POIs. The types of POIs that can be retrieved from the data are outside of the scope of this document. For a detailed list of the types of POIs that can be retrieved, please contact your data provider.

The POIQuery takes a POISearchCriteria object and returns a Vector of found POI objects.

This example queries the base data for "Starbucks". POI searches also match substrings. A search for "Star" would find "Starbucks", "Star Donuts", "Nightstar Restaurant", and any other POI containing "Star" in its name.

NOTE: our system also supports spatial searching side relational databases. This is useful for friend finder apps as well as custom business directories. We will be providing demos for this in the future.

	Thread t = new Thread((new Runnable() {
		public void run() {
			try {
				POISearchCriteria crit = new POISearchCriteria(map.getCenterPosition(), // center
					map.getRadiusY(), // radius for search 
					"Starbucks"); // search term
				// execute search	
				Vector pois = POIQuery.query(crit);
				for (int i = 0; i < pois.size(); i++) {
					// create new Pin
					Pin pin = new Pin(((POI) pois.elementAt(i)).getPosition());
					// give Pin a message
					pin.setMessage(((POI) pois.elementAt(i)).getName());
					// add Pin to Map
					map.addPin(pin);
				}
			} catch (Exception e) {
				// handle
			}
		}}));
	t.start();

Routing

Routing is crucial to all LBS applications. There are two main aspects to a route. The visualization of the route and the turn by turn route instructions.

For visualizing the route you need set the route preference to return route geometry. The route response will include a Vector of Positions which can be used in conjunction with the Shape API's for rendering the route on screen. In order to keep things fast we have added route generalization features for minimizing the number of Positions returned at each zoom level. The steps are the following: For including the route instructions use:

	Thread t = new Thread((new Runnable() {
		public void run() {
			try {
	
				Position origin=getCenterPosition();
				Position dest = userSuppliedPosition; // assumed
				RouteQuery rq = new RouteQuery();
	
				// make bbox of the origin and dest then get the zoom level for the bbox for generalizing
				int zoomToFitRoute = Util.getZoomLevelToFitBoundingBox(getWidth(), 
					getHeight(), 
					Util.getBoundingBox(origin, dest));
	
				Vector poss = new Vector();
				poss.addElement(origin); // route origin (center of map)
				poss.addElement(routeDestPos); // destintation
				RoutePreference pref = new RoutePreference();
				pref.setReturnGeometry(true);
				pref.setReturnRouteId(false); //return geometry points instead of routeid
				pref.setReturnInstructions(false);
				pref.setGeneralizationLevel(zoomToFitRoute);// important for generalizing
				pref.setStyle("Fastest");
				Route route = rq.query(poss, pref);
	
				// recalculate the exact zoom from the BBox the server returned
				int zoomToFitRoute2 = Util.getZoomLevelToFitBoundingBox(getWidth(), 
					getHeight(), 
					route.getBoundingBox());
				map.setZoomLevel(zoomToFitRoute2);
	
				Line line = new Line();
				line.setWidth(5);
				line.setRGB(200, 0, 200);
	
				line.setPositionGeometry(route.getRouteGeometry());
	
				map.removeOverlays();
				
				// get center of the route to correctly frame screen
				map.centerOnPosition(route.getBoundingBox().getCenterPosition());
				app.display.setCurrent(map);
	
				Pin originPin = new Pin(origin);
				Pin destPin = new Pin(dest);
				originPin.setIcon(new Icon(Image.createImage("/greenDot.png"), new XY(6,6)));
				destPin.setIcon(new Icon(Image.createImage("/redDot.png"), new XY(6,6)));
	
				map.addPin(originPin);
				map.addPin(destPin);
				map.addOverlay(line);
				addCommand(new Command(REMOVE_ROUTE, Command.SCREEN, 2));
	
			} catch (Exception e) {
				// handle
			}
		}
	}));
	t.start();

You can also indicate the server render route for you. You need to indicate the server return routeid first and then you need to add routeid onto the map. as below:

	Thread t = new Thread((new Runnable() {
		public void run() {
			try {
				Position origin=getCenterPosition();
				Position dest = routeDestPos.clone();
						
				RouteQuery rq = new RouteQuery();

				// make bbox of the origin and dest then get the zoom level for the bbox for generalizing
				int zoomToFitRoute = Util.getZoomLevelToFitBoundingBox(getWidth(), getHeight(), Util.getBoundingBox(origin, dest));

				Vector poss = new Vector();
				poss.addElement(origin); // route origin (center of map)
				poss.addElement(routeDestPos); // destintation
						
				RoutePreference pref = new RoutePreference();
				pref.setReturnGeometry(true);
				pref.setReturnRouteId(true);
				pref.setReturnInstructions(false);
				pref.setGeneralizationLevel(zoomToFitRoute);// important for generalizing
				pref.setStyle("Fastest");
						
				Route route = rq.query(poss, pref);

				// recalculate the exact zoom from the BBox the server returned
				int zoomToFitRoute2 = Util.getZoomLevelToFitBoundingBox(getWidth(), getHeight(), route.getBoundingBox());
				map.setZoomLevel(zoomToFitRoute2);

				map.removeOverlays();
				map.removePins();
				map.addRouteId(route.getRouteId()); // add the routeid onto the map
				map.centerOnPosition(route.getBoundingBox().getCenterPosition());
						
				app.display.setCurrent(map);

				Pin originPin = new Pin(origin);
				Pin destPin = new Pin(dest);
				originPin.setIcon(new Icon(Image.createImage("/greenDot.png"), new XY(6,6)));
				destPin.setIcon(new Icon(Image.createImage("/redDot.png"), new XY(6,6)));

				map.addPin(originPin);
				map.addPin(destPin);
					
				addCommand(new Command(REMOVE_ROUTE, Command.SCREEN, 2));

			} catch (Exception e) {
				// handle
			}
		}
	}));
	t.start();
	

GPS Location

We have added a few methods for using the device GPS. Location positioning is perhaps the most important and most device specific aspect of mobile LBS development. We will be rolling out several new approaches to positioning, including wifi and cell tower positioning.
	
	Thread t = new Thread((new Runnable() {
		public void run() {
			Alert alert = new Alert("locating GPS");
			try {
				alert.setTimeout(120000);
				alert.addCommand(new Command("CANCEL", Command.CANCEL, 2));
				alert.setImage(Image.createImage("/satellite.gif"));
				app.display.setCurrent(alert);
				alert.setString("looking for satellites...");
				Position gps = Locate.getCurrentLocation();
				map.centerOnPosition(gps);
			} catch (Exception e) {
				alert.setString(e.getMessage());
			} finally {
				app.display.setCurrent(map);
			}
		}}));
	t.start();