/* * 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.gwt2.client.map.render.dom; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; 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.event.TileLevelRenderedEvent; import org.geomajas.gwt2.client.event.TileLevelRenderedHandler; 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.layer.tile.TileBasedLayer; import org.geomajas.gwt2.client.map.render.LayerRenderer; import org.geomajas.gwt2.client.map.render.RenderingInfo; import org.geomajas.gwt2.client.map.render.TileLevelRenderer; 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 com.google.gwt.user.client.ui.IsWidget; /** * Layer renderer implementation for layers that use renderers at fixed scales. * * @author Pieter De Graef */ public abstract class DomTileLevelLayerRenderer implements LayerRenderer { private static Logger logger = Logger.getLogger(DomTileLevelLayerRenderer.class.getName()); private final ViewPort viewPort; private final Layer layer; private final Map<Integer, TileLevelRenderer> tileLevelRenderers; private final Map<Integer, HtmlContainer> tileLevelContainers; private HtmlContainer container; private TileLevelRenderer currentRenderer; private TileLevelRenderer targetRenderer; private int cacheSize = 2; // ------------------------------------------------------------------------ // Constructors: // ------------------------------------------------------------------------ public DomTileLevelLayerRenderer(final ViewPort viewPort, final Layer layer, final MapEventBus eventBus) { this.viewPort = viewPort; this.layer = layer; this.tileLevelRenderers = new HashMap<Integer, TileLevelRenderer>(); this.tileLevelContainers = new HashMap<Integer, HtmlContainer>(); // 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) { // logger.info("rendering position "+renderingInfo.getView().getPosition()); // logger.info("viewport position "+viewPort.getView().getPosition()); // logger.log(Level.INFO, "Rendering " + renderingInfo.getView().getResolution()); 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(); // only preload other levels when the view is non-interactive ! if (!targetView.isInteractive()) { if (renderingInfo.getTrajectory() != null) { targetView = renderingInfo.getTrajectory().getView(1.0); } try { prepareView(container, targetView); } catch (Exception e) { } } // Now render the current view: try { TileLevelRenderer renderer = getRendererForView(renderingInfo.getView()); // renderer.render(targetView); renderTileLevel(renderer, renderingInfo.getView().getResolution()); if (!targetView.isInteractive()) { cleanupCache(); } } catch (Exception e) { logger.log(Level.SEVERE, "could not render view", 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 TileLevelRenderer createNewScaleRenderer(int tileLevel, View view, HtmlContainer container); // ------------------------------------------------------------------------ // Protected & private methods: // ------------------------------------------------------------------------ protected int getResolutionIndex(double resolution) { if (layer instanceof TileBasedLayer) { return ((TileBasedLayer) layer).getTileConfiguration().getResolutionIndex(resolution); } return viewPort.getResolutionIndex(resolution); } protected double getResolution(int tileLevel) { try { if (layer instanceof TileBasedLayer) { return ((TileBasedLayer) layer).getTileConfiguration().getResolution(tileLevel); } return viewPort.getResolution(tileLevel); } catch (IllegalArgumentException iae) { return 0; } } protected int getResolutionCount() { if (layer instanceof TileBasedLayer) { return ((TileBasedLayer) layer).getTileConfiguration().getResolutionCount(); } return viewPort.getResolutionCount(); } protected TileLevelRenderer getRendererForView(View view) throws IllegalStateException { int tileLevel = getResolutionIndex(view.getResolution()); // Do we have a renderer at the tileLevel that is rendered? TileLevelRenderer 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 = getResolutionIndex(targetView.getResolution()); if (tileLevel < getResolutionCount()) { targetRenderer = getOrCreateTileLevelRenderer(tileLevel, targetView); targetRenderer.render(targetView); } } protected TileLevelRenderer 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())); TileLevelRenderer 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 onScaleLevelRendered(TileLevelRenderedEvent event) { TileLevelRenderer renderer = event.getRenderer(); // See if we can replace the current renderer with the one that just rendered: int viewPortTileLevel = 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: if (tileLevelRenderers.containsKey(renderer.getTileLevel())) { renderTileLevel(renderer, viewPort.getResolution()); } } } }); container.insert(tileLevelContainer, 0); tileLevelRenderers.put(tileLevel, renderer); tileLevelContainers.put(tileLevel, tileLevelContainer); return renderer; } public void renderCurrent() { renderTileLevel(currentRenderer, viewPort.getResolution()); } protected void renderTileLevel(TileLevelRenderer renderer, double currentResolution) { // Set the current renderer: currentRenderer = renderer; // Apply the correct transformation on the container: double rendererResolution = getResolution(renderer.getTileLevel()); Matrix transformation = viewPort.getTransformationService().getTranslationMatrix(currentResolution); HtmlContainer tileLevelContainer = tileLevelContainers.get(renderer.getTileLevel()); Coordinate origin = tileLevelContainer.getOrigin(); // logger.info("Applying scale " + rendererResolution / currentResolution + " to current " // + renderer.getTileLevel()); 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) { // logger.info("Setting level " + tileLevel + " to visible"); // 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<Integer, HtmlContainer> containerEntry : tileLevelContainers.entrySet()) { // logger.info(" Setting "+containerEntry.getKey()+" visiblity to "+(tileLevel == // containerEntry.getKey())); 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 (TileLevelRenderer 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); } }