Draw UNL Grid with Mapbox Maps SDK for Android

This example demonstrates how UNL Core library can be used in an Android app with Mapbox Maps SDK for Android library to draw a UNL Grid. In this example, we are going to generate the Unl grid lines and display them on the map by creating and adding a new layer to the map.

Project setup#

  1. First, you have to create an Android Studio project, with an empty activity:

Make sure the minimum SDK is at least select API 14: Android 4.0.0 (IceCreamSandwich), which is the lowest API level supported by Mapbox Maps SDK for Android:

For further information about setting up the project and an AVD, checkout the Android Studio documentation.

  1. Install the Mapbox Maps SDK for Android

To install the Mapbox SDK, you need to:

  • Sign up to Mapbox and go to your Account page
  • Create an access token
  • Add the token to your project's R.strings.xml file:
<string name="mapbox_access_token">MAPBOX_ACCESS_TOKEN</string>
  • Add the dependency:

    • In the build.gradle file include the new build rule for the latest mapbox-android-sdk:
    dependencies {
    implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.5.0'
    }
    • In the project-level build.gradle, declare the Mapbox Downloads API's v2/release/maven endpoint in the repositories block. Make sure you use the 'mapbox' as username and the token generated at the previous steps as password:
allprojects {
repositories {
maven {
url 'https://api.mapbox.com/downloads/v2/releases/maven'
authentication {
basic(BasicAuthentication)
}
credentials {
// Do not change the username below.
// This should always be `mapbox` (not your username).
username = 'mapbox'
// Use the secret token you stored in gradle.properties as the password
password = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: ""
}
}
}
}
  • Sync the Gradle files

You can read more about Mapbox Maps SDK for Android installation and usage here.

  1. Install UnlCore

To add UnlCore as a dependency to your Android project you must:

  • Authenticate to GithubPackages. Replace USERNAME with your GitHub username and TOKEN with your personal access token.
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/u-n-l/core-java")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}
  • Add the UnlCore dependency to your build.gradle file:
implementation 'unl:core:1.0.1'
  • Add the maven plugin to your build.gradle file:
plugins {
id 'maven'
}
  • Sync the gradle files or run gradle install

You can read more about the UnlCore library for Java installation and usage here.

Display Mapbox map#

Replace the code from the activity—main.xml file with the following to display the MapView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

In the onCreate method, set the map style and animate the camera to the INITIAL_COORDINATES and INITIAL_ZOOM level:

package com.example.unlcoresampleapp;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Style;
public class MainActivity extends AppCompatActivity {
private MapView mapView;
private MapboxMap mapboxMap;
private static final LatLng INITIAL_COORDINATES = new LatLng(52.3676, 4.9041);
private static final int INITIAL_ZOOM = 18;
private static final int INITIAL_ANIMATION_DURATION_IN_MS = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(this, getString(R.string.mapbox_access_token));
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap ->{
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.MAPBOX_STREETS, style ->{
CameraPosition position = new CameraPosition.Builder().target(INITIAL_COORDINATES).zoom(INITIAL_ZOOM).build();
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), INITIAL_ANIMATION_DURATION_IN_MS);
});
});
}
}

Display UNL Grid on the map#

We will restrict the zoom level at which the unl grid layer will be visible. Also define a color for the lines:

private static final String UNL_GRID_LINES_COLOR = "#C0C0C0";
private static final int MIN_GRID_LINES_ZOOM = 17;

In order to display the grid lines, we need to create the FeatureCollection, based on which we will create the GeoJsonSource. We get a CameraPosition object as input and validate the zoom level. Then, we get the visible bounds and generate the grid lines based on the sw and ne values, using the default precision, 9.

private LineString fromLngLats(double[][] coordinates) {
ArrayList <com.mapbox.geojson.Point> converted = new ArrayList<>(coordinates.length);
for (double[] coordinate: coordinates) {
converted.add(com.mapbox.geojson.Point.fromLngLat(coordinate[0], coordinate[1]));
}
return LineString.fromLngLats(converted);
}
private List<Feature> getGridLineFeatures() {
List<Feature> features = new ArrayList<>();
if (mapboxMap.getCameraPosition().zoom < MIN_GRID_LINES_ZOOM) {
return features;
}
VisibleRegion visibleRegion = mapboxMap.getProjection().getVisibleRegion();
Bounds bounds = new Bounds(
visibleRegion.latLngBounds.getLatNorth(), visibleRegion.latLngBounds.getLonEast(), visibleRegion.latLngBounds.getLatSouth(), visibleRegion.latLngBounds.getLonWest());
List <double[][]> gridLines = UnlCore.gridLines(bounds);
for (double[][] line: gridLines) {
features.add(
Feature.fromGeometry(fromLngLats(line)));
}
return features;
}

Next, create a source in the callback of the setStyle function and add LineLayer by passing the source id:

FeatureCollection gridLinesFeatureCollection = FeatureCollection.fromFeatures(getGridLineFeatures());
style.addSource(new GeoJsonSource("unl-grid-lines-source", gridLinesFeatureCollection));
Layer gridLinesLayer = new LineLayer("unlGridLinesLayer", "unl-grid-lines-source").withProperties( PropertyFactory.lineWidth(1f), PropertyFactory.lineColor(Color.parseColor(UNL_GRID_LINES_COLOR)));
gridLinesLayer.setMinZoom(MIN_GRID_LINES_ZOOM);
style.addLayer(gridLinesLayer);

In order the update the grid lines based on the current map bounds, we have to add the onCameraIdleListener and update the unl-grid-lines-source accordingly, with the new grid lines FeatureCollection:

mapboxMap.addOnCameraIdleListener(() ->{
GeoJsonSource geoJsonSource = style.getSourceAs("unl-grid-lines-source");
if (geoJsonSource != null) {
FeatureCollection newGridLinesFeatureCollection = FeatureCollection.fromFeatures(getGridLineFeatures());
geoJsonSource.setGeoJson(newGridLinesFeatureCollection);
}
});

Show/Hide the UnlGrid#

Add the floating action button for switching grid visibility in the activity_main.xml file:

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_layer_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_layers_black_24dp" />

Add the onClickListener:

findViewById(R.id.fab_layer_toggle).setOnClickListener(new View.OnClickListener() {@Override
public void onClick(View view) {
toggleLayer();
}
});

Implement the toggleLayer function which get the unlGridLinesLayer and switch its visibility:

private void toggleLayer() {
mapboxMap.getStyle(style ->{
Layer layer = style.getLayer("unlGridLinesLayer");
if (layer != null) {
if (VISIBLE.equals(layer.getVisibility().getValue())) {
layer.setProperties(visibility(NONE));
} else {
layer.setProperties(visibility(VISIBLE));
}
}
});
}

Complete source code of the Activity#

package com.example.unlcoresampleapp;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.LineString;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.VisibleRegion;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.LineLayer;
import com.mapbox.mapboxsdk.style.layers.PropertyFactory;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import java.util.ArrayList;
import java.util.List;
import unl.core.Bounds;
import unl.core.UnlCore;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
public class MainActivity extends AppCompatActivity {
private MapView mapView;
private MapboxMap mapboxMap;
private static final LatLng INITIAL_COORDINATES = new LatLng(52.3676, 4.9041);
private static final int INITIAL_ZOOM = 18;
private static final int INITIAL_ANIMATION_DURATION_IN_MS = 2000;
private static final String UNL_GRID_LINES_COLOR = "#C0C0C0";
private static final int MIN_GRID_LINES_ZOOM = 17;
private LineString fromLngLats(double[][] coordinates) {
ArrayList<com.mapbox.geojson.Point> converted = new ArrayList<>(coordinates.length);
for (double[] coordinate : coordinates) {
converted.add(com.mapbox.geojson.Point.fromLngLat(coordinate[0], coordinate[1]));
}
return LineString.fromLngLats(converted);
}
private List<Feature> getGridLineFeatures() {
List<Feature> features = new ArrayList<>();
if (mapboxMap.getCameraPosition().zoom < MIN_GRID_LINES_ZOOM) {
return features;
}
VisibleRegion visibleRegion = mapboxMap.getProjection().getVisibleRegion();
Bounds bounds = new Bounds(
visibleRegion.latLngBounds.getLatNorth(), visibleRegion.latLngBounds.getLonEast(),
visibleRegion.latLngBounds.getLatSouth(), visibleRegion.latLngBounds.getLonWest()
);
List<double[][]> gridLines = UnlCore.gridLines(bounds);
for (double[][] line : gridLines) {
features.add(
Feature.fromGeometry(fromLngLats(line))
);
}
return features;
}
@SuppressLint("ResourceType")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(this, getString(R.string.mapbox_access_token));
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> {
MainActivity.this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
FeatureCollection gridLinesFeatureCollection = FeatureCollection.fromFeatures(getGridLineFeatures());
style.addSource(new
GeoJsonSource("unl-grid-lines-source", gridLinesFeatureCollection));
Layer gridLinesLayer = new LineLayer("unlGridLinesLayer", "unl-grid-lines-source").withProperties(
PropertyFactory.lineWidth(1f),
PropertyFactory.lineColor(Color.parseColor(UNL_GRID_LINES_COLOR))
);
gridLinesLayer.setMinZoom(MIN_GRID_LINES_ZOOM);
style.addLayer(gridLinesLayer);
CameraPosition position = new CameraPosition.Builder()
.target(INITIAL_COORDINATES)
.zoom(INITIAL_ZOOM)
.build();
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), INITIAL_ANIMATION_DURATION_IN_MS);
mapboxMap.addOnCameraIdleListener(() -> {
GeoJsonSource geoJsonSource = style.getSourceAs("unl-grid-lines-source");
if (geoJsonSource != null) {
FeatureCollection newGridLinesFeatureCollection = FeatureCollection.fromFeatures(getGridLineFeatures());
geoJsonSource.setGeoJson(newGridLinesFeatureCollection);
}
}
);
findViewById(R.id.fab_layer_toggle).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
toggleLayer();
}
});
});
});
}
private void toggleLayer() {
mapboxMap.getStyle(style -> {
Layer layer = style.getLayer("unlGridLinesLayer");
if (layer != null) {
if (VISIBLE.equals(layer.getVisibility().getValue())) {
layer.setProperties(visibility(NONE));
} else {
layer.setProperties(visibility(VISIBLE));
}
}
});
}
}