Skip to content

Commit 8a61590

Browse files
committed
Enhance MyTripToCanada with custom journey icons and improve marker animation
Signed-off-by: makbn <[email protected]>
1 parent fafa28d commit 8a61590

File tree

2 files changed

+134
-34
lines changed

2 files changed

+134
-34
lines changed

jlmap-api/src/main/java/io/github/makbn/jlmap/map/JLMapProvider.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@
4747
@Builder(toBuilder = true)
4848
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
4949
public class JLMapProvider implements JLMapProviderInt {
50-
/** Built-in provider for OpenStreetMap standard tiles - no API key required */
50+
/**
51+
* Built-in provider for OpenStreetMap standard tiles - no API key required
52+
*/
5153
public static final JLMapProvider.JLMapProviderBuilder OSM_MAPNIK = new JLMapProvider("OpenStreetMap.Mapnik",
5254
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
5355
"&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
5456
JLProperties.DEFAULT_MAX_ZOOM,
5557
new HashSet<>()).toBuilder();
56-
/** Built-in provider for MapTiler - requires API key parameter */
58+
/**
59+
* Built-in provider for MapTiler - requires API key parameter
60+
*/
5761
public static final JLMapProvider.JLMapProviderBuilder MAP_TILER = new JLMapProvider("MapTiler",
5862
"https://api.maptiler.com/maps/aquarelle/256/{z}/{x}/{y}.png",
5963
"<a href=\"https://www.maptiler.com/copyright/\" target=\"_blank\">&copy; " +
@@ -66,12 +70,19 @@ public class JLMapProvider implements JLMapProviderInt {
6670
* Human-readable name of the map provider (e.g., "OpenStreetMap.Mapnik")
6771
*/
6872
String name;
69-
/** Tile server URL template with {z}, {x}, {y} placeholders for zoom, x, and y coordinates */
73+
public static final JLMapProvider.JLMapProviderBuilder WATER_COLOR = new JLMapProvider("Stamen.WaterColor",
74+
"https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg",
75+
"&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
76+
JLProperties.DEFAULT_MAX_ZOOM,
77+
new HashSet<>()).toBuilder();
78+
/**
79+
* Tile server URL template with {z}, {x}, {y} placeholders for zoom, x, and y coordinates
80+
*/
7081
String url;
71-
/** Attribution text to display on the map (typically copyright and data source information) */
82+
/**
83+
* Attribution text to display on the map (typically copyright and data source information)
84+
*/
7285
String attribution;
73-
/** Maximum zoom level supported by this tile provider */
74-
int maxZoom;
7586

7687
public JLMapProvider(String name, String url, String attribution, int maxZoom, Set<JLMapOption.Parameter> parameters, Set<String> requiredParameter) {
7788
this.name = name;
@@ -85,15 +96,21 @@ public JLMapProvider(String name, String url, String attribution, int maxZoom, S
8596
public JLMapProvider(String name, String url, String attribution, int maxZoom, Set<JLMapOption.Parameter> parameters) {
8697
this(name, url, attribution, maxZoom, parameters, Collections.emptySet());
8798
}
88-
/** Set of optional parameters (e.g., API keys) required by the tile provider */
89-
@Singular
90-
Set<JLMapOption.Parameter> parameters;
99+
/**
100+
* Maximum zoom level supported by this tile provider
101+
*/
102+
int maxZoom;
91103

92104
public static final JLMapProvider.JLMapProviderBuilder OSM_GERMAN = new JLMapProvider("OpenStreetMap.German",
93105
"https://tile.openstreetmap.de/{z}/{x}/{y}.png",
94106
"&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
95107
JLProperties.DEFAULT_MAX_ZOOM,
96108
new HashSet<>()).toBuilder();
109+
/**
110+
* Set of optional parameters (e.g., API keys) required by the tile provider
111+
*/
112+
@Singular
113+
Set<JLMapOption.Parameter> parameters;
97114

98115
public static final JLMapProvider.JLMapProviderBuilder OSM_FRENCH = new JLMapProvider("OpenStreetMap.French",
99116
"https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
@@ -127,7 +144,9 @@ public JLMapProvider(String name, String url, String attribution, int maxZoom, S
127144
"(<a href=\"https://creativecommons.org/licenses/by-sa/3.0/\">CC-BY-SA</a>)",
128145
JLProperties.DEFAULT_MAX_ZOOM,
129146
new HashSet<>()).toBuilder();
130-
/** Set of required parameter names that must be provided for this provider to function */
147+
/**
148+
* Set of required parameter names that must be provided for this provider to function
149+
*/
131150
Set<String> requiredParameter;
132151

133152
/**

jlmap-vaadin-demo/src/main/java/io/github/makbn/vaadin/demo/views/MyTripToCanada.java

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,63 @@
2828
public class MyTripToCanada extends VerticalLayout {
2929
public static final String MAP_API_KEY = "rNGhTaIpQWWH7C6QGKzF";
3030
private static final Logger log = LoggerFactory.getLogger(MyTripToCanada.class);
31+
3132
// Journey coordinates
3233
private final JLLatLng SARI = new JLLatLng(36.5633, 53.0601);
3334
private final JLLatLng TEHRAN = new JLLatLng(35.6892, 51.3890);
3435
private final JLLatLng DOHA = new JLLatLng(25.2854, 51.5310);
3536
private final JLLatLng MONTREAL = new JLLatLng(45.5017, -73.5673);
3637
private final JLLatLng CALGARY = new JLLatLng(51.0447, -114.0719);
38+
39+
// Custom icons for different stages of the journey
40+
private final JLIcon CAR_ICON = JLIcon.builder()
41+
.iconUrl("https://cdn-icons-png.flaticon.com/512/3097/3097220.png")
42+
.iconSize(new JLPoint(64, 64))
43+
.iconAnchor(new JLPoint(24, 24))
44+
.build();
45+
46+
private final JLIcon AIRPLANE_ICON = JLIcon.builder()
47+
.iconUrl("https://cdn-icons-png.flaticon.com/512/3182/3182857.png")
48+
.iconSize(new JLPoint(64, 64))
49+
.shadowAnchor(new JLPoint(26, 26))
50+
.iconAnchor(new JLPoint(24, 24))
51+
.build();
52+
private final JLIcon EAST_AIRPLANE_ICON = JLIcon.builder()
53+
.iconUrl("https://cdn-icons-png.flaticon.com/512/1058/1058318.png")
54+
.iconSize(new JLPoint(64, 64))
55+
.shadowAnchor(new JLPoint(26, 26))
56+
.iconAnchor(new JLPoint(24, 24))
57+
.build();
58+
59+
private final JLIcon RED_AIRPLANE_ICON = JLIcon.builder()
60+
.iconUrl("https://cdn-icons-png.flaticon.com/512/1077/1077903.png")
61+
.iconSize(new JLPoint(64, 64))
62+
.iconAnchor(new JLPoint(24, 24))
63+
.build();
64+
65+
private final JLIcon BRIEFCASE_ICON = JLIcon.builder()
66+
.iconUrl("https://cdn-icons-png.flaticon.com/512/5376/5376980.png")
67+
.iconSize(new JLPoint(64, 64))
68+
.iconAnchor(new JLPoint(24, 24))
69+
.build();
70+
71+
private final JLIcon DOCUMENT_ICON = JLIcon.builder()
72+
.iconUrl("https://cdn-icons-png.flaticon.com/512/3127/3127363.png")
73+
.iconSize(new JLPoint(64, 64))
74+
.iconAnchor(new JLPoint(24, 24))
75+
.build();
76+
private final JLIcon PASSPORT_ICON = JLIcon.builder()
77+
.iconUrl("https://cdn-icons-png.flaticon.com/512/18132/18132911.png")
78+
.iconSize(new JLPoint(64, 64))
79+
.iconAnchor(new JLPoint(24, 24))
80+
.build();
81+
82+
private final JLIcon HOUSE_ICON = JLIcon.builder()
83+
.iconUrl("https://cdn-icons-png.flaticon.com/512/3750/3750400.png")
84+
.iconSize(new JLPoint(64, 64))
85+
.iconAnchor(new JLPoint(24, 48))
86+
.build();
87+
3788
private JLMapView mapView;
3889
private JLMarker currentMarker;
3990
private JLPolyline currentPath;
@@ -46,7 +97,7 @@ public MyTripToCanada() {
4697

4798
// Create the map view
4899
mapView = JLMapView.builder()
49-
.jlMapProvider(JLMapProvider.OSM_FRENCH
100+
.jlMapProvider(JLMapProvider.WATER_COLOR
50101
.parameter(new JLMapOption.Parameter("key", MAP_API_KEY))
51102
.parameter(new JLMapOption.Parameter("initialZoom", "4"))
52103
.build())
@@ -89,52 +140,52 @@ private void startJourney() {
89140
animateSegment(
90141
SARI,
91142
TEHRAN,
92-
"🚗",
143+
CAR_ICON,
93144
"#FF5722",
94-
5000,
145+
3000,
95146
7,
96147
() -> {
97148
// Step 2: Briefcase and passport (1 second)
98149
Notification.show("Arriving in Tehran - Getting ready to fly...", 2000, Notification.Position.BOTTOM_CENTER);
99-
showTransition(TEHRAN, "💼", 1000, () -> {
150+
showTransition(TEHRAN, BRIEFCASE_ICON, 1500, () -> {
100151
// Step 3: Airplane Tehran to Doha (3 seconds)
101152
Notification.show("Flying to Doha...", 2000, Notification.Position.BOTTOM_CENTER);
102153
animateSegment(
103154
TEHRAN,
104155
DOHA,
105-
"✈️",
156+
AIRPLANE_ICON,
106157
"#2196F3",
107-
3000,
158+
4000,
108159
5,
109160
() -> {
110161
// Step 4: Change airplane animation (same position)
111162
Notification.show("Transit in Doha...", 2000, Notification.Position.BOTTOM_CENTER);
112-
showTransition(DOHA, "🛫", 1000, () -> {
163+
showTransition(DOHA, PASSPORT_ICON, 1500, () -> {
113164
// Step 5: Airplane Doha to Montreal (5 seconds)
114165
Notification.show("Flying to Montreal, Canada...", 2000, Notification.Position.BOTTOM_CENTER);
115166
animateSegment(
116167
DOHA,
117168
MONTREAL,
118-
"✈️",
169+
EAST_AIRPLANE_ICON,
119170
"#2196F3",
120171
5000,
121172
3,
122173
() -> {
123174
// Step 6: Paper document (1 second)
124175
Notification.show("Customs in Montreal...", 2000, Notification.Position.BOTTOM_CENTER);
125-
showTransition(MONTREAL, "📄", 1000, () -> {
176+
showTransition(MONTREAL, DOCUMENT_ICON, 1500, () -> {
126177
// Step 7: Red airplane Montreal to Calgary (4 seconds)
127178
Notification.show("Domestic flight to Calgary...", 2000, Notification.Position.BOTTOM_CENTER);
128179
animateSegment(
129180
MONTREAL,
130181
CALGARY,
131-
"🛩️",
182+
RED_AIRPLANE_ICON,
132183
"#E91E63",
133184
4000,
134185
6,
135186
() -> {
136187
// Step 8: House at Calgary
137-
showTransition(CALGARY, "🏠", 2000, () ->
188+
showTransition(CALGARY, HOUSE_ICON, 2000, () ->
138189
Notification.show("🎉 Welcome to Calgary, Canada! Journey Complete!",
139190
5000,
140191
Notification.Position.TOP_CENTER)
@@ -152,18 +203,18 @@ private void startJourney() {
152203
);
153204
}
154205

155-
private void animateSegment(JLLatLng start, JLLatLng end, String emoji, String pathColor,
206+
private void animateSegment(JLLatLng start, JLLatLng end, JLIcon icon, String pathColor,
156207
int duration, int zoomLevel, Runnable onComplete) {
157-
log.info("Animating segment from {} to {} with emoji {}", start, end, emoji);
208+
log.info("Animating segment from {} to {} with icon {}", start, end, icon);
158209

159210
// Remove previous path if exists
160211
if (currentPath != null) {
161212
log.info("Removing previous path");
162213
currentPath.remove();
163214
}
164215

165-
// Create animated path with fewer points for smoother animation
166-
JLLatLng[] pathPoints = createCurvedPath(start, end, 30);
216+
// Create animated path with more points for smoother animation (10x more)
217+
JLLatLng[] pathPoints = createCurvedPath(start, end, 300);
167218
log.info("Created path with {} points", pathPoints.length);
168219

169220
currentPath = mapView.getVectorLayer().addPolyline(
@@ -189,23 +240,26 @@ private void animateSegment(JLLatLng start, JLLatLng end, String emoji, String p
189240

190241
// Animate marker along path
191242
log.info("Starting marker animation");
192-
animateMarkerAlongPath(emoji, pathPoints, duration, onComplete);
243+
UI.getCurrent().push();
244+
animateMarkerAlongPath(icon, pathPoints, duration, onComplete);
193245
}
194246

195-
private void animateMarkerAlongPath(String emoji, JLLatLng[] path, int duration, Runnable onComplete) {
247+
private void animateMarkerAlongPath(JLIcon icon, JLLatLng[] path, int duration, Runnable onComplete) {
196248
UI ui = UI.getCurrent();
197249

198-
// Reduce to reasonable number of animation steps (20 steps max)
199-
int totalSteps = Math.min(20, path.length);
250+
// Increase animation steps to 200 for smoother animation (10x more than before)
251+
int totalSteps = Math.min(200, path.length);
200252
int delayPerStep = duration / totalSteps;
201253

202-
log.info("Animating marker with emoji {} along {} steps, delay per step: {}ms", emoji, totalSteps, delayPerStep);
254+
log.info("Animating marker with icon {} along {} steps, delay per step: {}ms", icon, totalSteps, delayPerStep);
203255

204256
// Create the marker once at starting position
205257
if (currentMarker == null) {
206258
currentMarker = mapView.getUiLayer().addMarker(path[0], null, false);
259+
setMarkerIconDirect(currentMarker, icon);
207260
} else {
208261
currentMarker.setLatLng(path[0]);
262+
setMarkerIconDirect(currentMarker, icon);
209263
}
210264

211265
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@@ -240,16 +294,18 @@ private void animateMarkerAlongPath(String emoji, JLLatLng[] path, int duration,
240294
}, 0, delayPerStep, TimeUnit.MILLISECONDS);
241295
}
242296

243-
private void showTransition(JLLatLng position, String emoji, int duration, Runnable onComplete) {
297+
private void showTransition(JLLatLng position, JLIcon icon, int duration, Runnable onComplete) {
244298
UI ui = UI.getCurrent();
245299

246-
log.info("Showing transition at {} with emoji {}", position, emoji);
300+
log.info("Showing transition at {} with icon {}", position, icon);
247301

248-
// Just update the marker position and text, don't remove
302+
// Just update the marker position and icon, don't remove
249303
if (currentMarker == null) {
250-
currentMarker = mapView.getUiLayer().addMarker(position, emoji, false);
304+
currentMarker = mapView.getUiLayer().addMarker(position, null, false);
305+
setMarkerIconDirect(currentMarker, icon);
251306
} else {
252307
currentMarker.setLatLng(position);
308+
setMarkerIconDirect(currentMarker, icon);
253309
}
254310

255311
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@@ -303,4 +359,29 @@ private void resetJourney() {
303359
// Reset view to starting position
304360
mapView.getControlLayer().flyTo(SARI, 4);
305361
}
362+
363+
/**
364+
* Helper method to set marker icon using direct JavaScript execution
365+
* This bypasses the toString() issue with JLIcon parameter serialization
366+
*/
367+
private void setMarkerIconDirect(JLMarker marker, JLIcon icon) {
368+
String iconScript = String.format("""
369+
var icon = L.icon({
370+
iconUrl: '%s',
371+
iconSize: [%d, %d],
372+
iconAnchor: [%d, %d]
373+
});
374+
this.%s.setIcon(icon);
375+
""",
376+
icon.getIconUrl(),
377+
(int) icon.getIconSize().getX(),
378+
(int) icon.getIconSize().getY(),
379+
(int) icon.getIconAnchor().getX(),
380+
(int) icon.getIconAnchor().getY(),
381+
marker.getJLId()
382+
);
383+
384+
mapView.getElement().executeJs(iconScript);
385+
log.debug("Set icon for marker {} using direct JS", marker.getJLId());
386+
}
306387
}

0 commit comments

Comments
 (0)