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

org.geomajas.gwt2.client.map.render.MapRendererImpl Maven / Gradle / Ivy

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2013 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.gwt2.client.map.render;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Matrix;
import org.geomajas.gwt.client.map.RenderSpace;
import org.geomajas.gwt2.client.event.LayerAddedEvent;
import org.geomajas.gwt2.client.event.LayerHideEvent;
import org.geomajas.gwt2.client.event.LayerOrderChangedEvent;
import org.geomajas.gwt2.client.event.LayerRefreshedEvent;
import org.geomajas.gwt2.client.event.LayerRemovedEvent;
import org.geomajas.gwt2.client.event.LayerShowEvent;
import org.geomajas.gwt2.client.event.LayerStyleChangedEvent;
import org.geomajas.gwt2.client.event.LayerVisibilityMarkedEvent;
import org.geomajas.gwt2.client.event.MapResizedEvent;
import org.geomajas.gwt2.client.event.ScaleLevelRenderedEvent;
import org.geomajas.gwt2.client.event.ScaleLevelRenderedHandler;
import org.geomajas.gwt2.client.event.ViewPortChangedEvent;
import org.geomajas.gwt2.client.event.ViewPortChangingEvent;
import org.geomajas.gwt2.client.event.ViewPortScaledEvent;
import org.geomajas.gwt2.client.event.ViewPortScalingEvent;
import org.geomajas.gwt2.client.event.ViewPortTranslatedEvent;
import org.geomajas.gwt2.client.event.ViewPortTranslatingEvent;
import org.geomajas.gwt2.client.gfx.HtmlContainer;
import org.geomajas.gwt2.client.gfx.HtmlGroup;
import org.geomajas.gwt2.client.gfx.HtmlObject;
import org.geomajas.gwt2.client.map.MapConfiguration;
import org.geomajas.gwt2.client.map.ViewPort;
import org.geomajas.gwt2.client.map.layer.Layer;
import org.geomajas.gwt2.client.map.layer.LayersModel;
import org.geomajas.gwt2.client.map.layer.RasterServerLayer;
import org.geomajas.gwt2.client.map.layer.VectorServerLayer;

import com.google.gwt.user.client.Timer;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

/**
 * Generic map renderer implementation. Has support for animated navigation through the {@link MapNavigationAnimation}.
 * 
 * @author Pieter De Graef
 */
public class MapRendererImpl implements MapRenderer {

	private final LayersModel layersModel;

	private final ViewPort viewPort;

	private final MapConfiguration configuration;

	private final HtmlContainer htmlContainer;

	private final Map layerRenderers;

	private final MapNavigationAnimation animation;

	private boolean first = true; // No animation the first time the map is rendered.

	// Keeping track of the fetch delay:

	private final FetchTimer fetchTimer;

	private int fetchDelay = 10000; // Delay for fetching scale levels. Results in less requests.

	// Keeping track of the navigation animation:

	private double previousScale;

	private double currentScale;

	private Coordinate previousTranslation;

	private boolean navigationBusy;

	@Inject
	private LayerScalesRendererFactory mapScalesRendererFactory;

	// ------------------------------------------------------------------------
	// Constructor:
	// ------------------------------------------------------------------------

	@Inject
	public MapRendererImpl(@Assisted LayersModel layersModel, @Assisted final ViewPort viewPort,
			@Assisted MapConfiguration configuration, @Assisted HtmlContainer htmlContainer) {
		this.layersModel = layersModel;
		this.viewPort = viewPort;
		this.configuration = configuration;
		this.htmlContainer = htmlContainer;

		this.layerRenderers = new HashMap();
		this.animation = new MapNavigationAnimation(configuration, new LinearNavigationFunction()) {

			protected void onComplete() {
				super.onComplete();
				onNavigationComplete();
			};
		};

		previousScale = viewPort.getScale();
		currentScale = viewPort.getScale();
		previousTranslation = new Coordinate(0, 0);
		fetchTimer = new FetchTimer();
	}

	// ------------------------------------------------------------------------
	// MapCompositionHandler implementation:
	// ------------------------------------------------------------------------

	public void onLayerAdded(LayerAddedEvent event) {
		Layer layer = event.getLayer();
		configuration.setAnimated(event.getLayer(), true);

		HtmlGroup layerContainer = new HtmlGroup(htmlContainer.getWidth(), htmlContainer.getHeight());
		layerContainer.setVisible(layer.isShowing());
		htmlContainer.add(layerContainer);

		LayerRenderer layerRenderer = null;
		if (layer instanceof HasLayerRenderer) {
			layerRenderer = ((HasLayerRenderer) layer).getRenderer(layerContainer);
		} else {
			layerRenderer = mapScalesRendererFactory.create(viewPort, layer, layerContainer);
		}
		if (layerRenderers.size() == 0) {
			layerRenderer.addScaleLevelRenderedHandler(new ScaleLevelRenderedHandler() {

				public void onScaleLevelRendered(ScaleLevelRenderedEvent event) {
					onScaleRendered(event.getScale());
				}
			});
		}
		layerRenderers.put(layer, layerRenderer);
		if (viewPort.isInitialized()) {
			navigateTo(viewPort.getBounds(), viewPort.getScale(), 0);
		}
	}

	public void onLayerRemoved(LayerRemovedEvent event) {
		Layer layer = event.getLayer();
		if (layerRenderers.containsKey(layer)) {
			LayerRenderer layerRenderer = layerRenderers.get(layer);
			layerRenderer.cancel();
			layerRenderer.clear();
			layerRenderers.remove(layer);
			if (event.getIndex() < htmlContainer.getChildCount()) {
				HtmlObject layerContainer = htmlContainer.getChild(event.getIndex());
				if (layerContainer != null) {
					htmlContainer.remove(layerContainer);
				}
			}
		}
	}

	// ------------------------------------------------------------------------
	// LayerOrderChangedHandler implementation:
	// ------------------------------------------------------------------------

	public void onLayerOrderChanged(LayerOrderChangedEvent event) {
		if (event.getFromIndex() < htmlContainer.getChildCount()) {
			int toIndex = event.getToIndex() > htmlContainer.getChildCount() ? htmlContainer.getChildCount() : event
					.getToIndex();
			HtmlObject layerContainer = htmlContainer.getChild(event.getFromIndex());
			if (layerContainer != null) {
				htmlContainer.remove(layerContainer);
				htmlContainer.insert(layerContainer, toIndex);
			}
		}
	}

	// ------------------------------------------------------------------------
	// LayerVisibleHandler implementation:
	// ------------------------------------------------------------------------

	public void onShow(LayerShowEvent event) {
		Layer layer = event.getLayer();
		if (layerRenderers.containsKey(layer)) {
			LayerRenderer layerRenderer = layerRenderers.get(layer);
			layerRenderer.ensureScale(viewPort.getScale(), viewPort.getBounds());
			layerRenderer.getHtmlContainer().setVisible(true);
		}
	}

	public void onHide(LayerHideEvent event) {
		Layer layer = event.getLayer();
		if (layerRenderers.containsKey(layer)) {
			HtmlContainer layerContainer = layerRenderers.get(layer).getHtmlContainer();
			layerContainer.setVisible(false);
		}
	}

	public void onVisibilityMarked(LayerVisibilityMarkedEvent event) {
	}

	// ------------------------------------------------------------------------
	// LayerStyleChangedHandler implementation:
	// ------------------------------------------------------------------------

	public void onLayerStyleChanged(LayerStyleChangedEvent event) {
		Layer layer = event.getLayer();
		if (layer instanceof RasterServerLayer) {
			HtmlContainer layerContainer = layerRenderers.get(layer).getHtmlContainer();
			layerContainer.setOpacity(((RasterServerLayer) layer).getOpacity());
		} else if (layer instanceof VectorServerLayer) {
			// TODO implement me...
		}
	}

	// ------------------------------------------------------------------------
	// LayerRefreshedHandler implementation:
	// ------------------------------------------------------------------------

	public void onLayerRefreshed(LayerRefreshedEvent event) {
		Layer layer = event.getLayer();
		LayerRenderer renderer = layerRenderers.get(layer);
		if (renderer != null) {
			renderer.clear();
			navigateTo(viewPort.getBounds(), viewPort.getScale(), 0);
		}
	}

	// ------------------------------------------------------------------------
	// MapResizedHandler implementation:
	// ------------------------------------------------------------------------

	public void onMapResized(MapResizedEvent event) {
		navigateTo(viewPort.getBounds(), viewPort.getScale(), 0);
	}

	// ------------------------------------------------------------------------
	// ViewPortChangedHandler implementation:
	// ------------------------------------------------------------------------

	public void onViewPortChanged(ViewPortChangedEvent event) {
		if (viewPort.getScale() > 0) {
			if (first) {
				navigateTo(viewPort.getBounds(), viewPort.getScale(), 0);
			} else {
				navigateTo(viewPort.getBounds(), viewPort.getScale(), getAnimationTime());
			}
		}
	}

	public void onViewPortScaled(ViewPortScaledEvent event) {
		if (viewPort.getScale() > 0) {
			navigateTo(viewPort.getBounds(), viewPort.getScale(), getAnimationTime());
		}
	}

	public void onViewPortTranslated(ViewPortTranslatedEvent event) {
		if (viewPort.getScale() > 0 && !animation.isRunning()) {
			navigateTo(viewPort.getBounds(), viewPort.getScale(), 0);
		}
	}

	@Override
	public void onViewPortChanging(ViewPortChangingEvent event) {
		if (viewPort.getScale() > 0) {
			if (first) {
				navigateTo(viewPort.getBounds(), viewPort.getScale(), 0, true);
			} else {
				navigateTo(viewPort.getBounds(), viewPort.getScale(), getAnimationTime(), true);
			}
		}
	}

	@Override
	public void onViewPortScaling(ViewPortScalingEvent event) {
		if (viewPort.getScale() > 0) {
			navigateTo(viewPort.getBounds(), viewPort.getScale(), getAnimationTime(), true);
		}
	}

	@Override
	public void onViewPortTranslating(ViewPortTranslatingEvent event) {
		if (viewPort.getScale() > 0 && !animation.isRunning()) {
			navigateTo(viewPort.getBounds(), viewPort.getScale(), 0, true);
		}
	}

	// ------------------------------------------------------------------------
	// Private methods:
	// ------------------------------------------------------------------------

	protected void onNavigationComplete() {
		navigationBusy = false;

		// Keep track of the
		Matrix translation = viewPort.getTranslationMatrix(RenderSpace.WORLD, RenderSpace.SCREEN);
		previousTranslation = new Coordinate(translation.getDx(), translation.getDy());

		// Bring the current scale to the font and make it visible (for all layers):
		for (int i = 0; i < layersModel.getLayerCount(); i++) {
			Layer layer = layersModel.getLayer(i);
			LayerRenderer presenter = layerRenderers.get(layer);
			if (presenter != null) {
				presenter.bringScaleToFront(currentScale);
				presenter.setScaleVisibility(currentScale, true);
				presenter.applyScaleTranslation(currentScale, previousTranslation);
			}
		}

		onScaleRendered(currentScale);
	}

	// Make the previous scale invisible. Can only be done if the animation has completed.
	protected void onScaleRendered(double scale) {
		if (scale == currentScale && scale != previousScale && !navigationBusy) {
			// Make the previous scale invisible:
			for (int i = 0; i < layersModel.getLayerCount(); i++) {
				Layer layer = layersModel.getLayer(i);
				LayerRenderer presenter = layerRenderers.get(layer);
				if (presenter != null) {
					presenter.setScaleVisibility(previousScale, false);
				}
			}
		}
	}
	
	private void navigateTo(Bbox bounds, double scale, int millis) {
		navigateTo(bounds, scale, millis, false);
	}
	
	private void navigateTo(Bbox bounds, double scale, int millis, boolean animationOnly) {
		navigationBusy = true;
		int delay = fetchDelay >= millis ? 0 : fetchDelay;
		Boolean animationEnabled = (Boolean) configuration.getMapHintValue(MapConfiguration.ANIMATION_ENABLED);
		// if ((Dom.isIE() && !Dom.isSvg()) || !animationEnabled) {
		if (!animationEnabled) {
			delay = 0;
			millis = 0;
		}

		// Calculate the map translation for the requested scale:
		Matrix translation = viewPort.getTranslationMatrix(RenderSpace.WORLD, RenderSpace.SCREEN);
		Coordinate transCoord = new Coordinate(translation.getDx(), translation.getDy());

		// Install a navigation animation:
		if (animation.isRunning()) {
			currentScale = scale;

			// Ensure the scale level after a certain delay:
			if (!animationOnly) {
				ensureScale(scale, bounds, delay);
			}

			// Extend the current animation:
			animation.extend(currentScale / previousScale, transCoord, millis);
		} else {
			// Keep track of navigation details:
			previousScale = currentScale;
			currentScale = scale;

			// Ensure the scale level after a certain delay:
			if (!animationOnly) {
				ensureScale(scale, bounds, delay);
			}

			// Create an ordered list of presenters for the animation.
			List presenters = new ArrayList();
			for (int i = 0; i < layersModel.getLayerCount(); i++) {
				Layer layer = layersModel.getLayer(i);
				LayerRenderer presenter = layerRenderers.get(layer);
				if (presenter != null) {
					presenters.add(presenter);
				}
			}

			// Start a new animation:
			animation.start(presenters, 1, currentScale / previousScale, previousTranslation, transCoord, millis);
		}
	}

	private void ensureScale(double scale, Bbox bounds, int delay) {
		fetchTimer.cancel();
		fetchTimer.setTargetLocation(scale, bounds);
		fetchTimer.schedule(delay);
	}

	private int getAnimationTime() {
		Long millis = (Long) configuration.getMapHintValue(MapConfiguration.ANIMATION_TIME);
		return millis == null ? 0 : millis.intValue();
	}

	/**
	 * Timer that keeps track of fetch delays to fetch requested scales.
	 * 
	 * @author Pieter De Graef
	 */
	private class FetchTimer extends Timer {

		private double scale;

		private Bbox bounds;

		public void schedule(int delayMillis) {
			if (delayMillis == 0) {
				run();
			} else {
				super.schedule(delayMillis);
			}
		}

		public void run() {
			// Ensure the scale at the requested location:
			List presenters = new ArrayList();
			for (int i = 0; i < layersModel.getLayerCount(); i++) {
				Layer layer = layersModel.getLayer(i);
				LayerRenderer presenter = layerRenderers.get(layer);
				if (presenter != null) {
					presenter.ensureScale(scale, bounds);
					if (first) {
						// First time: don't make it invisible. We're not using the animation either.
						presenter.setScaleVisibility(scale, true);
						first = false;
					} else {
						presenter.setScaleVisibility(scale, false);
					}
					presenters.add(presenter); // Create an ordered list of presenters for the animation.
				}
			}
		}

		public void setTargetLocation(double scale, Bbox bounds) {
			this.scale = scale;
			this.bounds = bounds;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy