All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.flow.component.map.MapBase Maven / Gradle / Ivy

There is a newer version: 24.5.5
Show newest version
/**
 * Copyright 2000-2023 Vaadin Ltd.
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */
package com.vaadin.flow.component.map;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.map.configuration.AbstractConfigurationObject;
import com.vaadin.flow.component.map.configuration.Configuration;
import com.vaadin.flow.component.map.configuration.Coordinate;
import com.vaadin.flow.component.map.configuration.Extent;
import com.vaadin.flow.component.map.configuration.Feature;
import com.vaadin.flow.component.map.configuration.View;
import com.vaadin.flow.component.map.configuration.layer.VectorLayer;
import com.vaadin.flow.component.map.events.MapFeatureClickEvent;
import com.vaadin.flow.component.map.events.MapClickEvent;
import com.vaadin.flow.component.map.events.MapFeatureDropEvent;
import com.vaadin.flow.component.map.events.MapViewMoveEndEvent;
import com.vaadin.flow.component.map.serialization.MapSerializer;
import com.vaadin.flow.component.shared.HasThemeVariant;
import com.vaadin.flow.internal.StateTree;
import com.vaadin.flow.shared.Registration;
import elemental.json.JsonValue;

import java.beans.PropertyChangeEvent;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

/**
 * Base class for the map component. Contains all base functionality for the map
 * component, but does not provide any defaults. This component should not be
 * used directly, instead use {@link Map}, which also provides some
 * out-of-the-box conveniences such as a pre-configured background layer, and a
 * feature layer.
 */
public abstract class MapBase extends Component
        implements HasSize, HasStyle, HasThemeVariant {
    private final Configuration configuration;
    private final MapSerializer serializer;

    private StateTree.ExecutionRegistration pendingConfigurationSync;

    protected MapBase() {
        this.serializer = new MapSerializer();
        this.configuration = new Configuration();
        this.configuration
                .addPropertyChangeListener(this::configurationPropertyChange);
        registerEventListeners();
    }

    protected Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Gets the {@link View} of the map. The view allows controlling properties
     * of the map's viewport, such as center, zoom level and rotation.
     *
     * @return the map's view
     */
    public View getView() {
        return configuration.getView();
    }

    /**
     * Sets the view of the map. This is only necessary when dealing with map
     * services that use custom coordinate projection, in which case a view with
     * a matching projection needs to be created and used.
     *
     * @param view
     *            the new view
     */
    public void setView(View view) {
        configuration.setView(view);
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        getElement().executeJs("window.Vaadin.Flow.mapConnector.init(this)");
        // Ensure the full configuration is synced when (re-)attaching the
        // component
        configuration.deepMarkAsDirty();
        requestConfigurationSync();
    }

    /**
     * Schedules a configuration sync, if there isn't a scheduled sync already
     */
    private void requestConfigurationSync() {
        if (pendingConfigurationSync != null) {
            return;
        }
        getUI().ifPresent(ui -> pendingConfigurationSync = ui
                .beforeClientResponse(this, context -> {
                    pendingConfigurationSync = null;
                    synchronizeConfiguration();
                }));
    }

    /**
     * Synchronize the map configuration to the client-side, into OpenLayers
     * class instances
     */
    private void synchronizeConfiguration() {
        // Use a linked hash set to prevent object duplicates, but guarantee
        // that the changes are synchronized in the order that they were added
        // to the set
        Set changedObjects = new LinkedHashSet<>();
        configuration.collectChanges(changedObjects::add);

        JsonValue jsonChanges = serializer.toJson(changedObjects);

        this.getElement().executeJs("this.$connector.synchronize($0)",
                jsonChanges);
    }

    private void configurationPropertyChange(PropertyChangeEvent e) {
        this.requestConfigurationSync();
    }

    private void registerEventListeners() {
        // Register an event listener before all the other listeners of the view
        // move end event to update view state to the latest values received
        // from the client
        addViewMoveEndEventListener(event -> {
            double rotation = event.getRotation();
            double zoom = event.getZoom();
            Coordinate center = event.getCenter();
            Extent extent = event.getExtent();
            getView().updateInternalViewState(center, rotation, zoom, extent);
        });
        // Register an event listener before all the other listeners of the
        // feature drop event to update the feature's position
        addFeatureDropListener(event -> {
            double deltaX = event.getCoordinate().getX()
                    - event.getStartCoordinate().getX();
            double deltaY = event.getCoordinate().getY()
                    - event.getStartCoordinate().getY();

            if (event.getFeature() != null
                    && event.getFeature().getGeometry() != null) {
                event.getFeature().getGeometry().translate(deltaX, deltaY);
            }
        });
    }

    /**
     * Adds an event listener for changes to the map's viewport. The event will
     * only be triggered after the user has finished manipulating the viewport,
     * for example after letting go of the mouse button after a mouse drag
     * interaction.
     *
     * @param listener
     * @return a registration object for removing the added listener
     */
    public Registration addViewMoveEndEventListener(
            ComponentEventListener listener) {
        return addListener(MapViewMoveEndEvent.class, listener);
    }

    /**
     * Adds a click listener for the map.
     * 

* Note that the listener will also be invoked when clicking on a * {@link Feature}. Use {@link MapClickEvent#getFeatures()} to distinguish * whether a feature exists at the clicked location. * * @param listener * @return a registration object for removing the added listener */ public Registration addClickEventListener( ComponentEventListener listener) { return addListener(MapClickEvent.class, listener); } /** * Adds a click listener for geographical features. The listener will be * invoked for a click on any feature in the specified layer. For clicks on * overlapping features, the listener will be invoked only for the top-level * feature at that location. * * @param listener * the listener to trigger * @return registration for the listener * @see Feature */ public Registration addFeatureClickListener(VectorLayer layer, ComponentEventListener listener) { return addListener(MapFeatureClickEvent.class, event -> { // Filter events for specified layer if (!Objects.equals(layer, event.getLayer())) return; listener.onComponentEvent(event); }); } /** * Adds a click listener for geographical features. The listener will be * invoked for a click on any feature, in any layer. To listen for feature * clicks in a specific layer, see * {@link #addFeatureClickListener(VectorLayer, ComponentEventListener)}. * For clicks on overlapping features, the listener will be invoked only for * the top-level feature at that location. * * @param listener * the listener to trigger * @return registration for the listener * @see Feature */ public Registration addFeatureClickListener( ComponentEventListener listener) { return addListener(MapFeatureClickEvent.class, listener); } /** * Adds an event listener for when a feature is dropped after a drag * operation. Features can be made draggable by setting * {@link Feature#setDraggable(boolean)}. * * @param listener * the listener to trigger * @return registration for the listener * @see Feature */ public Registration addFeatureDropListener( ComponentEventListener listener) { return addListener(MapFeatureDropEvent.class, listener); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy