Skip to content

Instantly share code, notes, and snippets.

@Nikituh
Last active May 24, 2019 09:15
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Nikituh/0fdba8c1be8ea82f979bbf40ad844a4a to your computer and use it in GitHub Desktop.
Mobile GIS workshop

Mobile GIS workshop

alt text

Jaak Laineste, head of mobile, CARTO

Aare Undo, mobile developer, CARTO

Link to presentation: https://docs.google.com/presentation/d/1I1yIHg0WlkbnKcodz3MMEcd6eMxHovcV6vcRJfMNFxk

FOSS4G 2016. Bonn, Germany

Preparation

A computer with:

  • Linux, Mac or Windows. Simplest for everyday use would be Mac
  • Additional permissions to install software (Java JDK and USB Drivers) are needed
  • Workshop computers are Linux OSGEO Live, so we use this as base
  • Android Studio, includes everything needed for Android app development (Eclipse is deprecated)

Required skills:

  • Java development knowledge basics

More help

Install Android Studio

Linux computer steps

Download Android studio installer. Several ways to get it:

  • Go to Android Developer site, download .zip file or installer for your platform.
  • Google “Android Studio 2.1.2”
  • Firefox does not want to show download in Ubuntu. Alternative download: https://goo.gl/Xl36n3

Unzip android studio zip file

Install pre-requisites for Ubuntu 16.4 (link):
sudo apt-get update
sudo apt-get install lib32stdc++6

And if you plan on using the emulator, you also need to install the following:

sudo apt-get install lib64stdc++6
cd Android/Sdk/tools/lib64/libstdc++
mv libstdc++.so.6 libstdc++.so.6.bak
ln -s /usr/lib64/libstdc++.so.6 ./
Start Android Studio
cd Downloads/android-studio/bin
./studio.sh

Android app principles

In broad terms, the general (basic) user interface setup of Android consists of two components: the activity the view (it’s a bit more complicated, but let’s leave it at that for now).

An Activity represents a single screen with a user interface. For example, an email app might have one activity that shows a list of new emails, another activity to compose an email, and another activity for reading emails. Activities are in charge of updating the data a certain view displays.

Example of a basic activity with view inflation and click-handlers:

alt text

A view in android is, generally, written in XML and inflated when a certain activity becomes “active” (is visible on the screen).

Example of a basic view file in xml (here you can see the id “toolbar” that we inflate in the previous image):

alt text

Now, when working with the CARTO, we use the same approach, but we use a custom class to indicate that a map should be shown instead of a button or a text field:

alt text

Download

However, in order to be able to make use of CARTO’s map, we first need to import the SDK. Download CARTO’s latest mobile SDK from the following link: https://nutifront.s3.amazonaws.com/sdk_snapshots/sdk4-android-snapshot-latest.zip

Creating a new Project

Now we’ve, finally, reached the point where you should open Android Studio and starting coding! After opening Android Studio, create a new project and set a unique Application name and Company domain (this is important when registering for a license on carto.com).

alt text

We don’t really want much example code in our example, so choose an Empty Activity:

alt text

Importing CARTO Mobile SDK

We use Maven dependency mechanism to include our SDK. To download the library as such, add the following lines to your build.gradle file and press Sync now

allprojects {
    repositories {
        jcenter()
        maven {
            url "http://repository-nutiteq.forge.cloudbees.com/release/"
        }
        maven {
            url "http://repository-nutiteq.forge.cloudbees.com/snapshot/"
        }
    }
}

dependencies {
    compile 'com.carto:carto-mobile-sdk:snapshot@aar'
}

Implementing CARTO Mobile SDK

Now you should be ready to start using CARTO’s SDK. Go ahead and replace your activity’s main view with the following snippet:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:minWidth="25dp"
   android:minHeight="25dp">
   <com.carto.ui.MapView
       android:id="@+id/map_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
</RelativeLayout>

And in your activity’s onCreate method reference that view as such:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   MapView mapView = (MapView)findViewById(R.id.map_view);
}

It should show a light-bluepopup prompting you to import a package. Import it and the red squiggly lines should disappear :).

Finally, now comes the time where you make use of the license you received when you registered on carto.com and created a new application.

Registering your license requires internet access and android requires that you add a permission beforehand. Be sure add the following to your AndroidManifest.xml right after the tag:

<uses-permission android:name="android.permission.INTERNET"/>

Getting Mobile SDK License

Normally you get a license key by logging in to https://carto.com/ and registering a new mobile application, but we have created a temporary license key for this event:

XTUN3Q0ZDc1BGVmtzSng1bjVMdHNJbmFrc0I3d2psT3hBaFFTTUF1L21NSCt1M3FQcnYrYkxOWnJqTFRUbnc9PQoKcHJvZHVjdHM9c2RrLWFuZHJvaWQtNC4qLHNkay1pb3MtNC4qCnBhY2thZ2VOYW1lPSoKd2F0ZXJtYXJrPWN1c3RvbQp2YWxpZFVudGlsPTIwMTYtMDktMDEKdXNlcktleT0zNjNmMTc3Y2YzNjUwMzBmMWVlOGI5M2NiZjU2NzhkYQo=

Registering CARTO Mobile SDK License

Now that you’ve got your key, add the following line to your activity:

static final String LICENSE = "<YOUR-LICENSE-HERE>";

Now, in your activity’s onCreate, register your license:

MapView.registerLicense(LICENSE, getApplicationContext());

Setting up a basic Map

Once the license is registered, you can initialize your map and add a base layer to it (mapView was declared as a class variable):

setContentView(R.layout.activity_hello_map);
mapView = (MapView) this.findViewById(R.id.map_view);

CartoOnlineVectorTileLayer layer =
       new CartoOnlineVectorTileLayer(CartoBaseMapStyle.CARTO_BASEMAP_STYLE_DEFAULT);
mapView.getLayers().add(layer);

At this point you should run your application and see what happens. If everything is correct, you should see something like this:

alt text

For our next example, let’s try zooming in to Berlin. First, we find out the latitude and longitude of Berlin, which happen to be 13.38933 and 52.51704, then add the following snippet to your code and it should zoom in to Berlin:

Projection projection = mapView.getOptions().getBaseProjection();

MapPos berlin = projection.fromWgs84(new MapPos(13.38933, 52.51704));
mapView.setFocusPos(berlin, 0);
mapView.setZoom(10, 0);

The result should look something like this:

alt text

Adding a vis.json Layer

That’s pretty cool, isn’t it? But we’re not done just yet. Now we finally get to use the sample vis.json url and see what happens. Add the following method to your to your activity:

protected void updateVis(final String url) {
   Thread thread = new Thread(new Runnable() {
       @Override
       public void run() {
           mapView.getLayers().clear();

           // Create overlay layer for popups
           Projection proj = mapView.getOptions().getBaseProjection();
           LocalVectorDataSource dataSource = new LocalVectorDataSource(proj);
           VectorLayer vectorLayer = new VectorLayer(dataSource);

           // Create VIS loader
           CartoVisLoader loader = new CartoVisLoader();
           loader.setDefaultVectorLayerMode(true);
           MyCartoVisBuilder visBuilder = new MyCartoVisBuilder(vectorLayer);
           try {
               loader.loadVis(visBuilder, url);
           }
           catch (IOException e) {
               Log.e("EXCEPTION", "Exception: " + e);
           }

           // Add the created popup overlay layer on top of all visJSON layers
           mapView.getLayers().add(vectorLayer);
       }
   });
  
   thread.start(); // TODO: should serialize execution
}

And invoke it in your activity's onCreate (after zooming in to Berlin):

String url = "http://documentation.carto.com/api/v2/viz/2b13c956-e7c1-11e2-806b-5404a6a683d5/viz.json";
updateVis(url);

You may notice that you can’t seem to be able to “fix” that one squiggly red line under MyCartoVisBuilder. That’s because it’s a custom class we create ourselves, inheriting from CartoVisBuilder. Here it is, copy it into your activity class:

private class MyCartoVisBuilder extends CartoVisBuilder {
   private VectorLayer vectorLayer; // vector layer for popups

   public MyCartoVisBuilder(VectorLayer vectorLayer) {
       this.vectorLayer = vectorLayer;
   }

   @Override
   public void setCenter(MapPos mapPos) {
MapPos position = mapView.getOptions().getBaseProjection().fromWgs84(mapPos);
mapView.setFocusPos(position, 1.0f);
   }

   @Override
   public void setZoom(float zoom) {
       mapView.setZoom(zoom, 1.0f);
   }

   @Override
   public void addLayer(Layer layer, Variant attributes) {
       // Add the layer to the map view
       mapView.getLayers().add(layer);
   }
}

If you used the sample url we provided, your screen should look like this:

alt text

Creating your own vis.json file

Log in to https://carto.com/ and go to your dashboard (left click on your avatar) and press NEW MAP. Then you have to option to connect a data set or use an existing one and create a map.

Then you have the option to manipulate with the data to enhance the map. Try different options from the tabs on the right.

When you’re done, press publish and replace the sample url of the application with your CartoDB.js url.

alt text

Adding Markers

Now it’s time to add a marker to our custom map. Copy the following method to your activity:

    private void addMarkerToPosition(MapView map, MapPos wgsPosition)
    {
        // Create a new layer
        Projection projection = map.getOptions().getBaseProjection();
        LocalVectorDataSource datasource = new LocalVectorDataSource(projection);
        VectorLayer layer = new VectorLayer(datasource);

        // Add layer to map
        map.getLayers().add(layer);

        MarkerStyleBuilder builder = new MarkerStyleBuilder();
        builder.setSize(30);

        builder.setColor(new Color(android.graphics.Color.GREEN));

        // Set marker position and style
        MapPos position = projection.fromWgs84(wgsPosition);
        MarkerStyle style = builder.buildStyle();

        // Create marker and add it to the source
        Marker marker = new Marker(position, style);
        datasource.add(marker);
    }

And invoke it right after adding the vis.json layer to your map as such:

MapPos tallinn = new MapPos(24.646469, 59.426939);
addMarkerToPosition(mapView, tallinn);

The final result should look like this:

alt text

Now, how about we make the map interactive?

Listening to map clicks

CARTO Mobile SDK can also listen to map clicks. Copy the following class to your activity:

private 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() {

   }

   @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);

       MapPos position = mapClickInfo.getClickPos();
       BalloonPopupStyle style = styleBuilder.buildStyle();

       MapPos wgs84Position = mapView.getOptions().getBaseProjection().toWgs84(position);

       String title = "You just clicked at:";
       String description = String.format(Locale.US, "%.4f, %.4f", wgs84Position.getY(), wgs84Position.getX());


       BalloonPopup clickPopup = new BalloonPopup(position, style, title, description);

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

And then invoke it after adding your first marker:

LocalVectorDataSource clickSource = new LocalVectorDataSource(proj);
VectorLayer clickLayer = new VectorLayer(clickSource);

mapView.getLayers().add(clickLayer);

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

Now, if you click on your map, it should look something like this:

alt text

CARTO Offline Map

So far we've covered what we can do online, but our base map also works offline. Let’s download a package. To do that, we must first create a package manager. But first, let’s clean up our current activity. Just remove or comment out the following lines:

CartoOnlineVectorTileLayer layer =
       new CartoOnlineVectorTileLayer(CartoBaseMapStyle.CARTO_BASEMAP_STYLE_DEFAULT);
mapView.getLayers().add(layer);

Projection projection = mapView.getOptions().getBaseProjection();

MapPos berlin = projection.fromWgs84(new MapPos(13.38933, 52.51704));
mapView.setFocusPos(berlin, 0);
mapView.setZoom(10, 0);

updateVis();

Then add the following class variables:

static final String bonn = "bbox(7.0082,50.7284,7.1582,50.7454)";
CartoPackageManager manager;

Now we’re going to create a directory for map packages, if it doesn’t already exist:

File folder = new File(getApplicationContext().getExternalFilesDir(null), "map_packages");

if (!folder.isDirectory()) {
   folder.mkdir();
}

Then we initialize our package manager and start package download, if it’s not already downloaded:

try {
   manager = new CartoPackageManager("nutiteq.osm", folder.getAbsolutePath());
}
catch (IOException e) {
   System.out.println("Exception: " + e.getMessage());
}

if (manager == null) {
   Toast.makeText(this, "Unable to initialize package manager", Toast.LENGTH_LONG).show();
   return;
}

manager.start();

if (manager.getLocalPackage(bonn) == null) {
   manager.startPackageDownload(bonn);
}

However, before we can start using the offline map, we need to create an asset folder:

alt text

Now download nutibright-v3.zip from https://nutifront.s3.amazonaws.com/releases/nutibright-v3.zip and add copy it to your asset folder.

And finally we need to create and add the actual layer and zoom in to Bonn. Copy the following snippet to your activity’s onCreate:

PackageManagerTileDataSource source = new PackageManagerTileDataSource(manager);
BinaryData styleBytes = AssetUtils.loadAsset("nutibright-v3.zip");
CompiledStyleSet styleSet = new CompiledStyleSet(new ZippedAssetPackage(styleBytes));

MBVectorTileDecoder decoder = new MBVectorTileDecoder(styleSet);

VectorTileLayer layer = new VectorTileLayer(source, decoder);

mapView.getLayers().add(layer);

MapPos bonnPos = mapView.getOptions().getBaseProjection().fromWgs84(new MapPos(7.0982, 50.7374));

mapView.setFocusPos(bonnPos, 0);
mapView.setZoom(14, 0);

And now Bonn is available to you offline!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment