deCarta Javascript Mobile API
deCarta confidential and proprietary.
Copyright 2006-2011 deCarta. All rights reserved.
Table of contents:
- Overview
- Getting Started
- Authentication
- A Note on
Function Scope
- Parameter
passing format
- Getting Started
- Map Overlays
- Using
deCarta DDS Web Services
- Advanced Topics
- Browser
/ device support table
Overview
The deCarta Mobile JavaScript API is a lightweight, full-featured,
highly configurable, high performance, HTML5 compliant framework for
building state of the art LBS applications for smart phone browsers
such as iPhone Safari, Samsung Bada, Android Browser, JIL, Opera Mobile and Opera
Widgets. The advanced rendering algorithms power a web based
user experience on par with any native user experience.
Getting started
The code for the API resides in this same folder. You need to include the API in
a script tag in the HEAD of each page while developing your application:
<!--debug version -->
<script src="deCartaMob.js"></script>
<!--release / compressed version -->
<script src="deCartaMob.min.js"></script>
Using this JavaScript based API requires including the following code snippet
in the BODY tag of the HTML page (with variable height and width):
<div id="map" style="width:800px; height:600px;"></div>
We recommend that you read through the examples in order.
You can save the source of the examples and use them as a starting point
for your application.
Authentication
The deCarta JavaScript 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.
You can set up your clientName and clientPassword by assigning the
appropriate values to the deCarta.Mobile.Configuration object.
Example:
deCarta.Mobile.Configuration.clientName = ""; // Input clientName between quotes
deCarta.Mobile.Configuration.clientPassword = ""; // Input clientPass between quotes
A
Note on Function Scope
Asynchronous JavaScript programming heavily relies on callback
functions. When they are called, these functions are within the local
scope of the object which received the response. This can be the cause
of several headaches. Take the following example.
var myObject = {
importantValue: 42, //Necessary To Decode Response
makeRequest: function(){
AJAX.getResponse("http://my.service.net/", this.receiveResponse);
},
receiveResponse: function(response){
//here we need to decode the response.
//however, our importantValue is not defined!!
alert(typeof this.importantValue); //says ‘undefined’
}
}
The reason why our importantValue is not defined is that
myObject.receiveResponse is called from within the AJAX object, so the
this keyword applies to AJAX, not myObject. This can cause many issues,
and there are several ways to work around the problem. The deCarta
Mobile API, which makes very heavy use of asynchronous calls, uses a
method called “binding” to ensure the scope of its callback functions
is correct. This method is common throughout many JavaScript libraries.
You can
read more about it here:
http://dhtmlkitchen.com/?category=/JavaScript/&date=2008/09/11/&entry=Function-prototype-bind
The deCarta Mobile API provides it’s own implementation of the bind()
function.
How do we use function binding? Here is our simple example, updated to
use function binding:
var myObject = {
importantValue: 42, //Necessary To Decode Response
makeRequest: function(){
AJAX.getResponse("http://my.service.net/", this.receiveResponse.bind(this));
},
receiveResponse: function(response){
//here we need to decode the response.
alert(this.importantValue); //says 42!
}
}
This is a very simple example of function binding. There is more that
can be done, for example by binding specific parameters to a function
call. Read more about it here:
http://www.prototypejs.org/api/function/bind
Parameter
Passing Format
Throughout the API you will notice that most (possibly all) methods
accept a single object parameter. This is a common technique in modern
JavaScript, as it presents several benefits over “normal” parameter
passing. For instance, you can omit unnecessary parameters without a
bunch of
ugly “null” values in your method call. It is easier to grow the
library without changing method signatures. Your arguments
can be named, and therefore passed in any order you like. Basic Map and
Controls
Getting
Started
Basic Map
The most basic use of the deCarta Mobile Api is to display a draggable
map within a <div> tag in your mobile-enabled web page.
The default options for the Map object constructor provide this
functionality, so it’s as easy as:
var map = new deCarta.Mobile.Map({ id: "mapContainer"});
The only option you are required to specify is the id of the DOM
element you want your map to be displayed in.
Map Controls
By default, Map objects are generated without controls. Map controls
can be added with a few lines of code each. Currently supported
controls are ZoomControl, OverviewControl, PanControl, and CopyrightControl.
There are two methods you can use to add controls to the map.
If you controls are static (i.e. they will be displayed at all times
while the user is viewing the map) you can simply include them in the
map constructor, as this example shows:
var map = new deCarta.Mobile.Map({
id: "mapContainer",
controls: [
new deCarta.Mobile.CopyrightControl({position:'bottomLeft'})
]});
In this example, we are passing in to the constructor a “controls”
array, which includes instances of the various controls we want to
display (the CopyrightControl in this case).
The second method is used to dynamically add controls to the map. This
can be useful if you wish to only display a control at certain times.
This example shows how to add a dynamic ZoomControl to the map:
var map = new deCarta.Mobile.Map({
id: "mapContainer",
controls: [
new deCarta.Mobile.CopyrightControl({position:'bottomLeft'})
]});
//later in the code ….
var locate = new deCarta.Mobile.ZoomControl({position:'topLeft'});
map.addControl(locate);
Resizing
the Map
The map can be dynamically simply by setting the new size of the
element containing it, and calling the resize method of the map object.
So it is as simple as :
document.getElementById(‘mapContainer’).style.height = ‘500px’;
document.getElementById(‘mapContainer’).style.width = ‘600px’;
map.resize();
Map Events
The deCarta Mobile API provides a mechanism to be alerted when specific
events happen within the map. The core of this system is the
EventManager object.
This object allows you to register callback functions that will be
executed when specific events are triggered within the map, or to
trigger notifications yourself for other components on the map to
consume (this can be useful for custom map controls, as we’ll see
later). For now, let’s see what we can use the EventManager for.
Currently the supported events are:
- zoomStart
- zoomChange
- zoomEnd
- moveStart
- move
- moveEnd
- click
- doubleClick
- resize
- showPinText
- hidePinText
- tileLoad
- longTouch
- doubleTap
For example, we might want to implement a specific behavior
if the user
“long touches” a certain position on the map. (A longTouch event is
triggered if the user keeps his/her finger pressed on the map for more
than 1 second).
To implement this, we will use the EventManager to install a listener
on the “longTouch” event.
deCarta.Mobile.EventManager.listen(‘longTouch’, function(eventParams){
/*implement the behavior you want here
eventParams is an object containing the following properties:
position: a Position object,with the Lat Lon for the touch
map: a reference to the Map object */
});
This is all that is required for you to be notified of specific events
on the map. Refer to the EventManager documentation for specific
information about
the available events and their parameters.
Other Map
Options
You can refer to the Map object’s documentation for a full list of
options that can be applied to a map. Some of the available
options include:
- easing (Boolean, default: true) : enables and disables the drag easing of the map
- digitalZoom (Boolean, default: true) : enables and disables the animated zooming effect
- doubleTapZoom (Boolean, default: true) : enables and disables the double tap/click to zoom
- limitFps (Boolean, default: true) : controls the rendering speed (frames per second)
- maxFps (int, default 30) : sets the max frames per second—go to uses on less smart phones
Map Overlays
Creating
Map Overlays
Map overlays are containers / buckets that you can use to add objects such as
Pins or Polylines to a map. Overlays are stacked on top of the map in
the order they are created, and can be turned on or off individually.
This allows you to add objects to the map and group them in sets that
you can display as needed.
Map overlays spatially index the content you add to ensure the fastest
possible rendering speed. The indexing algorithm allows for extremely
fast selection of the objects that require rendering for each map view.
Keep in mind that drawing a large number of objects on the screen will
always degrade performance, no matter how fast the search for those
objects is. The main function of the spatial indexing of the overlays
is to not waste time figuring out which objects are visible and which
are not. Depending on the device, the limit on the number of objects
that can be drawn on the screen at any given time varies greatly.
Overlays can be added to a Map directly in the constructor, or can be
dynamically added later as needed.
To add an overlay in the constructor, you need to pass it as an element
to the ‘layers’ array, as in this example:
var myOverlay = new deCarta.Mobile.MapOverlay({name: ‘My First Overlay’});
var map = new deCarta.Mobile.Map({
container: ‘mapContainer’,
layers: [myOverlay]
});
Notice that we retained a reference to the newly created MapOverlay
object. We will need this later if we decide to add Objects to this
overlay. To add the overlay dynamically, instead:
var myOverlay = new deCarta.Mobile.MapOverlay({name: ‘Dynamic Overlay’});
map.addOverlay(myOverlay);
//if we later decide to remove it …
Map.removeOverlay(myOverlay);
Adding Pins
One of the the most common elements you will add to an overlay is a Pin
object. A Pin is an object which is tied to a specific latitude and
longitude.
You have to option to display an image of your choice, or to use the
default Pin icon provided in the deCarta Mobile API distribution. A Pin
can also have descriptive text associated with it, which the user can
view by tapping it. Tapping the pin again will hide the text.
To create a Pin object, simply provide the constructor with the
necessary details:
var myPin = new deCarta.Mobile.Pin({
position: p, //a Position object
text: s, //a string (can contain HTML)
});
This will create a basic Pin, with all the default settings. Refer to
the documentation for the Pin object for a list of all available
options.
Once the Pin is created, it’s time to add it to the overlay. This is as
simple as:
myOverlay.addObject(myPin);
Yes, that’s all. If the overlay is visible, the pin is now on the map.
Adding
Polylines
Polylines are another common feature, since they are used to display
routes on a Map. The Polyline object is highly optimized, to allow you
to display
complex geometry even on the simplest device. It makes use of the
spatial indexing abilities of the Overlay object, together with a
geometry thinning algorithm, to provide excellent rendering speed on
all devices. The Polyline object also automatically selects different
rendering modes (canvas / SVG) depending on the device characteristics
completely transparent to the user.
Adding a polyline is just as simple as adding a pin:
var geometry = []; //here we assume “geometry” is an array of positions
var line = new deCarta.Mobile.Polyline({lineGeometry: geometry});
myOverlay.addObject(line);
A useful feature of Map Overlays is the ability to automatically "cluster" pins when they
are too close to be reliably clicked by the user.
When a MapOverlay has pin clustering enabled, pins that are too close together will be
merged into a single pin with a small notification icon indicating the number
of pins it contains. You can register a callback function with the MapOverlay that will
be invoked when the pin is clicked, allowing you to provide the user with a UI to further
refine their choice.
To enable pin clustering, initialize the MapOverlay with the 'clustering' option enabled:
var pinOverlay = new deCarta.Mobile.MapOverlay({
name: 'Clustered Pins',
clustering: true,
onClusterClick: function(pins){
//pins is an array containing all pins in the cluster
alert('You clicked a pin cluster with : ' + pins.length + ' pins');
}
});
Using
deCarta DDS Web Services
Geocoding
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.
The deCarta Mobile API provides a simple interface to make use of the
deCarta DDS Web Services’ geocoding functionality. All geocoding
operations are performed through the global static
Geocoder object. To geocode an address, you must first instantiate an
Address object.
Address objects come in two flavors: StructuredAddress or FreeFormAddress. Refer to
the API documentation for more detailed information on address types.
In this example we’ll work with a freeform address, and use the
Geocoder.geocode function to get information about the addresses
Latitude and Longitude. Since the Geocoder works with asynchronous
calls to the DDS Web Services, the results will be passed back to our
callback function for us to analyze.
var myAddress = new deCarta.Mobile.FreeFormAddress(
"4 N Second St., San Jose, CA",
new deCarta.Mobile.Locale('EN', 'US'’)
);
deCarta.Mobile.Geocoder.geocode(myAddress,function(addressResults){
//refer to the documentation for the format of addressResult
var latLon = addressResults[0].Point.pos.split(" ");
alert("Address Lat/Lon : " + latLon[0] + ", " + latLon[1]);
});
Reverse
Geocoding
As the name implies, Reverse Geocoding involves looking at a
latitude/longitude pair and returning the closes human readable
address. Again, we use the Geocoder object, but this time we will
invoke the
reverseGeocode function. Again, we will need to provide a callback
function that can interpret the results received form DDS WebServices.
var myPos = new deCarta.Mobile.Position(37.786505, -122.3862);
deCarta.Mobile.Geocoder.reverseGeocode(myPos, function(address){
if (address && address.toString() != ''){
alert('The address is : ' + address);
} else {
alert("Unable to reverse geocode that position");
}
});
Routing
The routing functionalities of DDS Web Services are exposed to the JS
Mobile API through the Routing object. The Routing object provides a
single method, “execute” which accepts as parameters a Criteria object
and a callback where it will return the results. The Criteria object
can contain several options, but in its most basic
form it has only one property: an array of Position objects that define
the waypoints of the route. Let’s see a very simple routing call:
var startPoint = new deCarta.Mobile.Position(37.786505, -122.3862);
var endpoint = new deCarta.Mobile.Position(37.5616, -122.3191);
var routeCriteria = {
waypoints: [startPoint, endpoint]
}
deCarta.Mobile.Routing.execute(routeCritera, function(routeResult){
//assuming we have an overlay on our map, add the route to it
var routeGeometry = routeResult.RouteGeometry.LineString.pos;
var routeLine = new deCarta.Mobile.Polyline({lineGeometry:routeGeometry})
myOverlay.addObject(routeLine);
});
POI Search
Another feature of DDS Web Services that is exposed through the Mobile
API is the ability to search for POIs, either by category or by free
form name matching.
Both search types rely on the POISearch object. Like the Routing
object, it has a single method, “execute” which accepts a search
parameters object and a callback function that will receive the
results. Refer to the API documentation for the full set of search
options.
An example of a freeform search query follows:
var searchCriteria = {
map: myMap,
properties: {
POIName: "pizza"
}
}
deCarta.Mobile.POISearch.execute(searchCriteria, function(poiResults){
for (var i = 0; i < resultSet.length; i++){
var poiInfo = resultSet[i].POI;
var pos = poiInfo.Point.pos.split(' ');
var position = new deCarta.Mobile.Position(pos[0], pos[1]);
var Pin = new deCarta.Mobile.Pin({
position: position,
text: poiInfo.POIName
});
}
});
An Example of category search:
/**
* common categories:
'Attractions'
'Bars & Clubs'
'Coffee'
'Cinema'
'Hotels'
'ATM'
'Museums'
'Gas stations'
'Transportation'
'Restaurant'
'Shopping'
*/
var searchCriteria = {
map: myMap,
properties: {
CATEGORY: "Coffee"
}
}
deCarta.Mobile.POISearch.execute(searchCriteria, function(poiResults){
for (var i = 0; i < resultSet.length; i++){
var poiInfo = resultSet[i].POI;
var pos = poiInfo.Point.pos.split(' ');
var position = new deCarta.Mobile.Position(pos[0], pos[1]);
var Pin = new deCarta.Mobile.Pin({
position: position,
text: poiInfo.POIName
});
}
});
Corridor
Search
Corridor search allows you to search for POIs along the route. There
are several options you can set to customize your search (distance from
route, type of distance calculation, and many more) but you can get
started with a very simple corridor search by adding a ‘route‘
parameter to the searchCriteria object. The value of this parameter is
a Route as returned by the Routing object.
Advanced
Topics
Image Packs
One of the goals of the deCarta Mobile API is to reduce to a minimum
the number of http requests performed by the application. In a mobile
environment, where bandwidth can be an issue (either for limited speed,
or for costs to the user) this is a very important aspect for any
application. The Mobile API, when minified, provides an extremely small
footprint.
However, you might need to load many external resources (pin images,
for example).
All the resources the API uses are packaged in a single file, which is
loaded together with the API. This reduces the number of http requests
to only one for the whole set of images.
Furthermore, modern mobile devices often have higher screen resolution.
The deCarta API distribution contains two separate image packs, a
high-resolution one and a low resolution one. The best pack for the
device is chosen at load time.
You can add your own images to the packs. This will benefit you in two
main ways:
- Your application will be able to load all its assets in one http request
- The right images for the device will be chosen for you by the API
To add the images to the packs, open the correct file in the resources
directory. This will be HiResImages.js for (you guessed it) high
resolution images, and StdResImages.js for the regular set of images.
You’ll see that the file contains a single object “imagePack”. Each
image asset is a property of this object. Each image itself is an
object with two properties:
- Img (required) : this is the actual image data, encoded in the Data:URI scheme.
- Scale (optional): if the image requires scaling to be properly displayed, set the scaling factor here
References:
Going to production
Here are a few tips that can help improve performance when you are ready to deploy to production:
Custom
Tile Layers
You can create custom tile layers for your map. To do so, you need to
create a new MapLayer object and a custom TileStore object. The
MapLayer is responsible for rendering a set of tiles on the map. It
gets the tile images from a TileStore object, which provides caching,
load management and other functionalities. You will have to extend the
base TileStore object, and override the
standard getTileUrl method (you can refer to one of the two TileStores
included in the API distribution, StreetTileStore or
SatelliteTileStore). Once the TileStore is created, you can create a
new MapLayer that uses
your new TileStore as source, and add it to the map using the
Map.addLayer method.
Custom
Map Controls
coming soon.
Browser
/ device support table
Currently
tested / supported devices
Full Support
- iOs Safari
- Android Browser (2.0 + )
- Samsung Wave
- Opera Widgets
- Opera Mobile
- JIL Wdgets
Partial
support
Android Browser (1.5 + )