1
+ /*
2
+ * Copyright 2018 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package com.example.kotlindemos
18
+
19
+ import android.graphics.Color
20
+ import android.graphics.Point
21
+ import android.location.Location
22
+ import android.support.v7.app.AppCompatActivity
23
+ import android.os.Bundle
24
+ import android.view.View
25
+ import android.widget.AdapterView
26
+ import android.widget.ArrayAdapter
27
+ import android.widget.CheckBox
28
+ import android.widget.SeekBar
29
+ import android.widget.Spinner
30
+
31
+ import com.google.android.gms.maps.CameraUpdateFactory
32
+ import com.google.android.gms.maps.GoogleMap
33
+ import com.google.android.gms.maps.OnMapReadyCallback
34
+ import com.google.android.gms.maps.SupportMapFragment
35
+ import com.google.android.gms.maps.model.BitmapDescriptorFactory
36
+ import com.google.android.gms.maps.model.Circle
37
+ import com.google.android.gms.maps.model.CircleOptions
38
+ import com.google.android.gms.maps.model.Dash
39
+ import com.google.android.gms.maps.model.Dot
40
+ import com.google.android.gms.maps.model.Gap
41
+ import com.google.android.gms.maps.model.LatLng
42
+ import com.google.android.gms.maps.model.Marker
43
+ import com.google.android.gms.maps.model.MarkerOptions
44
+ import com.google.android.gms.maps.model.PatternItem
45
+
46
+ import java.util.ArrayList
47
+ import java.util.Arrays
48
+
49
+ /* *
50
+ * This shows how to draw circles on a map.
51
+ */
52
+ class CircleDemoActivity :
53
+ AppCompatActivity (),
54
+ SeekBar .OnSeekBarChangeListener ,
55
+ AdapterView .OnItemSelectedListener ,
56
+ OnMapReadyCallback {
57
+
58
+ private val DEFAULT_RADIUS_METERS = 1000000.0
59
+
60
+ private val MAX_WIDTH_PX = 50
61
+ private val MAX_HUE_DEGREE = 360
62
+
63
+ private val MAX_ALPHA = 255
64
+ private val PATTERN_DASH_LENGTH = 100
65
+ private val PATTERN_GAP_LENGTH = 200
66
+
67
+ private val sydney = LatLng (- 33.87365 , 151.20689 )
68
+
69
+ private val dot = Dot ()
70
+ private val dash = Dash (PATTERN_DASH_LENGTH .toFloat())
71
+ private val gap = Gap (PATTERN_GAP_LENGTH .toFloat())
72
+ private val patternDotted = Arrays .asList(dot, gap)
73
+ private val patternDashed = Arrays .asList(dash, gap)
74
+ private val patternMixed = Arrays .asList(dot, gap, dot, dash, gap)
75
+
76
+ // These are the options for stroke patterns
77
+ private val patterns: List <Pair <Int , List <PatternItem >? >> = listOf (
78
+ Pair (R .string.pattern_solid, null ),
79
+ Pair (R .string.pattern_dashed, patternDashed),
80
+ Pair (R .string.pattern_dotted, patternDotted),
81
+ Pair (R .string.pattern_mixed, patternMixed)
82
+ )
83
+
84
+ private lateinit var map: GoogleMap
85
+
86
+ private val circles = ArrayList <DraggableCircle >(1 )
87
+
88
+ private var fillColorArgb : Int = 0
89
+ private var strokeColorArgb: Int = 0
90
+
91
+ private lateinit var fillHueBar: SeekBar
92
+ private lateinit var fillAlphaBar: SeekBar
93
+ private lateinit var strokeWidthBar: SeekBar
94
+ private lateinit var strokeHueBar: SeekBar
95
+ private lateinit var strokeAlphaBar: SeekBar
96
+ private lateinit var strokePatternSpinner: Spinner
97
+ private lateinit var clickabilityCheckbox: CheckBox
98
+
99
+ /* *
100
+ * This class contains information about a circle, including its markers
101
+ */
102
+ private inner class DraggableCircle (center : LatLng , private var radiusMeters : Double ) {
103
+ private val centerMarker: Marker = map.addMarker(MarkerOptions ().apply {
104
+ position(center)
105
+ draggable(true )
106
+ })
107
+
108
+ private val radiusMarker: Marker = map.addMarker(
109
+ MarkerOptions ().apply {
110
+ position(center.getPointAtDistance(radiusMeters))
111
+ icon(BitmapDescriptorFactory .defaultMarker(BitmapDescriptorFactory .HUE_AZURE ))
112
+ draggable(true )
113
+ })
114
+
115
+ private val circle: Circle = map.addCircle(
116
+ CircleOptions ().apply {
117
+ center(center)
118
+ radius(radiusMeters)
119
+ strokeWidth(strokeWidthBar.progress.toFloat())
120
+ strokeColor(strokeColorArgb)
121
+ fillColor(fillColorArgb)
122
+ clickable(clickabilityCheckbox.isChecked)
123
+ strokePattern(getSelectedPattern(strokePatternSpinner.selectedItemPosition))
124
+ })
125
+
126
+ fun onMarkerMoved (marker : Marker ): Boolean {
127
+ when (marker) {
128
+ centerMarker -> {
129
+ circle.center = marker.position
130
+ radiusMarker.position = marker.position.getPointAtDistance(radiusMeters)
131
+ }
132
+ radiusMarker -> {
133
+ radiusMeters = centerMarker.position.distanceFrom(radiusMarker.position)
134
+ circle.radius = radiusMeters
135
+ }
136
+ else -> return false
137
+ }
138
+ return true
139
+ }
140
+
141
+ fun onStyleChange () {
142
+ with (circle) {
143
+ strokeWidth = strokeWidthBar.progress.toFloat()
144
+ strokeColor = strokeColorArgb
145
+ fillColor = fillColorArgb
146
+ }
147
+ }
148
+
149
+ fun setStrokePattern (pattern : List <PatternItem >? ) {
150
+ circle.strokePattern = pattern
151
+ }
152
+
153
+ fun setClickable (clickable : Boolean ) {
154
+ circle.isClickable = clickable
155
+ }
156
+ }
157
+
158
+ override fun onCreate (savedInstanceState : Bundle ? ) {
159
+ super .onCreate(savedInstanceState)
160
+ setContentView(R .layout.activity_circle_demo)
161
+
162
+ // Set all the SeekBars
163
+ fillHueBar = findViewById<SeekBar >(R .id.fillHueSeekBar).apply {
164
+ max = MAX_HUE_DEGREE
165
+ progress = MAX_HUE_DEGREE / 2
166
+ }
167
+ fillAlphaBar = findViewById<SeekBar >(R .id.fillAlphaSeekBar).apply {
168
+ max = MAX_ALPHA
169
+ progress = MAX_ALPHA / 2
170
+ }
171
+ strokeWidthBar = findViewById<SeekBar >(R .id.strokeWidthSeekBar).apply {
172
+ max = MAX_WIDTH_PX
173
+ progress = MAX_WIDTH_PX / 3
174
+ }
175
+ strokeHueBar = findViewById<SeekBar >(R .id.strokeHueSeekBar).apply {
176
+ max = MAX_HUE_DEGREE
177
+ progress = 0
178
+ }
179
+ strokeAlphaBar = findViewById<SeekBar >(R .id.strokeAlphaSeekBar).apply {
180
+ max = MAX_ALPHA
181
+ progress = MAX_ALPHA
182
+ }
183
+
184
+ strokePatternSpinner = findViewById<Spinner >(R .id.strokePatternSpinner).apply {
185
+ adapter = ArrayAdapter (this @CircleDemoActivity,
186
+ android.R .layout.simple_spinner_item,
187
+ getResourceStrings())
188
+ }
189
+
190
+ clickabilityCheckbox = findViewById(R .id.toggleClickability)
191
+
192
+ val mapFragment = supportFragmentManager.findFragmentById(R .id.map) as SupportMapFragment
193
+ mapFragment.getMapAsync(this )
194
+ }
195
+
196
+ /* * Get all the strings of patterns and return them as Array. */
197
+ private fun getResourceStrings () = (patterns).map { getString(it.first) }.toTypedArray()
198
+
199
+ /* *
200
+ * When the map is ready, move the camera to put the Circle in the middle of the screen,
201
+ * create a circle in Sydney, and set the listeners for the map, circles, and SeekBars.
202
+ */
203
+ override fun onMapReady (googleMap : GoogleMap ? ) {
204
+ map = googleMap ? : return
205
+ // we need to initialise map before creating a circle
206
+ with (map) {
207
+ moveCamera(CameraUpdateFactory .newLatLngZoom(sydney, 4.0f ))
208
+ setContentDescription(getString(R .string.circle_demo_details))
209
+ setOnMapLongClickListener { point ->
210
+ // We know the center, let's place the outline at a point 3/4 along the view.
211
+ val view: View = supportFragmentManager.findFragmentById(R .id.map).view as View
212
+ val radiusLatLng = map.projection.fromScreenLocation(
213
+ Point (view.height * 3 / 4 , view.width * 3 / 4 ))
214
+ // Create the circle.
215
+ val newCircle = DraggableCircle (point, point.distanceFrom(radiusLatLng))
216
+ circles.add(newCircle)
217
+ }
218
+
219
+ setOnMarkerDragListener(object : GoogleMap .OnMarkerDragListener {
220
+ override fun onMarkerDragStart (marker : Marker ) {
221
+ onMarkerMoved(marker)
222
+ }
223
+
224
+ override fun onMarkerDragEnd (marker : Marker ) {
225
+ onMarkerMoved(marker)
226
+ }
227
+
228
+ override fun onMarkerDrag (marker : Marker ) {
229
+ onMarkerMoved(marker)
230
+ }
231
+ })
232
+
233
+ // Flip the red, green and blue components of the circle's stroke color.
234
+ setOnCircleClickListener { c -> c.strokeColor = c.strokeColor xor 0x00ffffff }
235
+ }
236
+
237
+ fillColorArgb = Color .HSVToColor (fillAlphaBar.progress,
238
+ floatArrayOf(fillHueBar.progress.toFloat(), 1f , 1f ))
239
+ strokeColorArgb = Color .HSVToColor (strokeAlphaBar.progress,
240
+ floatArrayOf(strokeHueBar.progress.toFloat(), 1f , 1f ))
241
+
242
+ val circle = DraggableCircle (sydney, DEFAULT_RADIUS_METERS )
243
+ circles.add(circle)
244
+
245
+ // Set listeners for all the SeekBar
246
+ fillHueBar.setOnSeekBarChangeListener(this )
247
+ fillAlphaBar.setOnSeekBarChangeListener(this )
248
+
249
+ strokeWidthBar.setOnSeekBarChangeListener(this )
250
+ strokeHueBar.setOnSeekBarChangeListener(this )
251
+ strokeAlphaBar.setOnSeekBarChangeListener(this )
252
+
253
+ strokePatternSpinner.onItemSelectedListener = this
254
+ }
255
+
256
+ private fun getSelectedPattern (pos : Int ): List <PatternItem >? = patterns[pos].second
257
+
258
+ override fun onItemSelected (parent : AdapterView <* >, view : View , pos : Int , id : Long ) {
259
+ if (parent.id == R .id.strokePatternSpinner) {
260
+ circles.map { it.setStrokePattern(getSelectedPattern(pos)) }
261
+ }
262
+ }
263
+
264
+ override fun onNothingSelected (parent : AdapterView <* >) {
265
+ // Don't do anything here.
266
+ }
267
+
268
+ override fun onStopTrackingTouch (seekBar : SeekBar ) {
269
+ // Don't do anything here.
270
+ }
271
+
272
+ override fun onStartTrackingTouch (seekBar : SeekBar ) {
273
+ // Don't do anything here.
274
+ }
275
+
276
+ override fun onProgressChanged (seekBar : SeekBar , progress : Int , fromUser : Boolean ) {
277
+ // Update the fillColorArgb if the SeekBars for it is changed, otherwise keep the old value
278
+ fillColorArgb = when (seekBar) {
279
+ fillHueBar -> Color .HSVToColor (Color .alpha(fillColorArgb),
280
+ floatArrayOf(progress.toFloat(), 1f , 1f ))
281
+ fillAlphaBar -> Color .argb(progress, Color .red(fillColorArgb),
282
+ Color .green(fillColorArgb), Color .blue(fillColorArgb))
283
+ else -> fillColorArgb
284
+ }
285
+
286
+ // Set the strokeColorArgb if the SeekBars for it is changed, otherwise keep the old value
287
+ strokeColorArgb = when (seekBar) {
288
+ strokeHueBar -> Color .HSVToColor (Color .alpha(strokeColorArgb),
289
+ floatArrayOf(progress.toFloat(), 1f , 1f ))
290
+ strokeAlphaBar -> Color .argb(progress, Color .red(strokeColorArgb),
291
+ Color .green(strokeColorArgb), Color .blue(strokeColorArgb))
292
+ else -> strokeColorArgb
293
+ }
294
+
295
+ circles.map { it.onStyleChange() }
296
+ }
297
+
298
+ private fun onMarkerMoved (marker : Marker ) {
299
+ circles.forEach { if (it.onMarkerMoved(marker)) return }
300
+ }
301
+
302
+ /* * Listener for the Clickable CheckBox, to set if all the circles can be click */
303
+ fun toggleClickability (view : View ) {
304
+ circles.map { it.setClickable((view as CheckBox ).isChecked) }
305
+ }
306
+ }
307
+
308
+ /* *
309
+ * Extension function to find the distance from this to another LatLng object
310
+ */
311
+ private fun LatLng.distanceFrom (other : LatLng ): Double {
312
+ val result = FloatArray (1 )
313
+ Location .distanceBetween(latitude, longitude, other.latitude, other.longitude, result)
314
+ return result[0 ].toDouble()
315
+ }
316
+
317
+ private fun LatLng.getPointAtDistance (distance : Double ): LatLng {
318
+ val radiusOfEarth = 6371009.0
319
+ val radiusAngle = (Math .toDegrees(distance / radiusOfEarth)
320
+ / Math .cos(Math .toRadians(latitude)))
321
+ return LatLng (latitude, longitude + radiusAngle)
322
+ }
0 commit comments