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

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

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2014 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 com.google.gwt.user.client.ui.IsWidget;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Matrix;
import org.geomajas.gwt2.client.event.LayerAddedEvent;
import org.geomajas.gwt2.client.event.LayerHideEvent;
import org.geomajas.gwt2.client.event.LayerRefreshedEvent;
import org.geomajas.gwt2.client.event.LayerRefreshedHandler;
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.LayerStyleChangedHandler;
import org.geomajas.gwt2.client.event.LayerVisibilityHandler;
import org.geomajas.gwt2.client.event.LayerVisibilityMarkedEvent;
import org.geomajas.gwt2.client.event.MapCompositionHandler;
import org.geomajas.gwt2.client.map.MapEventBus;
import org.geomajas.gwt2.client.map.View;
import org.geomajas.gwt2.client.map.ViewPort;
import org.geomajas.gwt2.client.map.layer.Layer;
import org.geomajas.gwt2.client.map.render.dom.container.HtmlContainer;
import org.geomajas.gwt2.client.map.render.dom.container.HtmlGroup;
import org.geomajas.gwt2.client.service.DomService;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Layer renderer implementation for layers that use renderers at fixed scales.
 *
 * @author Pieter De Graef
 */
public abstract class FixedScaleLayerRenderer implements LayerRenderer {

	private final ViewPort viewPort;

	private final Layer layer;

	private final Map tileLevelRenderers;

	private final Map tileLevelContainers;

	private HtmlContainer container;

	private FixedScaleRenderer currentRenderer;

	private FixedScaleRenderer targetRenderer;

	private int cacheSize = 3;

	// ------------------------------------------------------------------------
	// Constructors:
	// ------------------------------------------------------------------------

	public FixedScaleLayerRenderer(final ViewPort viewPort, final Layer layer, final MapEventBus eventBus) {
		this.viewPort = viewPort;
		this.layer = layer;
		this.tileLevelRenderers = new HashMap();
		this.tileLevelContainers = new HashMap();

		// Refresh the contents of this renderer when the layer is refreshed:
		eventBus.addLayerRefreshedHandler(new LayerRefreshedHandler() {

			@Override
			public void onLayerRefreshed(LayerRefreshedEvent event) {
				refresh();
			}
		}, layer);

		eventBus.addMapCompositionHandler(new MapCompositionHandler() {

			@Override
			public void onLayerRemoved(LayerRemovedEvent event) {
				if (event.getLayer() == layer) {
					clear();
				}
			}

			@Override
			public void onLayerAdded(LayerAddedEvent event) {
			}
		});

		// When a layers visibility status changes, the rendering must change accordingly:
		eventBus.addLayerVisibilityHandler(new LayerVisibilityHandler() {

			@Override
			public void onVisibilityMarked(LayerVisibilityMarkedEvent event) {
			}

			@Override
			public void onShow(LayerShowEvent event) {
				if (container != null) {
					container.setVisible(true);
					render(new RenderingInfo(container, viewPort.getView(), null));
				}
			}

			@Override
			public void onHide(LayerHideEvent event) {
				if (container != null) {
					container.setVisible(false);
				}
			}
		}, layer);

		// Refresh the rendering when the style changes:
		eventBus.addLayerStyleChangedHandler(new LayerStyleChangedHandler() {

			@Override
			public void onLayerStyleChanged(LayerStyleChangedEvent event) {
				refresh();
			}
		}, layer);
	}

	// ------------------------------------------------------------------------
	// LayerRenderer implementation:
	// ------------------------------------------------------------------------

	@Override
	public Layer getLayer() {
		return layer;
	}

	@Override
	public void render(RenderingInfo renderingInfo) {
		if (!(renderingInfo.getWidget() instanceof HtmlContainer)) {
			throw new IllegalArgumentException("This implementation requires HtmlContainers to render.");
		}
		if (renderingInfo.getView() == null) {
			throw new IllegalArgumentException("No view is specified.");
		}
		setContainer((HtmlContainer) renderingInfo.getWidget());

		// Prepare the target view. Try to make sure it's rendered when the animation arrives there:
		View targetView = renderingInfo.getView();
		if (renderingInfo.getTrajectory() != null) {
			targetView = renderingInfo.getTrajectory().getView(1.0);
		}
		try {
			prepareView(container, targetView);
		} catch (Exception e) {
		}

		// Now render the current view:
		try {
			FixedScaleRenderer renderer = getRendererForView(renderingInfo.getView());
			renderTileLevel(renderer, renderingInfo.getView().getResolution());
			cleanupCache();
		} catch (Exception e) {
		}
	}

	// ------------------------------------------------------------------------
	// OpacitySupported implementation:
	// ------------------------------------------------------------------------

	public void setOpacity(double opacity) {
		container.setOpacity(opacity);
	}

	public double getOpacity() {
		return container.getOpacity();
	}

	// ------------------------------------------------------------------------
	// Public methods:
	// ------------------------------------------------------------------------

	/**
	 * Create a renderer for a certain fixed tile level.
	 *
	 * @param tileLevel The tile level to create a new renderer for.
	 * @param view      The view that will be initially visible on the tile level.
	 * @param container The container that has been created for the tile renderer to render in.
	 * @return Return the new tile renderer.
	 */
	public abstract FixedScaleRenderer createNewScaleRenderer(int tileLevel, View view, HtmlContainer container);

	// ------------------------------------------------------------------------
	// Protected & private methods:
	// ------------------------------------------------------------------------

	protected FixedScaleRenderer getRendererForView(View view) throws IllegalStateException {
		int tileLevel = viewPort.getResolutionIndex(view.getResolution());

		// Do we have a renderer at the tileLevel that is rendered?
		FixedScaleRenderer renderer = getOrCreateTileLevelRenderer(tileLevel, view);
		if (currentRenderer == null || renderer.isRendered(view)) {
			return renderer;
		}
		return currentRenderer;
	}

	protected void prepareView(IsWidget widget, View targetView) {
		// Given a trajectory, try to fetch the target tiles before rendering.
		int tileLevel = viewPort.getResolutionIndex(targetView.getResolution());
		if (tileLevel < viewPort.getResolutionCount()) {
			targetRenderer = getOrCreateTileLevelRenderer(tileLevel, targetView);
			targetRenderer.render(targetView);
		}
	}

	protected FixedScaleRenderer getOrCreateTileLevelRenderer(int tileLevel, View view) {
		// Can we find it?
		if (tileLevelRenderers.containsKey(tileLevel)) {
			return tileLevelRenderers.get(tileLevel);
		}

		// If we can't find it, we create it:
		HtmlContainer tileLevelContainer = new HtmlGroup();
		tileLevelContainer.asWidget().getElement().setId("TileLevel-" + tileLevel);

		// Set origin:
		Matrix translation = viewPort.getTransformationService().getTranslationMatrix(view);
		tileLevelContainer.setOrigin(new Coordinate(translation.getDx(), translation.getDy()));

		FixedScaleRenderer renderer = createNewScaleRenderer(tileLevel, view, tileLevelContainer);
		if (renderer == null) {
			throw new IllegalStateException("Cannot create a TileLevelRenderer for layer " + layer.getTitle());
		}
		renderer.addTileLevelRenderedHandler(new TileLevelRenderedHandler() {

			@Override
			public void onTileLevelRendered(TileLevelRenderedEvent event) {
				FixedScaleRenderer renderer = event.getRenderer();

				// See if we can replace the current renderer with the one that just rendered:
				int viewPortTileLevel = viewPort.getResolutionIndex(viewPort.getResolution());
				if (renderer.getTileLevel() == viewPortTileLevel) {
					if (!renderer.isRendered(viewPort.getView())) {
						// TODO are we sure about this? Why else did we prepare this view?
						prepareView(tileLevelContainers.get(viewPortTileLevel), viewPort.getView());
					}

					// Render this tile level:
					renderTileLevel(renderer, viewPort.getResolution());
				}
			}
		});
		container.insert(tileLevelContainer, 0);
		tileLevelRenderers.put(tileLevel, renderer);
		tileLevelContainers.put(tileLevel, tileLevelContainer);
		return renderer;
	}

	protected void renderTileLevel(FixedScaleRenderer renderer, double currentResolution) {
		// Set the current renderer:
		currentRenderer = renderer;

		// Apply the correct transformation on the container:
		double rendererResolution = viewPort.getResolution(renderer.getTileLevel());
		Matrix transformation = viewPort.getTransformationService().getTranslationMatrix(currentResolution);
		HtmlContainer tileLevelContainer = tileLevelContainers.get(renderer.getTileLevel());
		Coordinate origin = tileLevelContainer.getOrigin();
		tileLevelContainer.applyScale(rendererResolution / currentResolution, 0, 0);
		double left = transformation.getDx() - origin.getX() * tileLevelContainer.getScale();
		double top = transformation.getDy() - origin.getY() * tileLevelContainer.getScale();
		tileLevelContainer.setLeft((int) Math.round(left));
		tileLevelContainer.setTop((int) Math.round(top));

		// Now make sure it's visible:
		container.bringToFront(tileLevelContainer);
		setVisible(renderer.getTileLevel());
	}

	protected void setContainer(HtmlContainer container) {
		if (this.container == null || this.container != container) {
			this.container = container;
			DomService.applyTransition(this.container.asWidget().getElement(), new String[] { "opacity" },
					new Integer[] { 300 });
		}
	}

	protected void setVisible(int tileLevel) {
		// First set the correct level to visible, so as to make sure the map never gets white:
		tileLevelContainers.get(tileLevel).setVisible(true);

		// Now go over all containers (we could leave out the correct one here...):
		for (Entry containerEntry : tileLevelContainers.entrySet()) {
			containerEntry.getValue().setVisible(tileLevel == containerEntry.getKey());
		}
	}

	protected void refresh() {
		clear();
		render(new RenderingInfo(container, viewPort.getView(), null));
	}

	private void clear() {
		currentRenderer = null;
		tileLevelRenderers.clear();
		tileLevelContainers.clear();
		if (container != null) {
			container.clear();
		}
	}

	private void cleanupCache() {
		while (tileLevelRenderers.size() > cacheSize) {
			int distance = -1;
			int tileLevel = -1;
			for (FixedScaleRenderer renderer : tileLevelRenderers.values()) {
				if (renderer != currentRenderer && renderer != targetRenderer) {
					int d;
					if (targetRenderer != null) {
						d = Math.abs(targetRenderer.getTileLevel() - renderer.getTileLevel());
					} else {
						d = Math.abs(currentRenderer.getTileLevel() - renderer.getTileLevel());
					}
					if (d > distance) {
						distance = d;
						tileLevel = renderer.getTileLevel();
					}
				}
			}
			if (tileLevel < 0) {
				return;
			}
			removeTileLevel(tileLevel);
		}
	}

	private void removeTileLevel(int tileLevel) {
		if (container != null) {
			HtmlContainer toRemove = tileLevelContainers.get(tileLevel);
			container.remove(toRemove);
		}
		tileLevelRenderers.remove(tileLevel);
		tileLevelContainers.remove(tileLevel);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy