Skip to content

Examples and Tutorials

Matt Akbarian edited this page Oct 6, 2025 · 3 revisions

Examples and Tutorials

This comprehensive guide provides runnable code examples for Java Leaflet (JLeaflet), covering both JavaFX and Vaadin implementations.

Table of Contents

  1. Simple Examples
  2. Intermediate Examples
  3. Advanced Examples
  4. Common Patterns
  5. Full Applications

Simple Examples

1.1 Creating a Basic Map

JavaFX Implementation

import io.github.makbn.jlmap.fx.JLMapView;
import io.github.makbn.jlmap.map.JLMapProvider;
import io.github.makbn.jlmap.model.JLLatLng;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class BasicMapJavaFX extends Application {

    @Override
    public void start(Stage stage) {
        // Create a simple map centered on Paris
        JLMapView map = JLMapView.builder()
                .jlMapProvider(JLMapProvider.OSM_MAPNIK.build())
                .startCoordinate(new JLLatLng(48.864716, 2.349014))
                .showZoomController(true)
                .build();

        AnchorPane root = new AnchorPane(map);
        Scene scene = new Scene(root, 800, 600);

        stage.setTitle("Basic Map - JavaFX");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Vaadin Implementation

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import io.github.makbn.jlmap.vaadin.JLMapView;
import io.github.makbn.jlmap.map.JLMapProvider;
import io.github.makbn.jlmap.model.JLLatLng;

@Route("basic-map")
public class BasicMapVaadin extends VerticalLayout {

    public BasicMapVaadin() {
        setSizeFull();

        // Create a simple map centered on Paris
        JLMapView map = JLMapView.builder()
                .jlMapProvider(JLMapProvider.OSM_MAPNIK.build())
                .startCoordinate(new JLLatLng(48.864716, 2.349014))
                .showZoomController(true)
                .build();

        add(map);
        expand(map);
    }
}

In some cases adding map directly to HtmlContainer or com.vaadin.flow.component.html.Main deos not work as expected. In such cases, you can wrap the map in a com.vaadin.flow.component.orderedlayout.* or as an example VerticalLayout!

1.2 Adding Markers

import io.github.makbn.jlmap.model.JLLatLng;
import io.github.makbn.jlmap.model.JLMarker;

// Add a simple marker
JLMarker marker = map.getUiLayer()
        .addMarker(new JLLatLng(48.864716, 2.349014), "Eiffel Tower", true);

// Add a marker without a popup
JLMarker simpleMarker = map.getUiLayer()
        .addMarker(new JLLatLng(48.858844, 2.294351), null, false);

1.3 Drawing Shapes

Circle

import io.github.makbn.jlmap.model.JLLatLng;
import io.github.makbn.jlmap.model.JLCircle;
import io.github.makbn.jlmap.model.JLOptions;
import io.github.makbn.jlmap.model.JLColor;

// Add a circle with custom styling
JLCircle circle = map.getVectorLayer()
        .addCircle(
            new JLLatLng(48.864716, 2.349014),
            5000,  // radius in meters
            JLOptions.builder()
                .color(JLColor.BLUE)
                .fillColor(JLColor.PURPLE)
                .fillOpacity(0.3)
                .weight(2)
                .build()
        );

Polyline

import io.github.makbn.jlmap.model.JLPolyline;

// Create a route between cities
JLLatLng[] route = {
    new JLLatLng(48.864716, 2.349014),  // Paris
    new JLLatLng(52.520008, 13.404954), // Berlin
    new JLLatLng(41.902783, 12.496366)  // Rome
};

JLPolyline polyline = map.getVectorLayer()
        .addPolyline(route, JLOptions.builder()
            .color(JLColor.RED)
            .weight(4)
            .build());

Polygon

import io.github.makbn.jlmap.model.JLPolygon;

// Create a triangle polygon
JLLatLng[][][] triangleVertices = {{
    {
        new JLLatLng(48.864716, 2.349014),
        new JLLatLng(48.874716, 2.339014),
        new JLLatLng(48.854716, 2.339014),
        new JLLatLng(48.864716, 2.349014)  // Close the polygon
    }
}};

JLPolygon polygon = map.getVectorLayer()
        .addPolygon(triangleVertices, JLOptions.builder()
            .color(JLColor.ORANGE)
            .fillColor(JLColor.YELLOW)
            .fillOpacity(0.5)
            .build());

1.4 Adding Popups

import io.github.makbn.jlmap.model.JLPopup;

// Add a standalone popup
JLPopup popup = map.getUiLayer()
        .addPopup(
            new JLLatLng(48.864716, 2.349014),
            "<b>Welcome!</b><br>This is the Eiffel Tower"
        );

// Add a popup to an existing marker
marker.getPopup().setContent("<b>Updated Content</b>");

Intermediate Examples

2.1 Event Handling

Click Events

import io.github.makbn.jlmap.listener.event.ClickEvent;
import io.github.makbn.jlmap.listener.event.MoveEvent;

// Handle marker click events
marker.setOnActionListener((source, event) -> {
    if (event instanceof ClickEvent) {
        System.out.println("Marker clicked at: " + source.getLatLng());
    }
});

// Handle marker drag events
JLMarker draggableMarker = map.getUiLayer()
        .addMarker(new JLLatLng(48.864716, 2.349014), "Drag me", true);

draggableMarker.setOnActionListener((source, event) -> {
    if (event instanceof MoveEvent) {
        System.out.println("Marker moved to: " + source.getLatLng());
    }
});

Map Events

import io.github.makbn.jlmap.listener.OnJLActionListener;
import io.github.makbn.jlmap.JLMap;

// Listen to map events
map.setOnActionListener((JLMap source, event) -> {
    System.out.println("Map event: " + event.action());
});

Multiple Event Types

marker.setOnActionListener((source, event) -> {
    switch (event.action()) {
        case CLICK -> handleClick(source);
        case DOUBLE_CLICK -> handleDoubleClick(source);
        case MOUSE_OVER -> handleMouseOver(source);
        case MOUSE_OUT -> handleMouseOut(source);
        case DRAG_START -> handleDragStart(source);
        case DRAG -> handleDrag(source);
        case DRAG_END -> handleDragEnd(source);
    }
});

2.2 Custom Icons

import io.github.makbn.jlmap.model.JLIcon;
import io.github.makbn.jlmap.model.JLPoint;

// Create a custom icon
JLIcon customIcon = JLIcon.builder()
        .iconUrl("https://cdn-icons-png.flaticon.com/512/3097/3097220.png")
        .iconSize(new JLPoint(64, 64))
        .iconAnchor(new JLPoint(32, 64))
        .popupAnchor(new JLPoint(0, -64))
        .build();

// Apply icon to marker
JLMarker markerWithIcon = map.getUiLayer()
        .addMarker(new JLLatLng(48.864716, 2.349014), "Custom Icon", true);
markerWithIcon.setIcon(customIcon);

Multiple Custom Icons

// Car icon
JLIcon carIcon = JLIcon.builder()
        .iconUrl("https://cdn-icons-png.flaticon.com/512/3097/3097220.png")
        .iconSize(new JLPoint(64, 64))
        .iconAnchor(new JLPoint(32, 32))
        .build();

// Airplane icon
JLIcon airplaneIcon = JLIcon.builder()
        .iconUrl("https://cdn-icons-png.flaticon.com/512/3182/3182857.png")
        .iconSize(new JLPoint(64, 64))
        .iconAnchor(new JLPoint(32, 32))
        .build();

// House icon
JLIcon houseIcon = JLIcon.builder()
        .iconUrl("https://cdn-icons-png.flaticon.com/512/3750/3750400.png")
        .iconSize(new JLPoint(64, 64))
        .iconAnchor(new JLPoint(32, 64))
        .build();

2.3 GeoJSON Loading

From URL

import io.github.makbn.jlmap.model.JLGeoJson;

// Load GeoJSON from URL
JLGeoJson geoJson = map.getGeoJsonLayer()
        .addFromUrl("https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_outline_5m.json");

// Handle GeoJSON events
geoJson.setOnActionListener((source, event) -> {
    System.out.println("GeoJSON clicked: " + event);
});

From File

import java.io.File;

// Load GeoJSON from local file
File geoJsonFile = new File("path/to/data.geojson");
JLGeoJson geoJson = map.getGeoJsonLayer()
        .addFromFile(geoJsonFile);

With Styling

import io.github.makbn.jlmap.model.JLGeoJsonOptions;

// Load GeoJSON with custom styling
JLGeoJsonOptions options = JLGeoJsonOptions.builder()
        .styleFunction(features -> JLOptions.builder()
            .fill(true)
            .fillColor(JLColor.fromHex((String) features.get(0).get("fill")))
            .fillOpacity((Double) features.get(0).get("fill-opacity"))
            .stroke(true)
            .color(JLColor.fromHex((String) features.get(0).get("stroke")))
            .build())
        .build();

JLGeoJson styledGeoJson = map.getGeoJsonLayer()
        .addFromUrl("https://example.com/data.geojson", options);

With Filtering

// Load GeoJSON with filtering
JLGeoJsonOptions filterOptions = JLGeoJsonOptions.builder()
        .filter(features -> {
            Map<String, Object> properties = features.get(0);
            // Show only features with even IDs
            return ((Integer) properties.get("id")) % 2 == 0;
        })
        .build();

JLGeoJson filteredGeoJson = map.getGeoJsonLayer()
        .addFromFile(geoJsonFile, filterOptions);

2.4 Context Menus

import io.github.makbn.jlmap.element.menu.JLContextMenu;
import io.github.makbn.jlmap.element.menu.JLMenuItem;

// Add context menu to marker
JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();

contextMenu
    .addItem("edit", "Edit Marker", "edit-icon.png")
    .addItem("info", "Show Info", "info-icon.png")
    .addItem("delete", "Delete Marker", "delete-icon.png");

// Handle menu item selections
contextMenu.setOnMenuItemListener(selectedItem -> {
    switch (selectedItem.getId()) {
        case "edit" -> openEditDialog(marker);
        case "info" -> showMarkerInfo(marker);
        case "delete" -> marker.remove();
    }
});

Complex Menu Items

// Create a disabled menu item
JLMenuItem disabledItem = JLMenuItem.builder()
        .id("disabled-action")
        .text("Disabled Action")
        .enabled(false)
        .build();

contextMenu.addItem(disabledItem);

// Create a conditional menu item
JLMenuItem conditionalItem = JLMenuItem.builder()
        .id("admin-action")
        .text("Admin Action")
        .enabled(userIsAdmin)
        .visible(userIsAdmin)
        .build();

contextMenu.addItem(conditionalItem);

Advanced Examples

3.1 Animated Journeys

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AnimatedJourney {

    private JLMapView map;
    private JLMarker animatedMarker;

    public void animateJourney() {
        JLLatLng start = new JLLatLng(48.864716, 2.349014);  // Paris
        JLLatLng end = new JLLatLng(52.520008, 13.404954);   // Berlin

        // Create path points for smooth animation
        JLLatLng[] path = createPath(start, end, 100);

        // Create animated marker
        animatedMarker = map.getUiLayer().addMarker(start, null, false);

        // Animate marker along path
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        AtomicInteger currentStep = new AtomicInteger(0);

        executor.scheduleAtFixedRate(() -> {
            int step = currentStep.getAndIncrement();
            if (step < path.length) {
                animatedMarker.setLatLng(path[step]);
            } else {
                executor.shutdown();
            }
        }, 0, 50, TimeUnit.MILLISECONDS);
    }

    private JLLatLng[] createPath(JLLatLng start, JLLatLng end, int points) {
        JLLatLng[] path = new JLLatLng[points];
        for (int i = 0; i < points; i++) {
            double ratio = (double) i / (points - 1);
            double lat = start.getLat() + (end.getLat() - start.getLat()) * ratio;
            double lng = start.getLng() + (end.getLng() - start.getLng()) * ratio;
            path[i] = new JLLatLng(lat, lng);
        }
        return path;
    }
}

Curved Path Animation

// Create a curved path using Bezier curves
private JLLatLng[] createCurvedPath(JLLatLng start, JLLatLng end, int points) {
    JLLatLng[] path = new JLLatLng[points];

    // Calculate control point for curve
    double midLat = (start.getLat() + end.getLat()) / 2;
    double midLng = (start.getLng() + end.getLng()) / 2;
    double offsetLat = (end.getLng() - start.getLng()) * 0.2;
    double offsetLng = -(end.getLat() - start.getLat()) * 0.2;
    JLLatLng control = new JLLatLng(midLat + offsetLat, midLng + offsetLng);

    // Generate points along quadratic Bezier curve
    for (int i = 0; i < points; i++) {
        double t = (double) i / (points - 1);
        double lat = Math.pow(1 - t, 2) * start.getLat() +
                     2 * (1 - t) * t * control.getLat() +
                     Math.pow(t, 2) * end.getLat();
        double lng = Math.pow(1 - t, 2) * start.getLng() +
                     2 * (1 - t) * t * control.getLng() +
                     Math.pow(t, 2) * end.getLng();
        path[i] = new JLLatLng(lat, lng);
    }

    return path;
}

3.2 Complex Styling

Dynamic Styling Based on Properties

// Style GeoJSON features based on properties
JLGeoJsonOptions dynamicStyle = JLGeoJsonOptions.builder()
        .styleFunction(features -> {
            Map<String, Object> properties = features.get(0);

            // Get color from feature properties
            String color = (String) properties.getOrDefault("color", "#3388ff");
            Double opacity = (Double) properties.getOrDefault("opacity", 0.5);
            Integer weight = (Integer) properties.getOrDefault("weight", 2);

            return JLOptions.builder()
                .color(JLColor.fromHex(color))
                .fillOpacity(opacity)
                .weight(weight)
                .build();
        })
        .build();

Conditional Styling

// Style features based on conditions
JLGeoJsonOptions conditionalStyle = JLGeoJsonOptions.builder()
        .styleFunction(features -> {
            Map<String, Object> props = features.get(0);
            Integer population = (Integer) props.get("population");

            // Different colors based on population
            JLColor color;
            if (population > 1000000) {
                color = JLColor.RED;
            } else if (population > 500000) {
                color = JLColor.ORANGE;
            } else {
                color = JLColor.GREEN;
            }

            return JLOptions.builder()
                .fillColor(color)
                .fillOpacity(0.6)
                .color(JLColor.BLACK)
                .weight(2)
                .build();
        })
        .build();

3.3 Multiple Layers

// Manage multiple overlays
public class LayerManager {
    private Map<String, JLGeoJson> layers = new HashMap<>();
    private JLMapView map;

    public void addLayer(String name, String url) {
        JLGeoJson layer = map.getGeoJsonLayer().addFromUrl(url);
        layers.put(name, layer);
    }

    public void toggleLayer(String name) {
        JLGeoJson layer = layers.get(name);
        if (layer != null) {
            if (layer.isVisible()) {
                layer.remove();
            } else {
                // Re-add layer logic
            }
        }
    }

    public void removeLayer(String name) {
        JLGeoJson layer = layers.remove(name);
        if (layer != null) {
            layer.remove();
        }
    }
}

3.4 Custom Map Providers

import io.github.makbn.jlmap.map.JLMapProvider;
import io.github.makbn.jlmap.model.JLMapOption;

// OpenStreetMap variants
JLMapProvider osmMapnik = JLMapProvider.OSM_MAPNIK.build();
JLMapProvider osmGerman = JLMapProvider.OSM_GERMAN.build();
JLMapProvider osmFrench = JLMapProvider.OSM_FRENCH.build();
JLMapProvider osmHot = JLMapProvider.OSM_HOT.build();
JLMapProvider osmCycle = JLMapProvider.OSM_CYCLE.build();

// Specialized providers
JLMapProvider openTopo = JLMapProvider.OPEN_TOPO.build();
JLMapProvider waterColor = JLMapProvider.WATER_COLOR.build();

// Map providers with API keys
JLMapProvider mapTiler = JLMapProvider.MAP_TILER
    .parameter(new JLMapOption.Parameter("key", "YOUR_API_KEY"))
    .build();

// Apply to map
JLMapView map = JLMapView.builder()
    .jlMapProvider(mapTiler)
    .startCoordinate(new JLLatLng(48.864716, 2.349014))
    .build();

Common Patterns

4.1 Error Handling

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SafeMapOperations {
    private static final Logger log = LoggerFactory.getLogger(SafeMapOperations.class);

    public void safelyAddGeoJson(JLMapView map, String url) {
        try {
            JLGeoJson geoJson = map.getGeoJsonLayer().addFromUrl(url);
            geoJson.setOnActionListener((source, event) -> {
                log.info("GeoJSON event: {}", event);
            });
        } catch (Exception e) {
            log.error("Failed to load GeoJSON from URL: {}", url, e);
            showErrorNotification("Failed to load map data");
        }
    }

    public void safelyAddMarker(JLMapView map, double lat, double lng) {
        try {
            JLMarker marker = map.getUiLayer()
                .addMarker(new JLLatLng(lat, lng), "Location", true);

            marker.setOnActionListener((source, event) -> {
                try {
                    handleMarkerEvent(source, event);
                } catch (Exception e) {
                    log.error("Error handling marker event", e);
                }
            });
        } catch (IllegalArgumentException e) {
            log.error("Invalid coordinates: lat={}, lng={}", lat, lng, e);
            showErrorNotification("Invalid location coordinates");
        }
    }

    private void handleMarkerEvent(JLMarker marker, Event event) {
        // Event handling logic
    }

    private void showErrorNotification(String message) {
        // Platform-specific notification
    }
}

4.2 Async Operations

JavaFX

import javafx.application.Platform;
import javafx.concurrent.Task;

public void loadGeoJsonAsync(JLMapView map, String url) {
    Task<JLGeoJson> loadTask = new Task<>() {
        @Override
        protected JLGeoJson call() throws Exception {
            return map.getGeoJsonLayer().addFromUrl(url);
        }
    };

    loadTask.setOnSucceeded(event -> {
        JLGeoJson geoJson = loadTask.getValue();
        Platform.runLater(() -> {
            // Update UI
            System.out.println("GeoJSON loaded successfully");
        });
    });

    loadTask.setOnFailed(event -> {
        Throwable error = loadTask.getException();
        Platform.runLater(() -> {
            System.err.println("Failed to load GeoJSON: " + error.getMessage());
        });
    });

    new Thread(loadTask).start();
}

Vaadin

import com.vaadin.flow.component.UI;

public void loadGeoJsonAsync(JLMapView map, String url) {
    UI ui = UI.getCurrent();

    CompletableFuture.supplyAsync(() -> {
        return map.getGeoJsonLayer().addFromUrl(url);
    }).thenAccept(geoJson -> {
        ui.access(() -> {
            // Update UI
            Notification.show("GeoJSON loaded successfully");
        });
    }).exceptionally(error -> {
        ui.access(() -> {
            Notification.show("Failed to load GeoJSON: " + error.getMessage());
        });
        return null;
    });
}

4.3 Performance Optimization

Marker Clustering

public class MarkerClusterer {
    private Map<String, List<JLMarker>> clusters = new HashMap<>();

    public void addMarkers(JLMapView map, List<LocationData> locations) {
        // Only show markers in current viewport
        map.getControlLayer().getBounds().whenComplete((bounds, error) -> {
            if (bounds != null) {
                locations.stream()
                    .filter(loc -> isInBounds(loc, bounds))
                    .forEach(loc -> addMarker(map, loc));
            }
        });
    }

    private boolean isInBounds(LocationData location, JLBounds bounds) {
        double lat = location.getLatitude();
        double lng = location.getLongitude();
        return lat >= bounds.getSouthWest().getLat() &&
               lat <= bounds.getNorthEast().getLat() &&
               lng >= bounds.getSouthWest().getLng() &&
               lng <= bounds.getNorthEast().getLng();
    }

    private void addMarker(JLMapView map, LocationData location) {
        map.getUiLayer().addMarker(
            new JLLatLng(location.getLatitude(), location.getLongitude()),
            location.getName(),
            true
        );
    }
}

Lazy Loading

public class LazyMapLoader {
    private boolean dataLoaded = false;

    public void setupLazyLoading(JLMapView map) {
        map.setOnActionListener((source, event) -> {
            if (event.action() == JLAction.MOVE_END && !dataLoaded) {
                loadDataForCurrentView(map);
                dataLoaded = true;
            }
        });
    }

    private void loadDataForCurrentView(JLMapView map) {
        map.getControlLayer().getBounds().whenComplete((bounds, error) -> {
            if (bounds != null) {
                // Load only data within bounds
                loadGeoJsonForBounds(map, bounds);
            }
        });
    }

    private void loadGeoJsonForBounds(JLMapView map, JLBounds bounds) {
        String url = buildUrlWithBounds(bounds);
        map.getGeoJsonLayer().addFromUrl(url);
    }

    private String buildUrlWithBounds(JLBounds bounds) {
        return String.format("https://api.example.com/data?sw=%f,%f&ne=%f,%f",
            bounds.getSouthWest().getLat(), bounds.getSouthWest().getLng(),
            bounds.getNorthEast().getLat(), bounds.getNorthEast().getLng());
    }
}

4.4 Testing Strategies

Unit Testing Map Logic

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;

public class MapServiceTest {

    private MapService mapService;

    @BeforeEach
    public void setUp() {
        mapService = new MapService();
    }

    @Test
    public void testCoordinateValidation() {
        assertTrue(mapService.isValidCoordinate(48.864716, 2.349014));
        assertFalse(mapService.isValidCoordinate(91.0, 0.0)); // Invalid latitude
        assertFalse(mapService.isValidCoordinate(0.0, 181.0)); // Invalid longitude
    }

    @Test
    public void testDistanceCalculation() {
        JLLatLng paris = new JLLatLng(48.864716, 2.349014);
        JLLatLng berlin = new JLLatLng(52.520008, 13.404954);

        double distance = paris.distanceTo(berlin);
        assertTrue(distance > 877000 && distance < 878000); // ~877 km
    }

    @Test
    public void testPathGeneration() {
        JLLatLng start = new JLLatLng(48.864716, 2.349014);
        JLLatLng end = new JLLatLng(52.520008, 13.404954);

        JLLatLng[] path = mapService.createPath(start, end, 10);

        assertEquals(10, path.length);
        assertEquals(start, path[0]);
        assertEquals(end, path[9]);
    }
}

Integration Testing

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

public class MapIntegrationTest {

    @Test
    public void testGeoJsonLoading() {
        JLMapView map = mock(JLMapView.class);
        JLGeoJsonLayer geoJsonLayer = mock(JLGeoJsonLayer.class);

        when(map.getGeoJsonLayer()).thenReturn(geoJsonLayer);

        String testUrl = "https://example.com/test.geojson";
        map.getGeoJsonLayer().addFromUrl(testUrl);

        verify(geoJsonLayer).addFromUrl(testUrl);
    }
}

Full Applications

5.1 JavaFX Desktop Application

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import io.github.makbn.jlmap.fx.JLMapView;
import io.github.makbn.jlmap.map.JLMapProvider;
import io.github.makbn.jlmap.model.*;

public class MapExplorerApp extends Application {

    private JLMapView mapView;
    private ListView<String> markerList;
    private List<JLMarker> markers = new ArrayList<>();

    @Override
    public void start(Stage primaryStage) {
        // Create main layout
        BorderPane root = new BorderPane();

        // Create map
        mapView = JLMapView.builder()
                .jlMapProvider(JLMapProvider.OSM_MAPNIK.build())
                .startCoordinate(new JLLatLng(48.864716, 2.349014))
                .showZoomController(true)
                .build();

        // Create control panel
        VBox controlPanel = createControlPanel();
        controlPanel.setPadding(new Insets(10));
        controlPanel.setSpacing(10);
        controlPanel.setMinWidth(250);

        // Layout
        root.setCenter(mapView);
        root.setRight(controlPanel);

        // Create scene
        Scene scene = new Scene(root, 1200, 800);
        primaryStage.setTitle("Map Explorer - JavaFX");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private VBox createControlPanel() {
        VBox panel = new VBox(10);

        // Add marker button
        Button addMarkerBtn = new Button("Add Marker");
        addMarkerBtn.setMaxWidth(Double.MAX_VALUE);
        addMarkerBtn.setOnAction(e -> addMarkerDialog());

        // Load GeoJSON button
        Button loadGeoJsonBtn = new Button("Load GeoJSON");
        loadGeoJsonBtn.setMaxWidth(Double.MAX_VALUE);
        loadGeoJsonBtn.setOnAction(e -> loadGeoJsonDialog());

        // Marker list
        Label markerLabel = new Label("Markers:");
        markerList = new ListView<>();
        markerList.setPrefHeight(300);
        markerList.setOnMouseClicked(e -> {
            if (e.getClickCount() == 2) {
                int index = markerList.getSelectionModel().getSelectedIndex();
                if (index >= 0 && index < markers.size()) {
                    JLMarker marker = markers.get(index);
                    mapView.getControlLayer().flyTo(marker.getLatLng(), 12);
                }
            }
        });

        // Clear all button
        Button clearBtn = new Button("Clear All");
        clearBtn.setMaxWidth(Double.MAX_VALUE);
        clearBtn.setOnAction(e -> clearAll());

        panel.getChildren().addAll(
            addMarkerBtn,
            loadGeoJsonBtn,
            new Separator(),
            markerLabel,
            markerList,
            clearBtn
        );

        return panel;
    }

    private void addMarkerDialog() {
        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.setTitle("Add Marker");

        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);

        TextField latField = new TextField("48.864716");
        TextField lngField = new TextField("2.349014");
        TextField nameField = new TextField("New Marker");

        grid.add(new Label("Latitude:"), 0, 0);
        grid.add(latField, 1, 0);
        grid.add(new Label("Longitude:"), 0, 1);
        grid.add(lngField, 1, 1);
        grid.add(new Label("Name:"), 0, 2);
        grid.add(nameField, 1, 2);

        dialog.getDialogPane().setContent(grid);
        dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);

        dialog.showAndWait().ifPresent(response -> {
            if (response == ButtonType.OK) {
                try {
                    double lat = Double.parseDouble(latField.getText());
                    double lng = Double.parseDouble(lngField.getText());
                    String name = nameField.getText();

                    JLMarker marker = mapView.getUiLayer()
                        .addMarker(new JLLatLng(lat, lng), name, true);

                    markers.add(marker);
                    markerList.getItems().add(name + " (" + lat + ", " + lng + ")");

                    // Add context menu
                    addContextMenu(marker, name);

                } catch (NumberFormatException e) {
                    showError("Invalid coordinates");
                }
            }
        });
    }

    private void loadGeoJsonDialog() {
        TextInputDialog dialog = new TextInputDialog(
            "https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_outline_5m.json"
        );
        dialog.setTitle("Load GeoJSON");
        dialog.setHeaderText("Enter GeoJSON URL");

        dialog.showAndWait().ifPresent(url -> {
            try {
                JLGeoJson geoJson = mapView.getGeoJsonLayer().addFromUrl(url);
                showInfo("GeoJSON loaded successfully");
            } catch (Exception e) {
                showError("Failed to load GeoJSON: " + e.getMessage());
            }
        });
    }

    private void addContextMenu(JLMarker marker, String name) {
        JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();

        contextMenu
            .addItem("center", "Center on Map")
            .addItem("delete", "Delete Marker");

        contextMenu.setOnMenuItemListener(item -> {
            switch (item.getId()) {
                case "center" -> mapView.getControlLayer().flyTo(marker.getLatLng(), 12);
                case "delete" -> {
                    marker.remove();
                    markers.remove(marker);
                    markerList.getItems().remove(name);
                }
            }
        });
    }

    private void clearAll() {
        markers.forEach(JLMarker::remove);
        markers.clear();
        markerList.getItems().clear();
    }

    private void showError(String message) {
        Alert alert = new Alert(Alert.AlertType.ERROR, message);
        alert.showAndWait();
    }

    private void showInfo(String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION, message);
        alert.showAndWait();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

5.2 Vaadin Web Application

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.router.Route;
import io.github.makbn.jlmap.vaadin.JLMapView;
import io.github.makbn.jlmap.map.JLMapProvider;
import io.github.makbn.jlmap.model.*;

import java.util.ArrayList;
import java.util.List;

@Route("map-explorer")
public class MapExplorerVaadin extends FlexLayout {

    private JLMapView mapView;
    private VerticalLayout markerList;
    private List<JLMarker> markers = new ArrayList<>();

    public MapExplorerVaadin() {
        setSizeFull();
        setFlexDirection(FlexDirection.ROW);

        // Create map
        mapView = JLMapView.builder()
                .jlMapProvider(JLMapProvider.OSM_MAPNIK.build())
                .startCoordinate(new JLLatLng(48.864716, 2.349014))
                .showZoomController(true)
                .build();
        mapView.setSizeFull();

        // Create control panel
        VerticalLayout controlPanel = createControlPanel();
        controlPanel.setWidth("300px");
        controlPanel.getStyle()
            .set("border-left", "1px solid var(--lumo-contrast-10pct)")
            .set("padding", "var(--lumo-space-m)");

        add(mapView, controlPanel);
        expand(mapView);
    }

    private VerticalLayout createControlPanel() {
        VerticalLayout panel = new VerticalLayout();
        panel.setSpacing(true);
        panel.setPadding(false);

        H2 title = new H2("Map Controls");
        title.getStyle().set("margin-top", "0");

        // Add marker button
        Button addMarkerBtn = new Button("Add Marker");
        addMarkerBtn.setWidthFull();
        addMarkerBtn.addClickListener(e -> openAddMarkerDialog());

        // Load GeoJSON button
        Button loadGeoJsonBtn = new Button("Load GeoJSON from URL");
        loadGeoJsonBtn.setWidthFull();
        loadGeoJsonBtn.addClickListener(e -> openLoadGeoJsonDialog());

        // Upload GeoJSON
        MemoryBuffer buffer = new MemoryBuffer();
        Upload uploadGeoJson = new Upload(buffer);
        uploadGeoJson.setAcceptedFileTypes(".geojson", ".json");
        uploadGeoJson.setMaxFiles(1);
        uploadGeoJson.addSucceededListener(event -> {
            try {
                mapView.getGeoJsonLayer().addFromFile(
                    buffer.getInputStream(),
                    event.getFileName()
                );
                Notification.show("GeoJSON uploaded successfully");
            } catch (Exception ex) {
                Notification.show("Error uploading GeoJSON: " + ex.getMessage());
            }
        });

        // Marker list
        H2 markerListTitle = new H2("Markers");
        markerListTitle.getStyle().set("margin-top", "var(--lumo-space-l)");

        markerList = new VerticalLayout();
        markerList.setPadding(false);
        markerList.setSpacing(false);

        // Clear button
        Button clearBtn = new Button("Clear All");
        clearBtn.setWidthFull();
        clearBtn.addClickListener(e -> clearAll());

        panel.add(
            title,
            addMarkerBtn,
            loadGeoJsonBtn,
            uploadGeoJson,
            markerListTitle,
            markerList,
            clearBtn
        );

        return panel;
    }

    private void openAddMarkerDialog() {
        Dialog dialog = new Dialog();
        dialog.setHeaderTitle("Add Marker");

        VerticalLayout content = new VerticalLayout();

        TextField latField = new TextField("Latitude");
        latField.setValue("48.864716");
        latField.setWidthFull();

        TextField lngField = new TextField("Longitude");
        lngField.setValue("2.349014");
        lngField.setWidthFull();

        TextField nameField = new TextField("Name");
        nameField.setValue("New Marker");
        nameField.setWidthFull();

        Button addBtn = new Button("Add", e -> {
            try {
                double lat = Double.parseDouble(latField.getValue());
                double lng = Double.parseDouble(lngField.getValue());
                String name = nameField.getValue();

                addMarker(lat, lng, name);
                dialog.close();

            } catch (NumberFormatException ex) {
                Notification.show("Invalid coordinates");
            }
        });
        addBtn.getStyle().set("margin-top", "var(--lumo-space-m)");

        content.add(latField, lngField, nameField, addBtn);
        dialog.add(content);
        dialog.open();
    }

    private void openLoadGeoJsonDialog() {
        Dialog dialog = new Dialog();
        dialog.setHeaderTitle("Load GeoJSON");

        VerticalLayout content = new VerticalLayout();

        TextField urlField = new TextField("GeoJSON URL");
        urlField.setValue("https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_outline_5m.json");
        urlField.setWidthFull();

        Button loadBtn = new Button("Load", e -> {
            try {
                String url = urlField.getValue();
                JLGeoJson geoJson = mapView.getGeoJsonLayer().addFromUrl(url);

                geoJson.addContextMenu()
                    .addItem("remove", "Remove Layer")
                    .setOnMenuItemListener(item -> {
                        geoJson.remove();
                        Notification.show("GeoJSON layer removed");
                    });

                Notification.show("GeoJSON loaded successfully");
                dialog.close();

            } catch (Exception ex) {
                Notification.show("Error: " + ex.getMessage());
            }
        });

        content.add(urlField, loadBtn);
        dialog.add(content);
        dialog.open();
    }

    private void addMarker(double lat, double lng, String name) {
        JLMarker marker = mapView.getUiLayer()
            .addMarker(new JLLatLng(lat, lng), name, true);

        markers.add(marker);

        // Add to list with click handler
        Button markerBtn = new Button(name + " (" + lat + ", " + lng + ")");
        markerBtn.setWidthFull();
        markerBtn.addClickListener(e ->
            mapView.getControlLayer().flyTo(marker.getLatLng(), 12)
        );
        markerList.add(markerBtn);

        // Add context menu
        JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();
        contextMenu
            .addItem("center", "Center on Map")
            .addItem("delete", "Delete Marker");

        contextMenu.setOnMenuItemListener(item -> {
            switch (item.getId()) {
                case "center" -> mapView.getControlLayer().flyTo(marker.getLatLng(), 12);
                case "delete" -> {
                    marker.remove();
                    markers.remove(marker);
                    markerList.remove(markerBtn);
                    Notification.show("Marker deleted");
                }
            }
        });

        Notification.show("Marker added: " + name);
    }

    private void clearAll() {
        markers.forEach(JLMarker::remove);
        markers.clear();
        markerList.removeAll();
        Notification.show("All markers cleared");
    }
}

Additional Resources

Testing URLs

Here are some useful GeoJSON URLs for testing:

  1. Simple Example: https://pkgstore.datahub.io/examples/geojson-tutorial/example/data/db696b3bf628d9a273ca9907adcea5c9/example.geojson
  2. US Outline: https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_outline_5m.json

Next Steps