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+ }
0 commit comments