/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.service.BboxService;
import org.geomajas.gwt2.client.GeomajasImpl;
import org.geomajas.gwt2.client.event.TileLevelRenderedEvent;
import org.geomajas.gwt2.client.event.TileLevelRenderedHandler;
import org.geomajas.gwt2.client.map.View;
import org.geomajas.gwt2.client.map.ViewPort;
import org.geomajas.gwt2.client.map.layer.tile.TileBasedLayer;
import org.geomajas.gwt2.client.map.render.Tile;
import org.geomajas.gwt2.client.map.render.TileCode;
import org.geomajas.gwt2.client.map.render.TileLevelRenderer;
import org.geomajas.gwt2.client.map.render.TileRenderer;
import org.geomajas.gwt2.client.map.render.dom.container.HtmlContainer;
import org.geomajas.gwt2.client.map.render.dom.container.HtmlImageImpl;
import com.google.gwt.core.client.Callback;
import com.google.web.bindery.event.shared.HandlerRegistration;
/**
* Definition for a renderer for WMS layers.
*
* @author Pieter De Graef
*/
public class DomTileLevelRenderer implements TileLevelRenderer {
private static Logger logger = Logger.getLogger(DomTileLevelRenderer.class.getName());
private static final int MAX_NR_TILES = 500;
private final HtmlContainer container;
private final TileBasedLayer layer;
private final double resolution;
private final Map<TileCode, Tile> tiles;
private final int tileLevel;
private final ViewPort viewPort;
private final TileRenderer tileRenderer;
private int nrLoadingTiles;
public DomTileLevelRenderer(TileBasedLayer layer, int tileLevel, ViewPort viewPort, HtmlContainer container,
TileRenderer tileRenderer) {
this.layer = layer;
this.tileLevel = tileLevel;
this.viewPort = viewPort;
this.container = container;
this.tileRenderer = tileRenderer;
this.tiles = new HashMap<TileCode, Tile>();
this.resolution = layer.getTileConfiguration().getResolution(tileLevel);
}
// ------------------------------------------------------------------------
// TileLevelRenderer implementation:
// ------------------------------------------------------------------------
@Override
public int getTileLevel() {
return tileLevel;
}
@Override
public void render(View view) {
if (layer.isShowing()) {
List<TileCode> tilesForBounds = getTileCodesForView(view);
for (TileCode tileCode : tilesForBounds) {
if (!tiles.containsKey(tileCode)) {
Tile tile = createTile(tileCode);
// Add the tile to the list and render it:
tiles.put(tileCode, tile);
nrLoadingTiles++;
renderTile(tile, new ImageCounter());
}
}
}
}
@Override
public void cancel() {
nrLoadingTiles = 0;
}
@Override
public boolean isRendered(View view) {
return nrLoadingTiles == 0 && tiles.size() > 0;
}
@Override
public HandlerRegistration addTileLevelRenderedHandler(TileLevelRenderedHandler handler) {
return GeomajasImpl.getInstance().getEventBus().addHandler(TileLevelRenderedHandler.TYPE, handler);
}
// ------------------------------------------------------------------------
// Private methods:
// ------------------------------------------------------------------------
protected Bbox asBounds(View view) {
double deltaScale = view.getResolution() / resolution;
Bbox bounds = viewPort.asBounds(view);
return BboxService.scale(bounds, deltaScale);
}
protected void renderTile(Tile tile, Callback<String, String> callback) {
container.add(new HtmlImageImpl(tile.getUrl(), tile.getBounds(), callback));
}
private Tile createTile(TileCode tileCode) {
Bbox worldBounds = getWorldBounds(tileCode);
Tile tile = new Tile(tileCode, getScreenBounds(worldBounds));
tile.setCode(tileCode);
tile.setUrl(tileRenderer.getUrl(tileCode));
return tile;
}
private Bbox getScreenBounds(Bbox worldBox) {
return new Bbox(Math.round(worldBox.getX() / resolution), -Math.round(worldBox.getMaxY() / resolution),
Math.round(worldBox.getMaxX() / resolution) - Math.round(worldBox.getX() / resolution),
Math.round(worldBox.getMaxY() / resolution) - Math.round(worldBox.getY() / resolution));
}
private Bbox getWorldBounds(TileCode tileCode) {
double worldTileWidth = layer.getTileConfiguration().getTileWidth() * resolution;
double worldTileHeight = layer.getTileConfiguration().getTileHeight() * resolution;
double x = layer.getTileConfiguration().getTileOrigin().getX() + tileCode.getX() * worldTileWidth;
double y = layer.getTileConfiguration().getTileOrigin().getY() + tileCode.getY() * worldTileHeight;
return new Bbox(x, y, worldTileWidth, worldTileHeight);
}
private List<TileCode> getTileCodesForView(View view) {
Bbox bounds = asBounds(view);
// clip to maximum bounds
bounds = BboxService.intersection(bounds, layer.getMaxBounds());
List<TileCode> codes = new ArrayList<TileCode>();
if (bounds.getHeight() == 0 || bounds.getWidth() == 0) {
return codes;
}
double worldTileWidth = layer.getTileConfiguration().getTileWidth() * resolution;
double worldTileHeight = layer.getTileConfiguration().getTileHeight() * resolution;
Coordinate tileOrigin = layer.getTileConfiguration().getTileOrigin();
int ymin = (int) Math.floor((bounds.getY() - tileOrigin.getY()) / worldTileHeight);
int ymax = (int) Math.floor((bounds.getMaxY() - tileOrigin.getY()) / worldTileHeight);
int xmin = (int) Math.floor((bounds.getX() - tileOrigin.getX()) / worldTileWidth);
int xmax = (int) Math.floor((bounds.getMaxX() - tileOrigin.getX()) / worldTileWidth);
if (ymin < 0) {
ymin = 0;
}
if (xmin < 0) {
xmin = 0;
}
if (xmax < 0 || ymax < 0) {
return codes;
}
int count = 0;
for (int x = xmin; x <= xmax; x++) {
for (int y = ymin; y <= ymax; y++) {
codes.add(new TileCode(tileLevel, x, y));
if (count++ > MAX_NR_TILES) {
logger.severe("Too many tiles for level " + tileLevel + " of layer " + layer.getId());
break;
}
}
}
return codes;
}
/**
* Counts the number of images that are still inbound. If all images are effectively rendered, we fire an event.
*
* @author Pieter De Graef
*/
private class ImageCounter implements Callback<String, String> {
// In case of failure, we can't just sit and wait. Instead we consider the resolution level rendered.
public void onFailure(String reason) {
GeomajasImpl
.getInstance()
.getEventBus()
.fireEventFromSource(new TileLevelRenderedEvent(DomTileLevelRenderer.this),
DomTileLevelRenderer.this);
}
public void onSuccess(String result) {
if (nrLoadingTiles > 0) { // A cancel may have reset the number of loading tiles.
nrLoadingTiles--;
if (nrLoadingTiles == 0) {
GeomajasImpl
.getInstance()
.getEventBus()
.fireEventFromSource(new TileLevelRenderedEvent(DomTileLevelRenderer.this),
DomTileLevelRenderer.this);
}
}
}
}
}