Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2000-2025 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
* license.
*/
package com.vaadin.flow.component.map;

import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.map.configuration.controls.AttributionControl;
import com.vaadin.flow.component.map.configuration.controls.ScaleLineControl;
import com.vaadin.flow.component.map.configuration.controls.ZoomControl;
import com.vaadin.flow.router.Route;

@Route("vaadin-map/controls")
public class ControlsPage extends Div {
public ControlsPage() {
Map map = new Map();
add(map);

NativeButton toggleAttributions = new NativeButton(
"Toggle attributions", (e) -> {
AttributionControl control = map.getControls()
.getAttribution();
control.setVisible(!control.isVisible());
});
toggleAttributions.setId("toggle-attributions");

NativeButton toggleZoom = new NativeButton("Toggle zoom", (e) -> {
ZoomControl control = map.getControls().getZoom();
control.setVisible(!control.isVisible());
});
toggleZoom.setId("toggle-zoom");

NativeButton toggleScaleLine = new NativeButton("Toggle scale line",
(e) -> {
ScaleLineControl control = map.getControls().getScaleLine();
control.setVisible(!control.isVisible());
});
toggleScaleLine.setId("toggle-scale-line");

add(new Div(toggleAttributions, toggleZoom, toggleScaleLine));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright 2000-2025 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
* license.
*/
package com.vaadin.flow.components.map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.flow.component.map.testbench.MapElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.testbench.ElementQuery;
import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.AbstractComponentIT;

@TestPath("vaadin-map/controls")
public class ControlsIT extends AbstractComponentIT {
private MapElement map;

@Before
public void init() {
open();
map = $(MapElement.class).waitForFirst();
}

@Test
public void attributions_toggleVisibility() {
// Attributions control should be visible initially
Assert.assertTrue(queryAttributionsControl().exists());

clickElementWithJs("toggle-attributions");

Assert.assertFalse(queryAttributionsControl().exists());

clickElementWithJs("toggle-attributions");

Assert.assertTrue(queryAttributionsControl().exists());
}

@Test
public void zoom_toggleVisibility() {
// Zoom control should be visible initially
Assert.assertTrue(queryZoomControl().exists());

clickElementWithJs("toggle-zoom");

Assert.assertFalse(queryZoomControl().exists());

clickElementWithJs("toggle-zoom");

Assert.assertTrue(queryZoomControl().exists());
}

@Test
public void scaleLine_toggleVisibility() {
// Scale lint should *not* be visible initially
Assert.assertFalse(queryScaleLineControl().exists());

clickElementWithJs("toggle-scale-line");

Assert.assertTrue(queryScaleLineControl().exists());

clickElementWithJs("toggle-scale-line");

Assert.assertFalse(queryScaleLineControl().exists());
}

private ElementQuery<TestBenchElement> queryAttributionsControl() {
return map.$(TestBenchElement.class).withClassName("ol-attribution")
.withClassName("ol-control");
}

private ElementQuery<TestBenchElement> queryZoomControl() {
return map.$(TestBenchElement.class).withClassName("ol-zoom")
.withClassName("ol-control");
}

private ElementQuery<TestBenchElement> queryScaleLineControl() {
return map.$(TestBenchElement.class).withClassName("ol-scale-line");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
package com.vaadin.flow.component.map;

import java.io.Serializable;
import java.util.List;
import java.util.Objects;

Expand All @@ -19,6 +20,10 @@
import com.vaadin.flow.component.map.configuration.Coordinate;
import com.vaadin.flow.component.map.configuration.Feature;
import com.vaadin.flow.component.map.configuration.View;
import com.vaadin.flow.component.map.configuration.controls.AttributionControl;
import com.vaadin.flow.component.map.configuration.controls.Control;
import com.vaadin.flow.component.map.configuration.controls.ScaleLineControl;
import com.vaadin.flow.component.map.configuration.controls.ZoomControl;
import com.vaadin.flow.component.map.configuration.feature.MarkerFeature;
import com.vaadin.flow.component.map.configuration.layer.FeatureLayer;
import com.vaadin.flow.component.map.configuration.layer.ImageLayer;
Expand Down Expand Up @@ -60,6 +65,11 @@
* setting the center, zoom level and rotation. The map's view can be accessed
* through {@link Map#getView()}.
* <p>
* The UI controls of the map (zoom buttons, attribution text, ...) can be
* configured using the respective control instances, accessible through
* {@link Map#getControls()}. By default, only the attribution and zoom controls
* are visible.
* <p>
* The default projection, or coordinate system, for all coordinates passed to,
* or returned from the public API is {@code EPSG:4326}, also referred to as GPS
* coordinates. This is called the user projection. Internally the component
Expand All @@ -80,6 +90,7 @@ public class Map extends MapBase {

private Layer backgroundLayer;
private final FeatureLayer featureLayer;
private final Controls controls = new Controls();

/**
* Sets the projection (or coordinate system) to use for all coordinates.
Expand Down Expand Up @@ -185,6 +196,11 @@ public Map() {
// layers by default. Developers can customize the z-index if they want
// a different rendering order.
featureLayer.setzIndex(100);

// Setup default control instances
getConfiguration().addControl(controls.attributionControl);
getConfiguration().addControl(controls.scaleLineControl);
getConfiguration().addControl(controls.zoomControl);
}

public Configuration getRawConfiguration() {
Expand Down Expand Up @@ -368,4 +384,59 @@ public void zoomToFit(List<Feature> features, int padding, int duration) {
"this.$connector.zoomToFit($0, $1)", featureIds,
options)));
}

/**
* Gets the default controls of the map.
* <p>
* By default, only the attribution and zoom controls are visible. The
* visibility of each control can be toggled using the respective control's
* {@link Control#setVisible} method.
*
* @return the default controls
*/
public Controls getControls() {
return controls;
}

/**
* The default controls available in the map.
*/
public static final class Controls implements Serializable {
private final AttributionControl attributionControl = new AttributionControl();
private final ScaleLineControl scaleLineControl = new ScaleLineControl();
private final ZoomControl zoomControl = new ZoomControl();

private Controls() {
attributionControl.setVisible(true);
scaleLineControl.setVisible(false);
zoomControl.setVisible(true);
}

/**
* Gets the attribution control of the map.
*
* @return the attribution control
*/
public AttributionControl getAttribution() {
return attributionControl;
}

/**
* Gets the scale line control of the map.
*
* @return the scale line control
*/
public ScaleLineControl getScaleLine() {
return scaleLineControl;
}

/**
* Gets the zoom control of the map.
*
* @return the zoom control
*/
public ZoomControl getZoom() {
return zoomControl;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public abstract class AbstractConfigurationObject implements Serializable {

protected final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
this);
private final SerializablePropertyChangeListener childChangeListener = this::notifyChange;

public AbstractConfigurationObject() {
this.id = UUID.randomUUID().toString();
Expand Down Expand Up @@ -165,7 +166,7 @@ protected void addChild(AbstractConfigurationObject configurationObject) {
Objects.requireNonNull(configurationObject,
"Child configuration object must not be null");
children.add(configurationObject);
configurationObject.addPropertyChangeListener(this::notifyChange);
configurationObject.addPropertyChangeListener(childChangeListener);
markAsDirty();
// When adding a sub-hierarchy, we need to make sure that the client
// receives the whole hierarchy. Otherwise objects that have been synced
Expand Down Expand Up @@ -198,7 +199,7 @@ protected void removeChild(
if (configurationObject == null)
return;
children.remove(configurationObject);
configurationObject.removePropertyChangeListener(this::notifyChange);
configurationObject.removePropertyChangeListener(childChangeListener);
markAsDirty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
package com.vaadin.flow.component.map.configuration;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -17,16 +18,21 @@

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.vaadin.flow.component.map.configuration.controls.Control;
import com.vaadin.flow.component.map.configuration.layer.Layer;

/**
* Contains the configuration for the map, such as layers, sources, features.
*/
public class Configuration extends AbstractConfigurationObject {
private final List<Layer> layers = new ArrayList<>();
private final List<Control> controls = new ArrayList<>();
private View view;

private final SerializablePropertyChangeListener controlPropertyChangeListener = this::handleControlPropertyChange;

public Configuration() {
setView(new View());
}
Expand Down Expand Up @@ -95,6 +101,57 @@ public void removeLayer(Layer layer) {
removeChild(layer);
}

/**
* The list of controls added to the map. This returns an immutable list.
*
* @return the list of controls added to the map
*/
@JsonIgnore
public List<Control> getControls() {
return Collections.unmodifiableList(controls);
}

/**
* The list of visible controls added to the map. This returns an immutable
* list.
*
* @return the list of visible controls added to the map
*/
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
public List<Control> getVisibleControls() {
return controls.stream().filter(Control::isVisible).toList();
}

/**
* Adds a control to the map.
*
* @param control
* the control to be added
*/
public void addControl(Control control) {
Objects.requireNonNull(control);

controls.add(control);
addChild(control);
control.addPropertyChangeListener(this.controlPropertyChangeListener);
}

/**
* Removes a control from the map.
*
* @param control
* the control to be removed
*/
public void removeControl(Control control) {
Objects.requireNonNull(control);

controls.remove(control);
removeChild(control);
control.removePropertyChangeListener(
this.controlPropertyChangeListener);
}

/**
* Gets the view of the map. The view gives access to properties like center
* and zoom level of the viewport.
Expand Down Expand Up @@ -154,4 +211,12 @@ public void collectChanges(
Consumer<AbstractConfigurationObject> changeCollector) {
super.collectChanges(changeCollector);
}

private void handleControlPropertyChange(PropertyChangeEvent event) {
// When visibility of a control changes, resync the configuration itself
// to send an updated list of visible controls to the client
if ("visible".equals(event.getPropertyName())) {
markAsDirty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class Constants {
public static final String OL_STYLE_STROKE = "ol/style/Stroke";
public static final String OL_STYLE_STYLE = "ol/style/Style";
public static final String OL_STYLE_TEXT = "ol/style/Text";
// Controls
public static final String OL_CONTROL_ATTRIBUTION = "ol/control/Attribution";
public static final String OL_CONTROL_SCALE_LINE = "ol/control/ScaleLine";
public static final String OL_CONTROL_ZOOM = "ol/control/Zoom";

public static final String OL_MAP = "ol/Map";
public static final String OL_VIEW = "ol/View";
Expand Down
Loading