Skip to content
This repository was archived by the owner on Oct 7, 2024. It is now read-only.

Commit be42fd7

Browse files
author
Langston Smith
authored
Adding bordered circle Kotlin example (#1275)
1 parent 0fa2cf5 commit be42fd7

10 files changed

Lines changed: 439 additions & 35 deletions

File tree

MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.mapbox.mapboxandroiddemo.examples.dds.CircleLayerClusteringActivity;
4040
import com.mapbox.mapboxandroiddemo.examples.dds.CircleRadiusActivity;
4141
import com.mapbox.mapboxandroiddemo.examples.dds.CircleToIconTransitionActivity;
42+
import com.mapbox.mapboxandroiddemo.examples.javaservices.KotlinBorderedCircleActivity;
4243
import com.mapbox.mapboxandroiddemo.examples.dds.CreateHotspotsActivity;
4344
import com.mapbox.mapboxandroiddemo.examples.dds.DrawGeojsonLineActivity;
4445
import com.mapbox.mapboxandroiddemo.examples.dds.DrawPolygonActivity;
@@ -1101,6 +1102,14 @@ private void initializeModels() {
11011102
R.string.activity_java_services_multiple_geometries_from_directions_route_url,
11021103
true, BuildConfig.MIN_SDK_VERSION));
11031104

1105+
exampleItemModels.add(new ExampleItemModel(
1106+
R.id.nav_java_services,
1107+
R.string.activity_java_services_bordered_circle_title,
1108+
R.string.activity_java_services_bordered_circle_description,
1109+
null,
1110+
new Intent(MainActivity.this, KotlinBorderedCircleActivity.class),
1111+
R.string.activity_java_services_bordered_circle_url, true, BuildConfig.MIN_SDK_VERSION));
1112+
11041113
exampleItemModels.add(new ExampleItemModel(
11051114
R.id.nav_snapshot_image_generator,
11061115
R.string.activity_image_generator_snapshot_notification_title,

MapboxAndroidDemo/src/main/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@
110110
android:name="android.support.PARENT_ACTIVITY"
111111
android:value="com.mapbox.mapboxandroiddemo.MainActivity" />
112112
</activity>
113+
<activity
114+
android:name=".examples.javaservices.KotlinBorderedCircleActivity"
115+
android:label="@string/activity_java_services_bordered_circle_title">
116+
<meta-data
117+
android:name="android.support.PARENT_ACTIVITY"
118+
android:value="com.mapbox.mapboxandroiddemo.MainActivity" />
119+
</activity>
113120
<activity
114121
android:name=".examples.camera.AnimateMapCameraActivity"
115122
android:label="@string/activity_camera_animate_title">
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package com.mapbox.mapboxandroiddemo.examples.javaservices
2+
3+
import android.graphics.Color
4+
import android.os.Bundle
5+
import android.widget.SeekBar
6+
import android.widget.TextView
7+
import android.widget.Toast
8+
import androidx.appcompat.app.AppCompatActivity
9+
import com.mapbox.geojson.Feature
10+
import com.mapbox.geojson.LineString
11+
import com.mapbox.geojson.Point
12+
import com.mapbox.geojson.Polygon
13+
import com.mapbox.mapboxandroiddemo.R
14+
import com.mapbox.mapboxsdk.Mapbox
15+
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
16+
import com.mapbox.mapboxsdk.geometry.LatLng
17+
import com.mapbox.mapboxsdk.maps.MapboxMap
18+
import com.mapbox.mapboxsdk.maps.Style
19+
import com.mapbox.mapboxsdk.style.layers.FillLayer
20+
import com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor
21+
import com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillOpacity
22+
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
23+
import com.mapbox.turf.TurfConstants.UNIT_KILOMETERS
24+
import com.mapbox.turf.TurfMeta
25+
import com.mapbox.turf.TurfTransformation
26+
import kotlinx.android.synthetic.main.activity_lab_turf_thick_bordered_circle.*
27+
28+
/**
29+
* Use [TurfTransformation.circle] to eventually create a thick border around a circular-shaped [Polygon].
30+
*/
31+
class KotlinBorderedCircleActivity : AppCompatActivity(), MapboxMap.OnMapClickListener {
32+
33+
private var mapboxMap: MapboxMap ? = null
34+
private lateinit var circlePolygonArea: Polygon
35+
private var lastClickPoint = STARTING_CAMERA_POINT
36+
private var circleRadiusUnits = UNIT_KILOMETERS
37+
private var currentSetCircleRadius = 25
38+
private var currentSetCircleBorderDifference = 2
39+
private var borderDifferenceUnitSeekbarMax = 200
40+
private var radiusSeekbarMax = 500
41+
private var circleOpacity = .7f
42+
43+
companion object {
44+
private const val CIRCLE_CENTER_SOURCE_ID = "CIRCLE_CENTER_SOURCE_ID"
45+
private const val CIRCLE_COLOR = "#2643ff"
46+
private const val CIRCLE_BORDER_COLOR = "#132287"
47+
private const val CIRCLE_BORDER_GEOJSON_SOURCE_ID = "CIRCLE_BORDER_GEOJSON_SOURCE_ID"
48+
private const val CIRCLE_GEOJSON_SOURCE_ID = "CIRCLE_GEOJSON_SOURCE_ID"
49+
private const val CIRCLE_BORDER_LAYER_ID = "CIRCLE_BORDER_LAYER_ID"
50+
private const val CIRCLE_LAYER_ID = "CIRCLE_LAYER_ID"
51+
private const val CIRCLE_STEPS = 360
52+
private const val RADIUS_SEEKBAR_DIFFERENCE = 1
53+
private const val BORDER_DIFFERENCE_SEEKBAR_DIFFERENCE = 1
54+
private val STARTING_CAMERA_POINT = Point.fromLngLat(33.22424223, 34.99415092)
55+
}
56+
57+
override fun onCreate(savedInstanceState: Bundle?) {
58+
super.onCreate(savedInstanceState)
59+
60+
// Mapbox access token is configured here. This needs to be called either in your application
61+
// object or in the same activity which contains the mapview.
62+
Mapbox.getInstance(this, getString(R.string.access_token))
63+
64+
// This contains the MapView in XML and needs to be called after the access token is configured.
65+
setContentView(R.layout.activity_lab_turf_thick_bordered_circle)
66+
mapView?.onCreate(savedInstanceState)
67+
mapView?.getMapAsync { mapboxMap ->
68+
mapboxMap.setStyle(Style.Builder().fromUri(Style.MAPBOX_STREETS)
69+
.withSource(GeoJsonSource(CIRCLE_CENTER_SOURCE_ID,
70+
Feature.fromGeometry(STARTING_CAMERA_POINT)))
71+
.withSource(GeoJsonSource(CIRCLE_BORDER_GEOJSON_SOURCE_ID))
72+
.withSource(GeoJsonSource(CIRCLE_GEOJSON_SOURCE_ID))
73+
.withLayer(FillLayer(CIRCLE_BORDER_LAYER_ID, CIRCLE_BORDER_GEOJSON_SOURCE_ID).withProperties(
74+
fillColor(Color.parseColor(CIRCLE_BORDER_COLOR))
75+
))
76+
.withLayer(FillLayer(CIRCLE_LAYER_ID, CIRCLE_GEOJSON_SOURCE_ID).withProperties(
77+
fillColor(Color.parseColor(CIRCLE_COLOR)),
78+
fillOpacity(circleOpacity)
79+
))) {
80+
81+
this@KotlinBorderedCircleActivity.mapboxMap = mapboxMap
82+
83+
drawPolygonCircle(lastClickPoint)
84+
85+
drawCircleBorder(lastClickPoint)
86+
87+
mapboxMap.addOnMapClickListener(this@KotlinBorderedCircleActivity)
88+
89+
Toast.makeText(this@KotlinBorderedCircleActivity,
90+
getString(R.string.tap_on_map_to_move_bordered_circle), Toast.LENGTH_SHORT).show()
91+
92+
initRadiusSeekbar()
93+
94+
initBorderDifferenceSeekbar()
95+
}
96+
}
97+
}
98+
99+
/**
100+
* Initialize the seekbar slider to adjust the circle radius
101+
*/
102+
private fun initRadiusSeekbar() {
103+
thick_bordered_circle_radius_seekbar?.apply {
104+
max = radiusSeekbarMax + RADIUS_SEEKBAR_DIFFERENCE
105+
incrementProgressBy(RADIUS_SEEKBAR_DIFFERENCE)
106+
progress = radiusSeekbarMax / 2
107+
108+
thick_bordered_circle_radius_textview?.text = String.format(getString(
109+
R.string.thick_bordered_circle_radius), progress)
110+
111+
this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
112+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
113+
adjustCircleRadius(thick_bordered_circle_radius_textview, seekBar.progress)
114+
}
115+
116+
override fun onStartTrackingTouch(seekBar: SeekBar) {
117+
// Not needed in this example.
118+
}
119+
120+
override fun onStopTrackingTouch(seekBar: SeekBar) {
121+
adjustCircleRadius(thick_bordered_circle_radius_textview, seekBar.progress)
122+
}
123+
})
124+
}
125+
}
126+
127+
/**
128+
* Initialize the seekbar slider to adjust the distance that represents the circle border size
129+
*/
130+
private fun initBorderDifferenceSeekbar() {
131+
thick_bordered_circle_border_difference_seekbar?.apply {
132+
max = borderDifferenceUnitSeekbarMax + BORDER_DIFFERENCE_SEEKBAR_DIFFERENCE
133+
incrementProgressBy(BORDER_DIFFERENCE_SEEKBAR_DIFFERENCE)
134+
progress = borderDifferenceUnitSeekbarMax / 2
135+
136+
thick_bordered_circle_border_difference_textview?.text = String.format(getString(
137+
R.string.thick_bordered_circle_border_difference), progress)
138+
139+
this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
140+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
141+
adjustCircleBorderDifference(thick_bordered_circle_border_difference_textview,
142+
seekBar.progress)
143+
}
144+
145+
override fun onStartTrackingTouch(seekBar: SeekBar) {
146+
// Not needed in this example.
147+
}
148+
149+
override fun onStopTrackingTouch(seekBar: SeekBar) {
150+
adjustCircleBorderDifference(thick_bordered_circle_border_difference_textview,
151+
seekBar.progress)
152+
}
153+
})
154+
}
155+
}
156+
157+
/**
158+
* Make the necessary GeoJSON adjustments to account for a new circle radius
159+
*
160+
* @param textView the TextView to update
161+
* @param progress the new progress value to add to the TextView
162+
*/
163+
private fun adjustCircleRadius(textView: TextView, progress: Int) {
164+
adjustSeekBarProgressTextView(R.string.thick_bordered_circle_radius, textView, progress)
165+
currentSetCircleRadius = progress
166+
drawPolygonCircle(lastClickPoint)
167+
drawCircleBorder(lastClickPoint)
168+
}
169+
170+
/**
171+
* Make the necessary GeoJSON adjustments to account for a new circle border size
172+
*
173+
* @param textView the TextView to update
174+
* @param progress the new progress value to add to the TextView
175+
*/
176+
private fun adjustCircleBorderDifference(textView: TextView, progress: Int) {
177+
adjustSeekBarProgressTextView(R.string.thick_bordered_circle_border_difference, textView, progress)
178+
currentSetCircleBorderDifference = progress
179+
drawCircleBorder(lastClickPoint)
180+
}
181+
182+
/**
183+
* Adjust the textView
184+
*
185+
* @param string the string resource to update
186+
* @param textView the TextView to update
187+
* @param progress the new progress value to add to the TextView
188+
*/
189+
private fun adjustSeekBarProgressTextView(string: Int, textView: TextView, progress: Int) {
190+
var newProgress = progress
191+
newProgress /= 1
192+
newProgress *= 1
193+
textView.text = String.format(getString(string), newProgress)
194+
}
195+
196+
/**
197+
* Make necessary camera and GeoJSON adjustments when the map is tapped on.
198+
* @param mapClickLatLng where the map was tapped
199+
*/
200+
override fun onMapClick(mapClickLatLng: LatLng): Boolean {
201+
mapboxMap?.easeCamera(CameraUpdateFactory.newLatLng(mapClickLatLng))
202+
lastClickPoint = Point.fromLngLat(mapClickLatLng.longitude, mapClickLatLng.latitude)
203+
drawPolygonCircle(lastClickPoint)
204+
drawCircleBorder(lastClickPoint)
205+
return true
206+
}
207+
208+
/**
209+
* Update the [FillLayer] based on the GeoJSON retrieved via [getTurfPolygon].
210+
*
211+
* @param newCircleCenter the center coordinate to be used in the Turf calculation.
212+
*/
213+
private fun drawPolygonCircle(newCircleCenter: Point) {
214+
mapboxMap?.getStyle { style ->
215+
// Use Turf to calculate the coordinates for the circular-shaped Polygon
216+
circlePolygonArea = getTurfPolygon(newCircleCenter, currentSetCircleRadius.toDouble())
217+
218+
// Set the source with the circle's GeoJSON
219+
val polygonCircleSource = style.getSourceAs<GeoJsonSource>(CIRCLE_GEOJSON_SOURCE_ID)
220+
polygonCircleSource?.setGeoJson(Polygon.fromOuterInner(
221+
LineString.fromLngLats(TurfMeta.coordAll(circlePolygonArea, false))))
222+
}
223+
}
224+
225+
/**
226+
* Update the [FillLayer] based on the GeoJSON retrieved via [getTurfPolygon].
227+
*
228+
* @param newCircleCenter the center coordinate to be used in the Turf calculation.
229+
*/
230+
private fun drawCircleBorder(newCircleCenter: Point) {
231+
mapboxMap?.getStyle {
232+
// Use Turf to calculate the coordinates for the circular-shaped Polygon's border
233+
val circleBorderPolygon = getTurfPolygon(newCircleCenter,
234+
currentSetCircleRadius.toDouble() - currentSetCircleBorderDifference)
235+
236+
// Set the source with the border's GeoJSON
237+
val circleBorderSource = it.getSourceAs<GeoJsonSource>(CIRCLE_BORDER_GEOJSON_SOURCE_ID)
238+
circleBorderSource?.setGeoJson(Polygon.fromOuterInner(
239+
// Create outer LineString
240+
LineString.fromLngLats(TurfMeta.coordAll(circleBorderPolygon, false)),
241+
// Create inter LineString
242+
LineString.fromLngLats(TurfMeta.coordAll(circlePolygonArea, false))
243+
))
244+
}
245+
}
246+
247+
/**
248+
* Use the Turf library [TurfTransformation.circle] method to
249+
* retrieve a [Polygon].
250+
*
251+
* @param centerPoint a [Point] which the circle will center around
252+
* @param radius the radius of the circle
253+
* @return a [Polygon] which represents the newly created circle
254+
*/
255+
private fun getTurfPolygon(centerPoint: Point, radius: Double): Polygon {
256+
return TurfTransformation.circle(centerPoint, radius, CIRCLE_STEPS, circleRadiusUnits)
257+
}
258+
259+
override fun onStart() {
260+
super.onStart()
261+
mapView?.onStart()
262+
}
263+
264+
public override fun onResume() {
265+
super.onResume()
266+
mapView?.onResume()
267+
}
268+
269+
public override fun onPause() {
270+
super.onPause()
271+
mapView?.onPause()
272+
}
273+
274+
override fun onLowMemory() {
275+
super.onLowMemory()
276+
mapView?.onLowMemory()
277+
}
278+
279+
override fun onStop() {
280+
super.onStop()
281+
mapView?.onStop()
282+
}
283+
284+
override fun onDestroy() {
285+
super.onDestroy()
286+
mapboxMap?.removeOnMapClickListener(this)
287+
mapView?.onDestroy()
288+
}
289+
290+
override fun onSaveInstanceState(outState: Bundle) {
291+
super.onSaveInstanceState(outState)
292+
mapView?.onSaveInstanceState(outState)
293+
}
294+
}

MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/TurfPhysicalCircleActivity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ protected void onStop() {
356356
@Override
357357
protected void onDestroy() {
358358
super.onDestroy();
359+
if (mapboxMap != null) {
360+
mapboxMap.removeOnMapClickListener(this);
361+
}
359362
mapView.onDestroy();
360363
}
361364

0 commit comments

Comments
 (0)