If you have CARTO Enterprise account plans, you can connect to the CARTO Engine APIs via Mobile SDK, to retrieve map visualizations and table data from your CARTO account.
There are several methods of connecting map data from your CARTO account (via the Mobile SDK) to your mobile app; depending on the size of your data, the visual requirements, and other factors.
RasterTileLayer
TorqueTileLayer
to define this.CARTO Mobile SDK can take map offline via the CARTO platform:
VectorTileLayer
, from MBTilesTileDataSource
and CartoCSS.This method enables you to get both optimized vector tiles and suitable CartoCSS styling for your map.
For details, see the Offline Maps page.
CARTO Mobile SDK supports CARTO Maps API integration for Anonymous Maps and Named Maps. Anonymous maps allow you to instantiate a map given SQL and CartoCSS. Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name.
Use CartoMapsService
class to configure layers. Note that this must be done in a separate thread on Android, as Maps API requires connecting to server, which is not allowed in the main thread. The following snippet sets up the config (SQL and CartoCSS) we are going to use to define the map. As Anonymous map means that the map configuration - data and styles is created by client, then there are two phases.
1) defining map parameters first:
private String getConfigJson() {
// Define server config
JSONObject configJson = new JSONObject();
try {
// Change these according to your DB
String sql = "SELECT * FROM stations_1";
String statTag = "3c6f224a-c6ad-11e5-b17e-0e98b61680bf";
String[] columns = new String[] { "name", "status", "slot" };
String cartoCSS = "#stations_1{" +
"marker-fill-opacity:0.9;marker-line-color:#FFF;" +
"marker-line-width:2;marker-line-opacity:1;marker-placement:point;" +
"marker-type:ellipse;marker-width:10;marker-allow-overlap:true;}\n" +
"" +
"#stations_1[status = 'In Service']{marker-fill:#0F3B82;}\n" +
"#stations_1[status = 'Not In Service']{marker-fill:#aaaaaa;}\n" +
"#stations_1[field_9 = 200]{marker-width:80.0;}\n" +
"#stations_1[field_9 <= 49]{marker-width:25.0;}\n" +
"#stations_1[field_9 <= 38]{marker-width:22.8;}\n" +
"#stations_1[field_9 <= 34]{marker-width:20.6;}\n" +
"#stations_1[field_9 <= 29]{marker-width:18.3;}\n" +
"#stations_1[field_9 <= 25]{marker-width:16.1;}\n" +
"#stations_1[field_9 <= 20.5]{marker-width:13.9;}\n" +
"#stations_1[field_9 <= 16]{marker-width:11.7;}\n" +
"#stations_1[field_9 <= 12]{marker-width:9.4;}\n" +
"#stations_1[field_9 <= 8]{marker-width:7.2;}\n" +
"#stations_1[field_9 <= 4]{marker-width:5.0;}";
// You may not need to change much of these standards
configJson.put("version", "1.0.1");
configJson.put("stat_tag", statTag);
JSONArray layersArrayJson = new JSONArray();
JSONObject layersJson = new JSONObject();
layersJson.put("type", "cartodb");
JSONObject optionsJson = new JSONObject();
optionsJson.put("sql", sql);
optionsJson.put("cartocss", cartoCSS);
optionsJson.put("cartocss_version", "2.1.1");
JSONArray interactivityJson = new JSONArray();
interactivityJson.put("cartodb_id");
optionsJson.put("interactivity", interactivityJson);
JSONObject attributesJson = new JSONObject();
attributesJson.put("id", "cartodb_id");
JSONArray columnsJson = new JSONArray();
for (String col : columns) {
columnsJson.put(col);
}
attributesJson.put("columns", columnsJson);
optionsJson.put("attributes", attributesJson);
layersJson.put("options", optionsJson);
layersArrayJson.put(layersJson);
configJson.put("layers", layersArrayJson);
} catch (JSONException e) {
return null;
}
return configJson.toString();
}
static string CartoCSS
{
get
{
return "#stations_1{" +
"marker-fill-opacity:0.9;" +
"marker-line-color:#FFF;" +
"marker-line-width:2;" +
"marker-line-opacity:1;" +
"marker-placement:point;" +
"marker-type:ellipse;" +
"marker-width:10;" +
"marker-allow-overlap:true;" +
"}" +
"#stations_1[status = 'In Service']{marker-fill:#0F3B82;}" +
"#stations_1[status = 'Not In Service']{marker-fill:#aaaaaa;}" +
"#stations_1[field_9 = 200]{marker-width:80.0;}" +
"#stations_1[field_9 <= 49]{marker-width:25.0;}" +
"#stations_1[field_9 <= 38]{marker-width:22.8;}" +
"#stations_1[field_9 <= 34]{marker-width:20.6;}" +
"#stations_1[field_9 <= 29]{marker-width:18.3;}" +
"#stations_1[field_9 <= 25]{marker-width:16.1;}" +
"#stations_1[field_9 <= 20.5]{marker-width:13.9;}" +
"#stations_1[field_9 <= 16]{marker-width:11.7;}" +
"#stations_1[field_9 <= 12]{marker-width:9.4;}" +
"#stations_1[field_9 <= 8]{marker-width:7.2;}" +
"#stations_1[field_9 <= 4]{marker-width:5.0;}";
}
}
public static JsonValue VectorLayerConfigJson
{
get
{
JsonObject json = new JsonObject();
json.Add("version", "1.0.1");
json.Add("stat_tag", "3c6f224a-c6ad-11e5-b17e-0e98b61680bf");
JsonArray layerArray = new JsonArray();
JsonObject layerJson = new JsonObject();
layerJson.Add("type", "cartodb");
JsonObject optionJson = new JsonObject();
optionJson.Add("sql", "SELECT * FROM stations_1");
optionJson.Add("cartocss", CartoCSS);
optionJson.Add("cartocss_version", "2.1.1");
JsonArray interactivityJson = new JsonArray();
interactivityJson.Add("cartodb_id");
optionJson.Add("interactivity", interactivityJson);
JsonObject attributesJson = new JsonObject();
attributesJson.Add("id", "cartodb_id");
JsonArray columnJson = new JsonArray { "name", "field_9", "slot" };
attributesJson.Add("columns", columnJson);
optionJson.Add("attributes", attributesJson);
layerJson.Add("options", optionJson);
layerArray.Add(layerJson);
json.Add("layers", layerArray);
return json;
}
}
- (NSString*) getConfig
{
NSDictionary* options = @{
@"sql": @"SELECT FROM stations_1",
@"cartocss": [self getCartoCSS],
@"cartocss_version": @"2.1.1",
@"interactivity": [self getInteractivityJson],
@"attributes": [self getAttributesJson]
};
NSMutableArray* layerArray = [[NSMutableArray alloc] init];
NSDictionary* layerJson = @{ @"options": options, @"type": @"cartodb" };
[layerArray addObject:layerJson];
NSDictionary* json = @{
@"version": @"1.0.1",
@"stat_tag": @"3c6f224a-c6ad-11e5-b17e-0e98b61680bf",
@"layers": layerArray
};
NSError* error;
NSData* data = [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
-(NSString*) getCartoCSS
{
NSString* base = @"#stations_1 { marker-fill-opacity:0.9; marker-line-color:#FFF; marker-line-width:2; marker-line-opacity:1; marker-placement:point; marker-type:ellipse; marker-width:10; marker-allow-overlap:true; }";
NSString* attributes = @"#stations_1[status = 'In Service'] { marker-fill:#0F3B82; } #stations_1[status = 'Not In Service'] { marker-fill:#aaaaaa; } #stations_1[field_9 = 200] { marker-width:80.0; } #stations_1[field_9 <= 49] { marker-width:25.0; } #stations_1[field_9 <= 38] { marker-width:22.8; } #stations_1[field_9 <= 34] { marker-width:20.6; } #stations_1[field_9 <= 29] { marker-width:18.3; } #stations_1[field_9 <= 25] { marker-width:16.1; } #stations_1[field_9 <= 20.5] { marker-width:13.9; } #stations_1[field_9 <= 16] { marker-width:11.7; } #stations_1[field_9 <= 12] { marker-width:9.4; } #stations_1[field_9 <= 8] { marker-width:7.2; } #stations_1[field_9 <= 4] { marker-width:5.0; } ";
return [base stringByAppendingString:attributes];
}
-(NSMutableArray*) getInteractivityJson
{
NSMutableArray* array = [[NSMutableArray alloc] init];
[array addObject:@"cartodb_id"];
return array;
}
-(NSDictionary*) getAttributesJson
{
NSMutableArray* columns = [[NSMutableArray alloc] init];
[columns addObject:@"name"];
[columns addObject:@"field_9"];
[columns addObject:@"slot"];
return @{ @"id": @"cartodb_id", @"columns": columns };
}
public func getConfig() -> String? {
let options = NSDictionary(dictionaryLiteral:
("sql","SELECT * FROM stations_1"),
("cartocss", getCartoCSS()),
("cartocss_version", "2.1.1"),
("interactivity", getInteractivityJson()),
("attributes", getAttributesJson())
)
let layerArray = NSMutableArray()
let layerJson = NSDictionary(dictionaryLiteral: ("options", options), ("type", "cartodb"))
layerArray.add(layerJson)
let json = NSDictionary(dictionaryLiteral:
("version", "1.0.1"),
("stat_tag", "3c6f224a-c6ad-11e5-b17e-0e98b61680bf"),
("layers", layerArray)
);
if let theJSONData = try? JSONSerialization.data(withJSONObject: json, options: []) {
return String(data: theJSONData, encoding: .ascii)
}
return nil
}
public func getCartoCSS() -> String {
let base = "#stations_1 { marker-fill-opacity:0.9; marker-line-color:#FFF; marker-line-width:2; marker-line-opacity:1; marker-placement:point; marker-type:ellipse; marker-width:10; marker-allow-overlap:true; }"
let attributes = "#stations_1[status = 'In Service'] { marker-fill:#0F3B82; } #stations_1[status = 'Not In Service'] { marker-fill:#aaaaaa; } #stations_1[field_9 = 200] { marker-width:80.0; } #stations_1[field_9 <= 49] { marker-width:25.0; } #stations_1[field_9 <= 38] { marker-width:22.8; } #stations_1[field_9 <= 34] { marker-width:20.6; } #stations_1[field_9 <= 29] { marker-width:18.3; } #stations_1[field_9 <= 25] { marker-width:16.1; } #stations_1[field_9 <= 20.5] { marker-width:13.9; } #stations_1[field_9 <= 16] { marker-width:11.7; } #stations_1[field_9 <= 12] { marker-width:9.4; } #stations_1[field_9 <= 8] { marker-width:7.2; } #stations_1[field_9 <= 4] { marker-width:5.0; } "
return base + attributes;
}
public func getInteractivityJson() -> NSMutableArray {
return NSMutableArray(array: ["cartodb_id"])
}
public func getAttributesJson() -> NSDictionary {
let columns = NSMutableArray(array: ["name", "field_9", "slot"])
return NSDictionary(dictionaryLiteral: ("id", "cartodb_id"), ("columns", columns))
}
fun getConfigJson(): String? {
// Define server config
val configJson = JSONObject()
try {
// Change these according to your DB
val sql = "SELECT * FROM stations_1"
val statTag = "3c6f224a-c6ad-11e5-b17e-0e98b61680bf"
val columns = arrayOf("name", "status", "slot")
val cartoCSS = "#stations_1{" +
"marker-fill-opacity:0.9;marker-line-color:#FFF;" +
"marker-line-width:2;marker-line-opacity:1;marker-placement:point;" +
"marker-type:ellipse;marker-width:10;marker-allow-overlap:true;}\n" +
"" +
"#stations_1[status = 'In Service']{marker-fill:#0F3B82;}\n" +
"#stations_1[status = 'Not In Service']{marker-fill:#aaaaaa;}\n" +
"#stations_1[field_9 = 200]{marker-width:80.0;}\n" +
"#stations_1[field_9 <= 49]{marker-width:25.0;}\n" +
"#stations_1[field_9 <= 38]{marker-width:22.8;}\n" +
"#stations_1[field_9 <= 34]{marker-width:20.6;}\n" +
"#stations_1[field_9 <= 29]{marker-width:18.3;}\n" +
"#stations_1[field_9 <= 25]{marker-width:16.1;}\n" +
"#stations_1[field_9 <= 20.5]{marker-width:13.9;}\n" +
"#stations_1[field_9 <= 16]{marker-width:11.7;}\n" +
"#stations_1[field_9 <= 12]{marker-width:9.4;}\n" +
"#stations_1[field_9 <= 8]{marker-width:7.2;}\n" +
"#stations_1[field_9 <= 4]{marker-width:5.0;}";
// You may not need to change much of these standards
configJson.put("version", "1.0.1")
configJson.put("stat_tag", statTag)
val layersArrayJson = JSONArray()
val layersJson = JSONObject()
layersJson.put("type", "cartodb")
val optionsJson = JSONObject()
optionsJson.put("sql", sql)
optionsJson.put("cartocss", cartoCSS)
optionsJson.put("cartocss_version", "2.1.1")
val interactivityJson = JSONArray()
interactivityJson.put("cartodb_id")
optionsJson.put("interactivity", interactivityJson)
val attributesJson = JSONObject()
attributesJson.put("id", "cartodb_id")
val columnsJson = JSONArray()
for (i in 0..columns.size - 1) {
columnsJson.put(columns[i])
}
attributesJson.put("columns", columnsJson)
optionsJson.put("attributes", attributesJson)
layersJson.put("options", optionsJson)
layersArrayJson.put(layersJson)
configJson.put("layers", layersArrayJson)
} catch (e: JSONException) {
return null
}
return configJson.toString()
}
2) Now that the config is set up, you initiate the map and create Layer to be added to the MapView. This snippet inits vector tiles, but you can change the default vector layer mode to false if you want to get a raster tiles.
final String config = getConfigJson();
// Use the Maps service to configure layers.
// Note that this must be done in a separate thread on Android,
// as Maps API requires connecting to server, which is not allowed in main thread.
Thread serviceThread = new Thread(new Runnable() {
@Override
public void run() {
CartoMapsService mapsService = new CartoMapsService();
mapsService.setUsername("nutiteq");
mapsService.setDefaultVectorLayerMode(true); // use vector layers
try {
LayerVector layers = mapsService.buildMap(Variant.fromString(config));
mapView.getLayers().addAll(layers);
}
catch (IOException e) {
Log.e("EXCEPTION", "Exception: " + e);
}
}
});
serviceThread.start();
JsonValue config = JsonUtils.VectorLayerConfigJson;
MapView.ConfigureAnonymousVectorLayers(config);
// Extension method in static class
public static void ConfigureAnonymousVectorLayers(this MapView map, JsonValue config)
{
// Use the Maps service to configure layers.
// Note that this must be done in a separate thread on Android,
// as Maps API requires connecting to server, which cannot be done in the main thread.
System.Threading.Tasks.Task.Run(delegate
{
CartoMapsService service = new CartoMapsService();
service.Username = "nutiteq";
service.DefaultVectorLayerMode = true; // Use VectorLayers
LayerVector layers = service.BuildMap(Variant.FromString(config.ToString()));
map.Layers.AddAll(layers);
});
}
NTCartoMapsService* mapsService = [[NTCartoMapsService alloc] init];
[mapsService setUsername:@"nutiteq"];
// Use vector layers, not raster layers
[mapsService setDefaultVectorLayerMode:TRUE];
[mapsService setInteractive:TRUE];
NTVariant* variant = [NTVariant fromString:[self getConfig]];
NTLayerVector* layers = [mapsService buildMap:variant];
[[self.mapView getLayers] addAll:layers];
let config = getConfig()
// This calculation should be in background thread
DispatchQueue.global(qos: .userInitiated).async {
let mapsService = NTCartoMapsService()
mapsService?.setUsername("nutiteq")
mapsService?.setDefaultVectorLayerMode(true) // use vector layers
let layers = mapsService?.buildMap(NTVariant.fromString(config))
self.mapView?.getLayers()?.addAll(layers)
}
val config = getConfigJson()
// Use the Maps service to configure layers.
// Note that this must be done in a separate thread on Android,
// as Maps API requires connecting to server, which is not allowed in main thread.
// Remember: Put your operations back on the main thread to change the UI
// Note:
// doAsync requires anko coroutines dependency
// compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
doAsync {
val mapsService = CartoMapsService()
mapsService.username = "nutiteq"
mapsService.isDefaultVectorLayerMode = true // use vector layers
try {
val layers = mapsService.buildMap(Variant.fromString(config))
mapView?.layers?.addAll(layers)
} catch (e: IOException) {
println("Exception: " + e.message)
}
}
If you have created Named map using CARTO Maps API then map is already configured in the server, and map initiation is simpler:
final CartoMapsService service = new CartoMapsService();
// Use vector layers
service.setDefaultVectorLayerMode(true);
service.setUsername("nutiteq");
final String name = "tpl_69f3eebe_33b6_11e6_8634_0e5db1731f59";
// Be sure to make network queries on another thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
LayerVector layers = service.buildNamedMap(name, new StringVariantMap());
mapView.getLayers().addAll(layers);
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
MapView.ConfigureNamedVectorLayers("tpl_69f3eebe_33b6_11e6_8634_0e5db1731f59");
// Extension method
public static void ConfigureNamedVectorLayers(this MapView map, string name)
{
System.Threading.Tasks.Task.Run(delegate
{
CartoMapsService service = new CartoMapsService();
service.Username = "nutiteq";
// Use VectorLayers
service.DefaultVectorLayerMode = true;
LayerVector layers = service.BuildNamedMap(name, new StringVariantMap());
map.Layers.AddAll(layers);
});
}
NTCartoMapsService* mapsService = [[NTCartoMapsService alloc] init];
[mapsService setUsername:@"nutiteq"];
// Use vector layers
[mapsService setDefaultVectorLayerMode:YES];
NTLayerVector* layers = [mapsService buildNamedMap:@"tpl_69f3eebe_33b6_11e6_8634_0e5db1731f59" templateParams: [[NTStringVariantMap alloc] init]];
[[self.mapView getLayers] addAll:layers];
let service = NTCartoMapsService()
// Use vector layers
service?.setDefaultVectorLayerMode(true)
service?.setUsername("nutiteq")
let name = "tpl_69f3eebe_33b6_11e6_8634_0e5db1731f59"
DispatchQueue.global(qos: .userInitiated).async {
let layers = service?.buildNamedMap(name, templateParams: NTStringVariantMap())
self.mapView?.getLayers()?.addAll(layers)
}
val service = CartoMapsService()
// Use vector layers
service.isDefaultVectorLayerMode = true
service.username = "nutiteq"
val name = "tpl_69f3eebe_33b6_11e6_8634_0e5db1731f59"
// Be sure to make network queries on another thread
// Remember: Put your operations back on the main thread to change the UI
// Note:
// doAsync requires anko coroutines dependency
// compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
doAsync {
try {
val layers = service.buildNamedMap(name, StringVariantMap())
mapView?.layers?.addAll(layers)
} catch (e: IOException) {
e.printStackTrace()
}
}
CARTO’s SQL API allows you to interact with your tables and data inside CARTO, as if you were running SQL statements against a normal database. In general you can use the SQL API to insert, update or delete data (i.e., insert a new column with a latitude and longitude data) or to select data from public tables in order to use it on your website or application (i.e., display the 10 closest records to a particular location).
Note: In mobile SDK you can only SELECT data from public tables without api_key for higher security. Private tables and using api_key is not allowed from mobile directly. If you need these, you need to proxy CARTO SQL API to a custom API towards your app, so api_key requests are done from server to server
static final String query = "SELECT * FROM cities15000 WHERE population > 100000";
final CartoSQLService service = new CartoSQLService();
service.setUsername("nutiteq");
// Be sure to make network queries on another thread
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
features = service.queryFeatures(query, baseProjection);
for (int i = 0; i < features.getFeatureCount(); i++) {
// This data set features point geometry,
// however, it can also be LineGeometry or PolygonGeometry
PointGeometry geometry = (PointGeometry)features.getFeature(i).getGeometry();
source.add(new Point(geometry, getPointStyle()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
const string query = "SELECT * FROM cities15000 WHERE population > 100000";
CartoSQLService service = new CartoSQLService();
service.Username = "nutiteq";
PointStyleBuilder builder = new PointStyleBuilder
{
Color = new Carto.Graphics.Color(255, 0, 0, 255),
Size = 1
};
MapView.QueryFeatures(service, source, builder.BuildStyle(), query);
// Extension Method in a static class (shared code)
public static void QueryFeatures(this MapView map, CartoSQLService service, LocalVectorDataSource source, PointStyle style, string query)
{
System.Threading.Tasks.Task.Run(delegate
{
FeatureCollection features = service.QueryFeatures(query, map.Options.BaseProjection);
for (int i = 0; i < features.FeatureCount; i++)
{
Feature feature = features.GetFeature(i);
// This data set features point geometry,
// however, it can also be LineGeometry or PolygonGeometry
PointGeometry geometry = (PointGeometry)feature.Geometry;
var point = new Point(geometry, style);
source.Add(point);
}
});
}
// Only get cities with over 100k, or else it will be too many results
NSString* sql = @"SELECT * FROM cities15000 WHERE population > 100000";
// Initialize CartoSQL service, set a username
NTCartoSQLService* service = [[NTCartoSQLService alloc] init];
[service setUsername:@"nutiteq"];
NTPointStyleBuilder* builder = [[NTPointStyleBuilder alloc] init];
NTColor* color = [[NTColor alloc] initWithR:255 g:0 b:0 a:255];
[builder setColor:color];
[builder setSize:1];
NTPointStyle* style = [builder buildStyle];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
// Set on background thread for "animated" point appear
dispatch_async(queue, ^{
NTFeatureCollection *features = [service queryFeatures:sql proj:self.projection];
for (int i = 0; i < [features getFeatureCount]; i++) {
// This data set features point geometry,
// however, it can also be LineGeometry or PolygonGeometry
NTPointGeometry* geometry = (NTPointGeometry *)[[features getFeature:i] getGeometry];
NTPoint* point = [[NTPoint alloc] initWithGeometry:geometry style:style];
[self.source add:point];
}
});
let query = "SELECT * FROM cities15000 WHERE population > 100000"
let service = NTCartoSQLService()
service?.setUsername("nutiteq")
let builder = NTPointStyleBuilder()
builder?.setSize(1.0)
builder?.setColor(NTColor(r: 255, g: 0, b: 0, a: 255))
let style = builder?.buildStyle()
let source = NTLocalVectorDataSource(projection: projection);
// Networking should be placed on a background thread
// Remember: Put your operations back on the main thread to change the UI
DispatchQueue.global(qos: .userInitiated).async {
let features = service?.queryFeatures(query, proj: projection)
let count = Int((features?.getFeatureCount())!)
for i in 0..<count {
// This data set features point geometry,
// however, it can also be LineGeometry or PolygonGeometry
let geometry = features?.getFeature(Int32(i)).getGeometry() as! NTPointGeometry
source?.add(NTPoint (geometry: geometry, style: style))
}
}
val query = "SELECT * FROM cities15000 WHERE population > 100000"
val service = CartoSQLService()
service.username = "nutiteq"
val builder = PointStyleBuilder()
builder.size = 1F
builder.color = Color(255, 0, 0, 255)
val style = builder.buildStyle()
// Remember: Put your operations back on the main thread to change the UI
// Note:
// doAsync requires anko coroutines dependency
// compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
doAsync {
try {
val features = service.queryFeatures(query, projection)
for (i in 0..features.featureCount - 1) {
// This data set features point geometry,
// however, it can also be LineGeometry or PolygonGeometry
val geometry = features.getFeature(i).geometry as PointGeometry
source?.add(Point (geometry, style))
}
} catch (e: IOException) {
}
}