/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium. * * The program is available in open source according to the GNU Affero * General Public License. All contributions in this program are covered * by the Geomajas Contributors License Agreement. For full licensing * details, see LICENSE.txt in the project root. */ package org.geomajas.plugin.editing.client.merge; import com.google.gwt.core.client.Callback; import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.event.shared.SimpleEventBus; import org.geomajas.annotation.Api; import org.geomajas.command.dto.UnionInfo; import org.geomajas.geometry.Geometry; import org.geomajas.gwt2.client.service.GeometryOperationService; import org.geomajas.gwt2.client.service.GeometryOperationServiceImpl; import org.geomajas.plugin.editing.client.GeometryFunction; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeAddedEvent; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeAddedHandler; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeRemovedEvent; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeRemovedHandler; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeStartEvent; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeStartHandler; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeStopEvent; import org.geomajas.plugin.editing.client.merge.event.GeometryMergeStopHandler; import java.util.ArrayList; import java.util.List; /** * Service for the process of merging multiple geometries into a single geometry. * * @author Pieter De Graef * @since 2.0.0 */ @Api(allMethods = true) public class GeometryMergeService { private final List<Geometry> geometries = new ArrayList<Geometry>(); private final EventBus eventBus = new SimpleEventBus(); private boolean busy; private int precision = -1; private boolean usePrecisionAsBuffer; // ------------------------------------------------------------------------ // Constructors: // ------------------------------------------------------------------------ /** * Default constructor. */ public GeometryMergeService() { } // ------------------------------------------------------------------------ // Public methods for adding handlers: // ------------------------------------------------------------------------ /** * Register a {@link GeometryMergeStartHandler} to listen to events that signal the merging process has started. * * @param handler The {@link GeometryMergeStartHandler} to add as listener. * @return The registration of the handler. */ public HandlerRegistration addGeometryMergeStartHandler(GeometryMergeStartHandler handler) { return eventBus.addHandler(GeometryMergeStartHandler.TYPE, handler); } /** * Register a {@link GeometryMergeStopHandler} to listen to events that signal the merging process has ended (either * through stop or cancel). * * @param handler The {@link GeometryMergeStopHandler} to add as listener. * @return The registration of the handler. */ public HandlerRegistration addGeometryMergeStopHandler(GeometryMergeStopHandler handler) { return eventBus.addHandler(GeometryMergeStopHandler.TYPE, handler); } /** * Register a {@link GeometryMergeAddedHandler} to listen to events that signal a geometry has been added to the * list for merging. * * @param handler The {@link GeometryMergeAddedHandler} to add as listener. * @return The registration of the handler. */ public HandlerRegistration addGeometryMergeAddedHandler(GeometryMergeAddedHandler handler) { return eventBus.addHandler(GeometryMergeAddedHandler.TYPE, handler); } /** * Register a {@link GeometryMergeRemovedHandler} to listen to events that signal a geometry has been removed from * the list for merging. * * @param handler The {@link GeometryMergeRemovedHandler} to add as listener. * @return The registration of the handler. */ public HandlerRegistration addGeometryMergeRemovedHandler(GeometryMergeRemovedHandler handler) { return eventBus.addHandler(GeometryMergeRemovedHandler.TYPE, handler); } // ------------------------------------------------------------------------ // Public methods for merging work-flow: // ------------------------------------------------------------------------ /** * Start the merging process. From this point on add some geometries and call either <code>stop</code> or * <code>cancel</code>. * * @throws GeometryMergeException In case a merging process is already started. */ public void start() throws GeometryMergeException { if (busy) { throw new GeometryMergeException("Can't start a new merging process while another one is still busy."); } busy = true; eventBus.fireEvent(new GeometryMergeStartEvent()); } /** * Add a geometry to the list for merging. When <code>stop</code> is called, it is this list that is merged. * * @param geometry The geometry to add. * @throws GeometryMergeException In case the merging process has not been started. */ public void addGeometry(Geometry geometry) throws GeometryMergeException { if (!busy) { throw new GeometryMergeException("Can't add a geometry if no merging process is active."); } geometries.add(geometry); eventBus.fireEvent(new GeometryMergeAddedEvent(geometry)); } /** * Remove a geometry from the merging list again. * * @param geometry The geometry to remove. * @throws GeometryMergeException In case the merging process has not been started. */ public void removeGeometry(Geometry geometry) throws GeometryMergeException { if (!busy) { throw new GeometryMergeException("Can't remove a geometry if no merging process is active."); } geometries.remove(geometry); eventBus.fireEvent(new GeometryMergeRemovedEvent(geometry)); } /** * Clear the entire list of geometries for merging, basically resetting the process. * * @throws GeometryMergeException In case the merging process has not been started. */ public void clearGeometries() throws GeometryMergeException { if (!busy) { throw new GeometryMergeException("Can't clear geometry list if no merging process is active."); } for (Geometry geometry : geometries) { eventBus.fireEvent(new GeometryMergeRemovedEvent(geometry)); } geometries.clear(); } /** * End the merging process by effectively executing the merge operation and returning the result through a * call-back. * * @param callback The call-back function that will receive the merged geometry. * @throws GeometryMergeException Thrown in case the merging process has not been started or some other merging * error. */ public void stop(final GeometryFunction callback) throws GeometryMergeException { if (!busy) { throw new GeometryMergeException("Can't stop the merging process since it is not activated."); } if (callback == null) { cancel(); return; } merge(new GeometryFunction() { public void execute(Geometry geometry) { callback.execute(geometry); try { clearGeometries(); } catch (GeometryMergeException e) { } busy = false; eventBus.fireEvent(new GeometryMergeStopEvent(geometry)); } }); } /** * End the merging process without executing the merge operation. This method will simply clean up. * * @throws GeometryMergeException In case the merging process has not been started. */ public void cancel() throws GeometryMergeException { clearGeometries(); busy = false; eventBus.fireEvent(new GeometryMergeStopEvent(null)); } /** * Is the merging process currently active or not? * * @return Is the merging process currently active or not? */ public boolean isBusy() { return busy; } /** * Get the current precision to be used when merging geometries. * * @return The current precision to be used when merging geometries. */ public int getPrecision() { return precision; } /** * Set the precision to be used when merging geometries. Basically there are 2 options: <ul> <li>-1: Use a floating * point precision model. This is the default value.</li> <li>≥ 0: Use a fixed precision model. Know that larger * values, although increasingly precise, can run into robustness problems.</li> </ul> * * @param precision The new value. */ public void setPrecision(int precision) { this.precision = precision; } /** * Set the boolean that triggers the usage of the precision value as buffer. <p><b>Default</b> = false<p> * <p><b>Note</b>: If false all lines and points are ignored during the merging process.<p> * * @param usePrecisionAsBuffer */ public void setUsePrecisionAsBuffer(boolean usePrecisionAsBuffer) { this.usePrecisionAsBuffer = usePrecisionAsBuffer; } /** * Get the boolean that triggers the usage of the precision value as buffer. * * @return usePrecisionAsBuffer */ public boolean usePrecisionAsBuffer() { return usePrecisionAsBuffer; } // ------------------------------------------------------------------------ // Private methods: // ------------------------------------------------------------------------ private void merge(final GeometryFunction callback) { GeometryOperationService operationService = new GeometryOperationServiceImpl(); UnionInfo unionInfo = new UnionInfo(); unionInfo.setUsePrecisionAsBuffer(true); unionInfo.setPrecision(precision); operationService.union(geometries, unionInfo, new Callback<Geometry, Throwable>() { public void onSuccess(Geometry result) { callback.execute(result); } public void onFailure(Throwable reason) { reason.printStackTrace(); } }); } }