Skip to content

Commit 5d48344

Browse files
authored
chore(heatmaps): Port heatmaps package to Kotlin (#1573)
* Rename .java to .kt * refactor(library): Convert projection.Point to Kotlin and add tests The `@Deprecated` annotation has been updated to the Kotlin syntax and now includes a `ReplaceWith` expression to provide IDE quick-fix support. Additionally, new unit tests using Google Truth have been added for the `Point` class to verify its construction and inheritance. * refactor(heatmaps): Port Gradient and WeightedLatLng to Kotlin This commit migrates the `Gradient` and `WeightedLatLng` classes and their corresponding tests from Java to idiomatic Kotlin. This conversion is part of the larger effort to modernize the library and prepare it for Kotlin Multiplatform (KMP) compatibility. Key changes include: - `WeightedLatLng` is now a `data class` with a private primary constructor to ensure correct instantiation while providing the benefits of auto-generated methods. The `@ExposedCopyVisibility` annotation has been added to maintain binary compatibility for the `copy()` method. - `Gradient` has been converted to a Kotlin class, using `@JvmOverloads` for constructor compatibility and a `companion object` for its static helper method. - The `internal` visibility of a method in `Gradient` was changed to `public` to ensure visibility to Java classes during the mixed-language phase of the migration. - Unit tests for both classes have been ported to Kotlin, using the Truth assertion framework. * feat(heatmaps): Port HeatmapTileProvider to Kotlin This commit completes the migration of the `heatmaps` package to Kotlin by porting the final and most complex class, `HeatmapTileProvider`. This conversion modernizes the class by introducing several idiomatic Kotlin features and improves its design by refactoring a key data structure. Key changes include: - The `HeatmapTileProvider` and its `Builder` are now written in Kotlin. - The `getMaxValue()` method was refactored to use a `Map` with a type-safe `Vector` key instead of the Android-specific `LongSparseArray`, improving readability and moving closer to KMP compatibility. - A new test suite was created from scratch for the provider, as none existed previously. This improves the code coverage and reliability of the module. - Necessary adjustments were made to the demo application to support the newly ported Kotlin class. * fix(tests): Correct copyright headers in new test files Updates the copyright headers in the test files that were created during the Kotlin porting process. - Corrects the copyright year to 2025. - Corrects the company name from "Google Inc." to "Google LLC". * chore: Add PLACES_API_KEY to local.defaults.properties * fix(heatmaps): Correct copyright headers Updates the copyright headers in the `heatmaps` package source and test files. - Corrects the copyright year to 2025. - Corrects the company name from "Google Inc." to "Google LLC". * refactor(heatmaps): Improve API clarity and documentation Addresses feedback to make the heatmap API more intuitive and robust. - Renames `setWeightedData` and `setData` in `HeatmapTileProvider` to `updateData` and `updateLatLngs` respectively. The `update` prefix better communicates that these methods perform expensive operations that rebuild internal state, rather than just setting a property. - The old method names are preserved and marked as `@Deprecated` to provide a smooth migration path for existing users and avoid a breaking change. - Adds a default value of 0.7 to the `opacity` parameter in `Gradient.generateColorMap`. This aligns with the default in `HeatmapTileProvider` and simplifies common use cases. - Annotates `generateColorMap` with `@JvmOverloads` to ensure the new default parameter is exposed correctly to Java clients. - Improves KDoc for all modified methods to be more descriptive, explaining the purpose of the code and the reasoning behind the design choices. * refactor(heatmaps): Use constant for default opacity Updates the `generateColorMap` function to use the `HeatmapTileProvider.DEFAULT_OPACITY` constant for its default opacity value. This removes a hardcoded value, improving maintainability and ensuring the default remains consistent with the provider. The accompanying KDoc has also been corrected.
1 parent 4ada6c9 commit 5d48344

File tree

15 files changed

+1029
-1246
lines changed

15 files changed

+1029
-1246
lines changed

demo/src/main/java/com/google/maps/android/utils/demo/BaseDemoActivity.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.google.android.gms.maps.OnMapReadyCallback;
2929
import com.google.android.gms.maps.SupportMapFragment;
3030

31+
import java.util.Objects;
32+
3133
public abstract class BaseDemoActivity extends FragmentActivity implements OnMapReadyCallback {
3234
private GoogleMap mMap;
3335
private boolean mIsRestore;
@@ -60,7 +62,7 @@ public void onMapReady(@NonNull GoogleMap map) {
6062
}
6163

6264
private void setUpMap() {
63-
((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMapAsync(this);
65+
((SupportMapFragment) Objects.requireNonNull(getSupportFragmentManager().findFragmentById(R.id.map))).getMapAsync(this);
6466
}
6567

6668
/**

demo/src/main/java/com/google/maps/android/utils/demo/HeatmapsPlacesDemoActivity.java

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.content.Context;
2020
import android.graphics.Color;
2121
import android.os.AsyncTask;
22+
import android.os.Bundle;
2223
import android.util.Log;
2324
import android.view.View;
2425
import android.view.inputmethod.EditorInfo;
@@ -63,6 +64,9 @@ public class HeatmapsPlacesDemoActivity extends BaseDemoActivity {
6364
private final String TAG = "HeatmapPlacesDemo";
6465

6566
private final LatLng SYDNEY = new LatLng(-33.873651, 151.2058896);
67+
private final LatLng BOULDER = new LatLng(40.0216437819216, -105.25471683073081);
68+
69+
private final LatLng FOCUS = BOULDER;
6670

6771
/**
6872
* The base URL for the radar search request.
@@ -78,27 +82,28 @@ public class HeatmapsPlacesDemoActivity extends BaseDemoActivity {
7882
/**
7983
* Places API server key.
8084
*/
81-
private static final String API_KEY = "YOUR_KEY_HERE"; // TODO place your own here!
85+
private static final String API_KEY = BuildConfig.PLACES_API_KEY;
8286

8387
/**
8488
* The colors to be used for the different heatmap layers.
8589
*/
8690
private static final int[] HEATMAP_COLORS = {
87-
HeatmapColors.RED.color,
88-
HeatmapColors.BLUE.color,
89-
HeatmapColors.GREEN.color,
90-
HeatmapColors.PINK.color,
91-
HeatmapColors.GREY.color
91+
HeatmapColors.RED.color,
92+
HeatmapColors.BLUE.color,
93+
HeatmapColors.GREEN.color,
94+
HeatmapColors.PINK.color,
95+
HeatmapColors.GREY.color
9296
};
9397

9498
public enum HeatmapColors {
95-
RED (Color.rgb(238, 44, 44)),
96-
BLUE (Color.rgb(60, 80, 255)),
97-
GREEN (Color.rgb(20, 170, 50)),
98-
PINK (Color.rgb(255, 80, 255)),
99-
GREY (Color.rgb(100, 100, 100));
99+
RED(Color.rgb(238, 44, 44)),
100+
BLUE(Color.rgb(60, 80, 255)),
101+
GREEN(Color.rgb(20, 170, 50)),
102+
PINK(Color.rgb(255, 80, 255)),
103+
GREY(Color.rgb(100, 100, 100));
100104

101105
private final int color;
106+
102107
HeatmapColors(int color) {
103108
this.color = color;
104109
}
@@ -115,7 +120,7 @@ public enum HeatmapColors {
115120
/**
116121
* Stores the TileOverlay corresponding to each of the keywords that have been searched for.
117122
*/
118-
private Hashtable<String, TileOverlay> mOverlays = new Hashtable<String, TileOverlay>();
123+
private final Hashtable<String, TileOverlay> mOverlays = new Hashtable<String, TileOverlay>();
119124

120125
/**
121126
* A layout containing checkboxes for each of the heatmaps rendered.
@@ -132,6 +137,19 @@ public enum HeatmapColors {
132137
*/
133138
private int mOverlaysInput = 0;
134139

140+
@Override
141+
public void onCreate(Bundle savedInstanceState) {
142+
super.onCreate(savedInstanceState);
143+
if ("YOUR_API_KEY".equals(API_KEY)) {
144+
Toast.makeText(
145+
this,
146+
"Please sign up for a Places API key and add it to HeatmapsPlacesDemoActivity.API_KEY",
147+
Toast.LENGTH_LONG
148+
).show();
149+
finish();
150+
}
151+
}
152+
135153
@Override
136154
protected int getLayoutId() {
137155
return R.layout.places_demo;
@@ -152,11 +170,11 @@ protected void startDemo(boolean isRestore) {
152170
mCheckboxLayout = findViewById(R.id.checkboxes);
153171
GoogleMap map = getMap();
154172
if (!isRestore) {
155-
map.moveCamera(CameraUpdateFactory.newLatLngZoom(SYDNEY, 11));
173+
map.moveCamera(CameraUpdateFactory.newLatLngZoom(FOCUS, 11));
156174
}
157-
// Add a circle around Sydney to roughly encompass the results
175+
// Add a circle around FOCUS to roughly encompass the results
158176
map.addCircle(new CircleOptions()
159-
.center(SYDNEY)
177+
.center(FOCUS)
160178
.radius(SEARCH_RADIUS * 1.2)
161179
.strokeColor(Color.RED)
162180
.strokeWidth(4));
@@ -167,18 +185,13 @@ protected void startDemo(boolean isRestore) {
167185
* Called when a search query is submitted
168186
*/
169187
public void submit(View view) {
170-
if ("YOUR_KEY_HERE".equals(API_KEY)) {
171-
Toast.makeText(this, "Please sign up for a Places API key and add it to HeatmapsPlacesDemoActivity.API_KEY",
172-
Toast.LENGTH_LONG).show();
173-
return;
174-
}
175188
EditText editText = findViewById(R.id.input_text);
176189
String keyword = editText.getText().toString();
177190
if (mOverlays.contains(keyword)) {
178191
Toast.makeText(this, "This keyword has already been inputted :(", Toast.LENGTH_SHORT).show();
179192
} else if (mOverlaysRendered == MAX_CHECKBOXES) {
180193
Toast.makeText(this, "You can only input " + MAX_CHECKBOXES + " keywords. :(", Toast.LENGTH_SHORT).show();
181-
} else if (keyword.length() != 0) {
194+
} else if (!keyword.isEmpty()) {
182195
mOverlaysInput++;
183196
ProgressBar progressBar = findViewById(R.id.progress_bar);
184197
progressBar.setVisibility(View.VISIBLE);
@@ -202,11 +215,11 @@ public void submit(View view) {
202215
private Collection<LatLng> getPoints(String keyword) {
203216
HashMap<String, LatLng> results = new HashMap<>();
204217

205-
// Calculate four equidistant points around Sydney to use as search centers
218+
// Calculate four equidistant points around FOCUS to use as search centers
206219
// so that four searches can be done.
207220
ArrayList<LatLng> searchCenters = new ArrayList<>(4);
208221
for (int heading = 45; heading < 360; heading += 90) {
209-
searchCenters.add(SphericalUtil.computeOffset(SYDNEY, SEARCH_RADIUS / 2, heading));
222+
searchCenters.add(SphericalUtil.computeOffset(FOCUS, (double) SEARCH_RADIUS / 2, heading));
210223
}
211224

212225
for (int j = 0; j < 4; j++) {

library/src/main/java/com/google/maps/android/heatmaps/Gradient.java

Lines changed: 0 additions & 193 deletions
This file was deleted.

0 commit comments

Comments
 (0)