Mobile SDK allows you to create mobile apps over the CARTO platform.

Map View

CARTO SDK MapView is the central map object to be added to your application screen - Storyboard or Layout. Technically it is platform-specific, so some details could be different on different platforms, but in general it provides same features cross-platform.

Following figure provides summary of MapView most used properties, methods and relationships with other SDK classes:

SDK overview

MapView manipulation

First step with MapView is always RegisterLicense, this is needed to connect to our map servers. Typical next step is to add your Layers, which are shown in same order as they are added. You can change layer order later, make layers invisible also. Typically you have basemap layer, such as CartoOnlineVectorTileLayer. Brand new map view is empty, with no map data.

MapView has direct methods to zoom, pan, rotate and tilt map. Also you can request coordinates of the map view etc.

MapView options

CARTO SDK MapView has various configuration options and settings, some of them are generic enough to be useful for almost any mobile app. SDK overview above lists some of them:

  • Parameters to limit interaction: zooming, panning rotating, tilting, touches
  • Finetune performance - number of threads/workers to load data. Set bigger for multi-core devices
  • Set custom map and sky background patterns and clors
  • etc

Listening to Events

SDK contains various Listener hooks that are used to receive various events from the SDK.

There are listeners for user interaction related events, rendering events and map download events among others:

  • MapEventListener - map touches and movements - attach it to your MapView. See below for details
  • VectorElementEventListener - listen Vector Element touches/clicks, attach it to the related VectorLayer.

Implementing MapEventListener

An app can implement a custom MapEventListener to receive notifications about various events, such as:

  • Map is being moved or zoomed (onMapMoved)
  • Map has reached ‘stable state’, all tiles and data has been loaded displayed (onMapStable)
  • Map has been clicked (onMapClicked)

Create a new class called MyMapEventListener which implements MapEventListner interface.

    public class MyMapEventListener extends MapEventListener {
        private MapView mapView;
        private LocalVectorDataSource vectorDataSource;

        private BalloonPopup oldClickLabel;

        public MyMapEventListener(MapView mapView, LocalVectorDataSource vectorDataSource) {
            this.mapView = mapView;
            this.vectorDataSource = vectorDataSource;
        }

        @Override
        public void onMapMoved() {
            final MapPos topLeft = mapView.screenToMap(new ScreenPos(0, 0));
            final MapPos bottomRight = mapView.screenToMap(new ScreenPos(mapView.getWidth(), mapView.getHeight()));
        }

        @Override
        public void onMapClicked(MapClickInfo mapClickInfo) {

            // Remove old click label
            if (oldClickLabel != null) {
                vectorDataSource.remove(oldClickLabel);
                oldClickLabel = null;
            }

            BalloonPopupStyleBuilder styleBuilder = new BalloonPopupStyleBuilder();
            // Make sure this label is shown on top all other labels
            styleBuilder.setPlacementPriority(10);

            // Check the type of the click
            String clickMsg = null;
            if (mapClickInfo.getClickType() == ClickType.CLICK_TYPE_SINGLE) {
                clickMsg = "Single map click!";
            } else if (mapClickInfo.getClickType() == ClickType.CLICK_TYPE_LONG) {
                clickMsg = "Long map click!";
            } else if (mapClickInfo.getClickType() == ClickType.CLICK_TYPE_DOUBLE) {
                clickMsg = "Double map click!";
            } else if (mapClickInfo.getClickType() == ClickType.CLICK_TYPE_DUAL) {
                clickMsg ="Dual map click!";
            }

            MapPos clickPos = mapClickInfo.getClickPos();

            BalloonPopupStyle style = styleBuilder.buildStyle();

            MapPos wgs84Clickpos = mapView.getOptions().getBaseProjection().toWgs84(clickPos);
            String msg = String.format(Locale.US, "%.4f, %.4f", wgs84Clickpos.getY(), wgs84Clickpos.getX());

            BalloonPopup clickPopup = new BalloonPopup(clickPos, style, clickMsg, msg);

            vectorDataSource.add(clickPopup);
            oldClickLabel = clickPopup;
        }
    }

    
  public class MyMapListener : MapEventListener
    {
        MapView mapView;
        LocalVectorDataSource dataSource;
    
        BalloonPopup _oldClickLabel;

        public MyMapListener(MapView mapView, LocalVectorDataSource dataSource)
        {
            this.mapView = mapView;
            this.dataSource = dataSource;
        }

        public override void OnMapClicked(MapClickInfo mapClickInfo)
        {
            // A note about iOS: DISABLE 'Optimize PNG files for iOS' option in iOS build settings,
            // otherwise icons can not be loaded using AssetUtils/Bitmap constructor as Xamarin converts
            // PNGs to unsupported custom format.

            // Remove old click label
            if (_oldClickLabel != null)
            {
                dataSource.Remove(_oldClickLabel);
                _oldClickLabel = null;
            }

            var styleBuilder = new BalloonPopupStyleBuilder();
            // Make sure this label is shown on top all other labels
            styleBuilder.PlacementPriority = 10;

            // Check the type of the click
            ClickType type = mapClickInfo.ClickType;
                  
            string clickMsg = "Unknown click Type";

            if (type == ClickType.ClickTypeSingle)
            {
                clickMsg = "Single map click!";
            }
            else if (type == ClickType.ClickTypeLong)
            {
                clickMsg = "Long map click!";
            }
            else if (type == ClickType.ClickTypeDouble)
            {
                clickMsg = "Double map click!";
            }
            else if (type == ClickType.ClickTypeDual)
            {
                clickMsg = "Dual map click!";
            }

            MapPos position = mapClickInfo.ClickPos;
            BalloonPopupStyle style = styleBuilder.BuildStyle();

            MapPos wgs84Position = mapView.Options.BaseProjection.ToWgs84(position);
            string description = "" + wgs84Position.Y + " - " + wgs84Position.X;
            var clickPopup = new BalloonPopup(position, style, clickMsg, description);

            dataSource.Add(clickPopup);
            _oldClickLabel = clickPopup;
        }

        public override void OnMapMoved()
        {

        }
    }
    
    @interface  MyMapEventListener : NTMapEventListener

    @property NTMapView* mapView;
    @property NTLocalVectorDataSource* vectorDataSource;

    @property NTBalloonPopup* oldClickLabel;

    -(void)setMapView:(NTMapView*)mapView vectorDataSource:(NTLocalVectorDataSource*)vectorDataSource;
    -(void)onMapMoved;
    -(void)onMapClicked:(NTMapClickInfo*)mapClickInfo;

    @end

    @implementation MyMapEventListener

    -(void)setMapView:(NTMapView*)mapView vectorDataSource:(NTLocalVectorDataSource*)vectorDataSource
    {
        self.mapView = mapView;
        self.vectorDataSource = vectorDataSource;
    }

    -(void)onMapMoved
    {
        // called very often, even just console logging can lag map movement animation
    }

    -(void)onMapClicked:(NTMapClickInfo*)mapClickInfo
    {
        if (self.oldClickLabel != nil) {
            [self.vectorDataSource remove:self.oldClickLabel];
            self.oldClickLabel = nil;
        }
        
        NSString* clickMsg;
        // Check the type of the click
        if ([mapClickInfo getClickType] == NT_CLICK_TYPE_SINGLE)
        {
            clickMsg = @"Single map click!";
        }
        else if ([mapClickInfo getClickType] == NT_CLICK_TYPE_LONG)
        {
            clickMsg = @"Long map click!";
        }
        else if ([mapClickInfo getClickType] == NT_CLICK_TYPE_DOUBLE)
        {
            clickMsg = @"Double map click!";
        }
        else if ([mapClickInfo getClickType] == NT_CLICK_TYPE_DUAL)
        {
            clickMsg = @"Dual map click!";
        }
        
        NTMapPos* clickPos = [mapClickInfo getClickPos];
        NTMapPos* wgs84Clickpos = [[[_mapView getOptions] getBaseProjection] toWgs84:clickPos];
        
        NSString* description = [NSString stringWithFormat:@"%f, %f", [wgs84Clickpos getY], [wgs84Clickpos getX]];
        
        NTBalloonPopupStyleBuilder* builder = [[NTBalloonPopupStyleBuilder alloc] init];
        [builder setPlacementPriority:10];
        NTBalloonPopupStyle* style = [builder buildStyle];
        
        NTBalloonPopup* popup = [[NTBalloonPopup alloc] initWithPos:clickPos style:style title:clickMsg desc:description];
        
        [self.vectorDataSource add:popup];
        self.oldClickLabel = popup;
    }

    @end

    
  
    public class MyMapEventListener : NTMapEventListener {
        var mapView: NTMapView?
        var vectorDataSource: NTLocalVectorDataSource?

        var oldClickLabel: NTBalloonPopup?
        
        convenience init(mapView: NTMapView?, vectorDataSource: NTLocalVectorDataSource?) {
            
            self.init()
            
            self.mapView = mapView
            self.vectorDataSource = vectorDataSource
        }
        
        public override func onMapMoved() {
            
            // This is called every time the map moves, and we can calculate positions as such:
            
            var topLeft = mapView?.screen(toMap: NTScreenPos(x: 0, y: 0))
            
            let width: Float = Float((mapView?.frame.width)!)
            let height: Float = Float((mapView?.frame.width)!)
            
            var bottomRight = mapView?.screen(toMap: NTScreenPos(x: width, y: height))
        }
        
        public override func onMapClicked(_ mapClickInfo: NTMapClickInfo!) {
            
            // Remove old click label
            if (oldClickLabel != nil) {
                vectorDataSource?.remove(oldClickLabel)
                oldClickLabel = nil
            }
            
            let clickPos = mapClickInfo?.getClickPos()
            
            let styleBuilder = NTBalloonPopupStyleBuilder();
            // Make sure this label is shown on top all other labels
            styleBuilder?.setPlacementPriority(10)
            let style = styleBuilder?.buildStyle()
            
            // Check the type of the click
            var clickMsg = ""
            let clickType = mapClickInfo?.getClickType()
            
            if (clickType == NTClickType.CLICK_TYPE_SINGLE) {
                clickMsg = "Single map click!"
            } else if (clickType == NTClickType.CLICK_TYPE_LONG) {
                clickMsg = "Long map click!"
            } else if (clickType == NTClickType.CLICK_TYPE_DOUBLE) {
                clickMsg = "Double map click!"
            } else if (clickType == NTClickType.CLICK_TYPE_DUAL) {
                clickMsg = "Dual map click!"
            }
            
            let wgs84Clickpos = mapView?.getOptions().getBaseProjection()?.toWgs84(clickPos)
            let msg = String(describing: wgs84Clickpos?.getY()) + String(describing: wgs84Clickpos?.getX())
            
            let clickPopup = NTBalloonPopup(pos: clickPos, style: style, title: clickMsg, desc: msg)
            
            vectorDataSource?.add(clickPopup)
            oldClickLabel = clickPopup
        }
    }

    
  
    class MyMapEventListener(var mapView: MapView?, var vectorDataSource: LocalVectorDataSource?) : MapEventListener() {
        var mapView: MapView? = null
        var vectorDataSource: LocalVectorDataSource? = null

        var oldClickLabel: BalloonPopup? = null

        override fun onMapMoved() {

            // This is called every time the map moves, and we can calculate positions as such:

            var topLeft = mapView?.screenToMap(ScreenPos(0F, 0F))

            val width: Float = mapView!!.width.toFloat()
            val height: Float = mapView!!.height.toFloat()

            var bottomRight = mapView?.screenToMap(ScreenPos(width, height))
        }

        override fun onMapClicked(mapClickInfo: MapClickInfo?) {

            // Remove old click label
            if (oldClickLabel != null) {
                vectorDataSource?.remove(oldClickLabel)
                oldClickLabel = null
            }

            val clickPos = mapClickInfo?.clickPos

            val styleBuilder = BalloonPopupStyleBuilder();
            // Make sure this label is shown on top all other labels
            styleBuilder.placementPriority = 10
            val style = styleBuilder.buildStyle()

            // Check the type of the click
            var clickMsg = ""
            val clickType = mapClickInfo?.clickType

            if (clickType == ClickType.CLICK_TYPE_SINGLE) {
                clickMsg = "Single map click!"
            } else if (clickType == ClickType.CLICK_TYPE_LONG) {
                clickMsg = "Long map click!"
            } else if (clickType == ClickType.CLICK_TYPE_DOUBLE) {
                clickMsg = "Double map click!"
            } else if (clickType == ClickType.CLICK_TYPE_DUAL) {
                clickMsg = "Dual map click!"
            }

            val wgs84Clickpos = mapView?.options?.baseProjection?.toWgs84(clickPos)
            val msg = wgs84Clickpos?.y.toString() + wgs84Clickpos?.x

            val clickPopup = BalloonPopup(clickPos, style, clickMsg, msg)

            vectorDataSource?.add(clickPopup)
            oldClickLabel = clickPopup
        }
    }

    

MyMapEventListener contains special functionality that shows, or hides, a small label text over a clicked object. The following label click event rules apply:

  • If you click on an object, it creates another map object as a BalloonPopup. This contains the text from the metadata of the clicked object. For this reason, our samples apps include a Metadata field value. You can use the metadata value directly, or use the object unique ID as the metadata, to query details about the clicked object from the database.
  • When a viewer clicks a new location on the map (or click another map object), the original BalloonPopup is deleted, and new one appears at the click location
  • The BalloonPopup is added to the DataSource, which is linked existing map layers. The same DataSource is used for other vector elements on a map

Initializing Listener

Apply the following code to initialize listener events for map clicks.

  
    mapView.setMapEventListener(new MyMapEventListener(mapView, source));

    
  
    MapView.MapEventListener = new MyMapListener(MapView, source);

    
  
    MyMapEventListener* listener = [[MyMapEventListener alloc] init];
    [listener setMapView:mapView vectorDataSource:vectorDataSource];
    [mapView setMapEventListener:listener];

    
  
    mapView?.setMapEventListener(MyMapEventListener(mapView: mapView, vectorDataSource: source))

    
    mapView?.mapEventListener = MyMapEventListener(mapView, source)