-
Notifications
You must be signed in to change notification settings - Fork 30
Geo Widget
Geowidget is and android widget that allows visualisation , inspection and manipulation of geospatial data View geospatial features Edit geospatial features View details on geospatial features Offline support
This widget implements kujaku mapview (which implements Mapbox). Kujaku is a japanese word meaning peacock
Geowidget can offer more features like drawing polygons and estimate size of a map section.
It
- Allows customisation
- Use tiles from different tile servers eg. Digital Globe for Reveal
- Supports GeoJSON
- Extendable
Add the artifact repository url to your project root build.gradle file
allprojects {
repositories {
\\ Other repositories here
...
mavenCentral()
}
}
Add the library as a dependency
implementation('io.ona.kujaku:library:0.9.0') {
exclude group: 'com.android.volley'
exclude group: 'stax', module: 'stax-api'
}
This may be required in case you are accessing styles and other functionality from mapbox.com
Enable your token like this in the host application:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Mapbox Access token
Mapbox.getInstance(getApplicationContext(), getString(R.string.mapbox_access_token));
}
}
Open the Java file of the activity where you'd like to include the map in and add the code below to the file.
private KujakuMapView kujakuMapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
kujakuMapView = (MapView) findViewById(R.id.kujakuMapView);
kujakuMapView.onCreate(savedInstanceState);
kujakuMapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(MapboxMap mapboxMap) {
// Customize map with markers, polylines, etc.
}
});
}
Open the activity's XML layout file and add the mapView within your layout.
<io.ona.kujaku.views.KujakuMapView
android:id="@+id/kujakuMapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
mapbox:mapbox_styleUrl="@string/mapbox_style_mapbox_streets" />
Below is a sample JSON Config to use geowidget (Some tags and blocks are subject to change based on need basis)
{
"title": "Register Structure",
"display_back_button": true,
"no_padding": true,
"fields": [
{
"key": "structure",
"type": "geowidget",
"v_zoom_max": {
"value": "16.5",
"err": "Please zoom in to add a point"
}
},
{
"key": "selectedOpAreaLabel",
"type": "label",
"text": "Selected Operational Area",
"read_only": false,
"hint_on_text": false,
"text_color": "#000000",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": ""
},
{
"key": "selectedOpAreaName",
"type": "label",
"text": "",
"read_only": false,
"hint_on_text": true,
"text_color": "#000000",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": ""
},
{
"key": "structureType",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": "",
"type": "native_radio",
"label": "Type of Structure",
"options": [
{
"key": "Residential Structure",
"text": "Residential Structure"
},
{
"key": "Mosquito Collection Point",
"text": "Mosquito Collection Point"
},
{
"key": "Larval Breeding Site",
"text": "Larval Breeding Site"
},
{
"key": "Potential Area of Transmission",
"text": "Potential Area of Transmission"
}
],
"value": "Residential Structure",
"v_required": {
"value": true,
"err": "Please specify Type of structure"
}
},
{
"key": "physicalType",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": "",
"type": "native_radio",
"label": "Location Physical Type",
"options": [
{
"key": "Home",
"text": "Home"
},
{
"key": "Hut",
"text": "Hut"
}
],
"value": "Home",
"relevance": {
"step1:structureType": {
"type": "string",
"ex": "equalTo(., \"Residential Structure\")"
}
}
},
{
"key": "structureName",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": "",
"type": "edit_text",
"hint": "Name of structure",
"edit_type": "name"
},
{
"key": "zoom_level",
"type": "hidden",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": ""
},
{
"key": "valid_operational_area",
"type": "hidden",
"openmrs_entity_parent": "",
"openmrs_entity": "",
"openmrs_entity_id": ""
}
]
}
If you're using an Android Studio version that is 3.1.0 or above, you can ignore this section because the new dex compiler D8 will be enabled by default.
The Mapbox Maps SDK for Android introduces the use of Java 8. To fix any Java versioning issues, ensure that you are using Gradle version of 3.0 or greater. Once you’ve done that, add the following compileOptions
to the android section of your app-level build.gradle
file like so:
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Kujaku is built on-top-of Mapbox: We are currently using com.mapbox.mapboxsdk:mapbox-android-sdk:9.7.1
Other dependencies include:
androidx.legacy:legacy-support-v4:1.0.0
io.ona.kujaku:utils:0.9.0
com.android.volley:volley:1.2.0
com.snatik:storage:2.1.0
joda-time:joda-time:2.9.9
com.android.support:cardview-v7:26.+
com.android.support:recyclerview-v7:26.+
com.google.android.gms:play-services-maps:8.4.0
com.google.android.gms:play-services-location:8.4.0
io.realm:realm-android-library:4.1.1
io.realm:realm-annotations:4.1.1
com.android.support:appcompat-v7:26.1.0
com.cocoahero.android:geojson:1.0.1
The library uses the following permissions:
-
android.permission.ACCESS_FINE_LOCATION
To get the user’s location when the “My location button” is clicked -
android.permission.ACCESS_NETWORK_STATE
To automatically switch between Google Location services and device GPS depending on internet connection availability when retrieving the user’s location -
android.permission.READ_EXTERNAL_STORAGE
To read styles stored in local storage when using the MapActivity -
android.permission.WRITE_EXTERNAL_STORAGE
To cache styles in local storage when using the MapActivity -
android.permission.INTERNET
To fetch tiles and styles from Mapbox To use enable use of Google Location services when retrieving the user’s location
To provide data to KujakuMapView
you initially set a Mapbox style, the KujakuMapView
comes with its default style referenced by it’s style url. This style can contain all the styling and data required. For more go to https://www.mapbox.com/studio/.
The style contains the basemap that you want to use, it can also contain parameters such as zoom level, map center and constraints. These and more can be found on the Mapbox Style Specification documentation at https://www.mapbox.com/mapbox-gl-js/style-spec/
Each layer in this approach has a specific styling. You then attach data-sources to it. The data points in your source will use the layer’s styling eg circles, polygons, icons etc.
We end up with something like this:
In this case, you style data across ranges or style data with conditions using a single layer and a single data source (a single FeatureCollection made up of GeoJSON feature array). The only limit being you can only use this a single layer type’s visual characteristics - You cannot mix a fill layer, line layer or an icon layer
For a single feature
com.mapbox.geojson.Feature feature =
com.mapbox.geojson.Feature.fromGeometry(
com.mapbox.geojson.Point.fromLngLat(
// Lat, Long
1.39239, 7.923993
)
);
GeoJSONSource pointsSource = new GeoJsonSource(pointsSourceId);
pointsSource.setGeoJson(feature);
For multiple features
GeoJson FeatureCollection string
JSONArray featuresArray = new JSONArray();
//featuresArray.put(jsonFeature)
JSONObject featureCollection = new JSONObject();
featureCollection.put("type", "FeatureCollection");
featureCollection.put("features", featuresArray);
featureCollection.toString();
GeoJSONSource pointsSource = new GeoJsonSource(“reveal-tasks-source”);
pointsSource.setGeoJson(featureCollection);
There are many other ways to create the GeoJSONSource...
Finally, add the source to the MapboxMap
mapboxMap.addSource(pointsSource);
What a mapbox style with layer styling looks like?
{
"id": "red and blue kids - ",
"type": "symbol",
"source": "composite",
"source-layer": "zeir-livingstone-data-2-kigamba",
"filter": ["==", "$type", "Point"],
"layout": {
"icon-image": [
"step",
["get", "is_red"],
"red-baby",
1,
"blue-baby"
],
"icon-allow-overlap": true,
"visibility": "none"
},
"paint": {}
},
{
"id": "red and blue kids - data conditions",
"type": "symbol",
"source": "composite",
"source-layer": "zeir-livingstone-data-2-kigamba",
"filter": ["==", "$type", "Point"],
"layout": {
"icon-allow-overlap": true,
"icon-image": [
"match",
["get", "is_red"],
0,
"blue-baby",
[1],
"red-baby",
""
]
},
"paint": {}
}
This uses the following expressions https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-match and https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step
The most important listeners:
onMapClick
onCameraChange
onScroll
How to get the feature that was clicked on the map depends on the onMapClickListener
mapboxMap.addOnMapClickListener(new MapboxMap.OnMapClickListener() {
@Override
public void onMapClick(@NonNull LatLng point) {
// Convert LatLng coordinates to screen pixel and only query the rendered features.
final PointF pixel = mapboxMap.getProjection().toScreenLocation(point);
List<Feature> features = mapboxMap.queryRenderedFeatures(pixel);
}
}
- Use the low-level API which gives you control on what to show and define the user experience/flow to achieve the add point operation
The low-level API means that it enables you to control the small operations that end up forming a complete add point API. The KujakuMapView
In the add point API, you have access to the following functions
/**
* Enables/disables the marker layout so that the user can scroll to their preferred location
*
* @param canAddPoint
*/
void enableAddPoint(boolean canAddPoint);
/**
* Call this to enable/disable adding a new point using GPS. In this layout the user cannot
* scroll since the marker position is guided by the GPS updates. Disabling it disables the marker
* layout and GPS querying.
*
* @param canAddPoint
* @param onLocationChanged
*/
void enableAddPoint(boolean canAddPoint, @Nullable OnLocationChanged onLocationChanged);
/**
* This should be called after calling {@link #enableAddPoint(boolean)} with {@code true} thus
* disabling the marker layout, adding a point at the current marker position & returns a geoJSON
* feature of the current marker position.
*
* @return
*/
@Nullable JSONObject dropPoint();
/**
* This should be called after calling {@link #enableAddPoint(boolean, OnLocationChanged)} with {@code true} thus
* disabling the marker layout, disabling GPS location updates, adding a point at @param latLng & returns a geoJSON
* feature at @param latLng
*
* @param latLng
* @return
*/
@Nullable JSONObject dropPoint(@Nullable LatLng latLng);
The following activity in the app showcases this io.ona.kujaku.sample.activities.LowLevelManualAddPointMapView.java
In low-level, you therefore have to define your own UI for enabling the user to show the marker layout and enable the add point mode. You also add the UI for disabling the mode
kujakuMapView = findViewById(R.id.kmv_lowLevelManualAddPointMapView_mapView);
kujakuMapView.showCurrentLocationBtn(true);
kujakuMapView.enableAddPoint(true);
Button button = findViewById(R.id.btn_lowLevelManualAddPointMapView_doneBtn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (kujakuMapView.isCanAddPoint()) {
JSONObject featurePoint = kujakuMapView.dropPoint();
Log.e("FEATURE POINT", featurePoint.toString());
}
}
});
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
kujakuMapView.enableAddPoint(!kujakuMapView.isCanAddPoint());
return true;
}
});
- Use the high-level API which gives you less control off what is shown and the flow on the UI. You basically call one method and provide a callback. Once the add operation is either completed or cancelled, the callback is made with the point GeoJSON or the cancel function
The following activity in the app showcases this io.ona.kujaku.sample.activities.HighLevelMapView.java
In high-level API, all UI is provided and you cannot change it
kujakuMapView = findViewById(R.id.kmv_highLevelMapView_mapView);
kujakuMapView.addPoint(false, new AddPointCallback() {
@Override
public void onPointAdd(JSONObject jsonObject) {
Log.d(TAG, jsonObject.toString());
}
@Override
public void onCancel() {
Log.d(TAG, "User cancelled adding points");
}
});
Forked from Android Native JSON Form Library. Adapted in love by the OpenSRP Community . Apache License, Version 2.0
Introduction
Core Features
Form
Views