/* * 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.gwt.client.map.cache.tile; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.geomajas.command.dto.GetVectorTileRequest; import org.geomajas.command.dto.GetVectorTileResponse; import org.geomajas.gwt.client.command.AbstractCommandCallback; import org.geomajas.gwt.client.command.Deferred; import org.geomajas.gwt.client.command.GwtCommand; import org.geomajas.gwt.client.command.GwtCommandDispatcher; import org.geomajas.gwt.client.gfx.PaintableGroup; import org.geomajas.gwt.client.gfx.PainterVisitor; import org.geomajas.gwt.client.gfx.style.PictureStyle; import org.geomajas.gwt.client.map.MapModel; import org.geomajas.gwt.client.map.MapView; import org.geomajas.gwt.client.map.cache.SpatialCache; import org.geomajas.gwt.client.map.layer.VectorLayer; import org.geomajas.gwt.client.spatial.Bbox; import org.geomajas.gwt.client.util.Dom; import org.geomajas.gwt.client.util.Log; import org.geomajas.layer.tile.TileCode; import org.geomajas.layer.tile.VectorTile.VectorTileContentType; /** * Representation of a vector tile in the GWT client. * * @author Pieter De Graef */ public class VectorTile extends AbstractVectorTile { /** * Data holder: contains SVG or VML from the server for features. */ private ContentHolder featureContent; /** * Data holder: contains SVG or VML from the server for labels. */ private ContentHolder labelContent; private VectorTileContentType contentType; /** dependent tile codes */ private List<TileCode> codes = new ArrayList<TileCode>(); private Deferred deferred; private GetVectorTileRequest lastRequest; private PictureStyle pictureStyle; // ------------------------------------------------------------------------- // Constructors: // ------------------------------------------------------------------------- public VectorTile(TileCode code, Bbox bbox, SpatialCache cache) { super(code, bbox, cache); featureContent = new ContentHolder(code.toString()); labelContent = new ContentHolder(code.toString()); } // ------------------------------------------------------------------------- // Spatial node functions: // ------------------------------------------------------------------------- /** * Fetch all data related to this tile. * * @param filter When fetching it is possible to filter the data with this filter object. Null otherwise. * @param callback When this node's data comes from the server, it will be handled by this callback function. */ public void fetch(final String filter, final TileFunction<VectorTile> callback) { final GetVectorTileRequest request = createRequest(filter); GwtCommand command = new GwtCommand(GetVectorTileRequest.COMMAND); command.setCommandRequest(request); final VectorTile self = this; lastRequest = request; deferred = GwtCommandDispatcher.getInstance().execute(command, new AbstractCommandCallback<GetVectorTileResponse>() { public void execute(GetVectorTileResponse tileResponse) { if (null == deferred || !deferred.isCancelled()) { org.geomajas.layer.tile.VectorTile tile = tileResponse.getTile(); for (TileCode relatedTile : tile.getCodes()) { codes.add(relatedTile); } code = tile.getCode(); contentType = tile.getContentType(); featureContent.setContent(tile.getFeatureContent()); labelContent.setContent(tile.getLabelContent()); try { callback.execute(self); } catch (Throwable t) { Log.logError("VectorTile: error calling the callback after a fetch.", t); } } deferred = null; } }); } /** * Execute a {@link TileFunction} in this tile and all connected tiles. Only tiles which are not yet included in * updatedTiles are processed. Tiles are added in updatedTiles when processed. If these connected tiles are not yet * part of the cache, then they will be fetched before applying the {@link TileFunction} on them. * * @param filter A filter that needs to be used in case connected tile need to be fetched. * @param callback The {@link TileFunction} to execute on the connected tiles. * @param updatedTiles list of already processed tiles to assure tiles are only processed once */ public void applyConnectedOnce(final String filter, final TileFunction<VectorTile> callback, final Map<String, VectorTile> updatedTiles) { apply(filter, new TileFunction<VectorTile>() { public void execute(VectorTile tile) { updatedTiles.put(tile.getCode().toString(), tile); callback.execute(tile); List<TileCode> tileCodes = tile.getCodes(); for (TileCode tileCode : tileCodes) { if (!updatedTiles.containsKey(tileCode.toString())) { VectorTile temp = tile.cache.addTile(tileCode); updatedTiles.put(tileCode.toString(), temp); temp.apply(filter, callback); } } } }); } /** * Execute a TileFunction on this tile. If the tile is not yet loaded, attach it to the isLoaded event. * * @param filter filter which needs to be applied when fetching * @param callback callback to call */ public void apply(final String filter, final TileFunction<VectorTile> callback) { switch (getStatus()) { case EMPTY: fetch(filter, callback); break; case LOADING: if (needsReload(filter)) { deferred.cancel(); fetch(filter, callback); } else { final VectorTile self = this; deferred.addCallback(new AbstractCommandCallback<GetVectorTileResponse>() { public void execute(GetVectorTileResponse response) { callback.execute(self); } }); } break; case LOADED: if (needsReload(filter)) { // Check if the labels need to be fetched as well: fetch(filter, callback); } else { callback.execute(this); } break; default: throw new IllegalStateException("Unknown status " + getStatus()); } } /** * Return the current status of this VectorTile. Can be one of the following: * <ul> * <li>STATUS.EMPTY</li> * <li>STATUS.LOADING</li> * <li>STATUS.LOADED</li> * </ul> * * @return status */ public STATUS getStatus() { if (featureContent.isLoaded()) { return STATUS.LOADED; } if (deferred == null) { return STATUS.EMPTY; } return STATUS.LOADING; } /** * Cancel the fetching of this tile. No callback will be executed anymore. */ public void cancel() { super.cancel(); if (deferred != null) { deferred.cancel(); } } // ------------------------------------------------------------------------- // Some getters and setters: // ------------------------------------------------------------------------- public List<TileCode> getCodes() { return codes; } public ContentHolder getFeatureContent() { return featureContent; } public ContentHolder getLabelContent() { return labelContent; } public VectorTileContentType getContentType() { return contentType; } /** * Holds string content. * * @author Jan De Moerloose */ public class ContentHolder implements PaintableGroup { private String groupName; private String content; ContentHolder(String groupName) { this.groupName = groupName; } public String getGroupName() { return groupName; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public boolean isLoaded() { return content != null; } public void accept(PainterVisitor visitor, Object group, Bbox bounds, boolean recursive) { } } private boolean needsReload(String filter) { GetVectorTileRequest request = createRequest(filter); return lastRequest == null || !request.isPartOf(lastRequest); } // ------------------------------------------------------------------------- // Private methods: // ------------------------------------------------------------------------- private GetVectorTileRequest createRequest(String filter) { GetVectorTileRequest request = new GetVectorTileRequest(); VectorLayer layer = cache.getLayer(); MapModel mapModel = layer.getMapModel(); MapView mapView = mapModel.getMapView(); request.setCode(code); request.setCrs(mapModel.getCrs()); request.setFilter(filter); request.setLayerId(layer.getServerLayerId()); request.setPaintGeometries(!featureContent.isLoaded()); request.setPaintLabels(layer.isLabelsShowing() && !labelContent.isLoaded()); request.setPanOrigin(mapView.getPanOrigin()); request.setRenderer(Dom.isSvg() ? "SVG" : "VML"); request.setScale(mapView.getCurrentScale()); request.setStyleInfo(layer.getLayerInfo().getNamedStyleInfo()); return request; } public String toString() { return super.toString(); } public void setPictureStyle(PictureStyle pictureStyle) { this.pictureStyle = pictureStyle; } public PictureStyle getPictureStyle() { return pictureStyle; } }