@Override
  public void accept(PainterVisitor visitor, Object group, Bbox bounds, boolean recursive) {
    if (googleMap != null) {
      String sourceCrs = map.getMapModel().getCrs();
      if (isGoogleProjection(sourceCrs)) {
        int zoomLevel = calcZoomLevel(map.getMapModel().getMapView().getCurrentScale());
        Coordinate latLon = convertToLatLon(bounds.getCenterPoint());
        fitGoogleMapBounds(googleMap, latLon, zoomLevel);
      } else {
        // transform on server
        TransformGeometryRequest request = new TransformGeometryRequest();
        request.setBounds(
            new org.geomajas.geometry.Bbox(
                bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()));
        request.setSourceCrs(map.getMapModel().getCrs());
        request.setTargetCrs(EPSG_3857);
        GwtCommand command = new GwtCommand(TransformGeometryRequest.COMMAND);
        command.setCommandRequest(request);
        GwtCommandDispatcher.getInstance()
            .execute(
                command,
                new AbstractCommandCallback<TransformGeometryResponse>() {

                  public void execute(TransformGeometryResponse response) {
                    Bbox google = new Bbox(response.getBounds());
                    int zoomLevel = calcZoomLevelFromBounds(google);
                    fitGoogleMapBounds(
                        googleMap, convertToLatLon(google.getCenterPoint()), zoomLevel);
                  }
                });
      }
    }
  }
 private void updateTiles(Bbox bounds, final TileFunction<RasterTile> onUpdate) {
   Bbox panBounds = worldToPan(bounds);
   for (RasterTile tile : tiles.values()) {
     if (panBounds.intersects(tile.getBounds())) {
       onUpdate.execute(tile);
     }
   }
 }
  /** Method based on WmsTileServiceImpl in GWT2 client. */
  private List<org.geomajas.layer.tile.RasterTile> calculateTilesForBounds(Bbox bounds) {
    List<org.geomajas.layer.tile.RasterTile> tiles =
        new ArrayList<org.geomajas.layer.tile.RasterTile>();

    if (bounds.getHeight() == 0 || bounds.getWidth() == 0) {
      return tiles;
    }

    return tiles;
  }
  private void fetchAndUpdateTiles(Bbox bounds, final TileFunction<RasterTile> onUpdate) {
    // fetch a bigger area to avoid server requests while panning
    tileBounds = bounds.scale(3);
    // Calculate tiles

    // double scale = lastViewState.getScale();
    // addTiles(rasterLayer.getTiles(tileBounds, scale > 1 ? scale : 1 / scale));
    addTiles(rasterLayer.getTiles(tileBounds, 1 / lastViewState.getScale()));
    Bbox panBounds = worldToPan(bounds);
    // for each tile:
    for (RasterTile tile : tiles.values()) {
      if (panBounds.intersects(tile.getBounds())) {
        onUpdate.execute(tile);
      }
    }
  }
 private void addTiles(List<org.geomajas.layer.tile.RasterTile> images) {
   Matrix t = rasterLayer.getMapModel().getMapView().getWorldToPanTranslation();
   Bbox cacheBounds = null;
   // flag and reference tile to realign the grid when new tiles come in (transformation shift!)
   boolean newTiles = false;
   RasterTile referenceTile = null;
   for (org.geomajas.layer.tile.RasterTile image : images) {
     TileCode code = image.getCode().clone();
     if (!tiles.containsKey(code)) {
       Bbox panBounds = new Bbox(image.getBounds());
       panBounds.translate(Math.round(t.getDx()), Math.round(t.getDy()));
       if (cacheBounds == null) {
         cacheBounds = panBounds;
       } else {
         cacheBounds = cacheBounds.union(panBounds);
       }
       RasterTile tile = new RasterTile(code, panBounds, image.getUrl(), this);
       tiles.put(code, tile);
       newTiles = true;
       referenceTile = tile;
     }
   }
   // This realigns the grid of tiles based on their code
   if (newTiles) {
     for (RasterTile tile : tiles.values()) {
       if (!tile.getCode().equals(referenceTile.getCode())) {
         Bbox aligned = new Bbox(referenceTile.getBounds());
         aligned.setX(
             referenceTile.getBounds().getX()
                 + (tile.getCode().getX() - referenceTile.getCode().getX()) * aligned.getWidth());
         if (tile.getCode().getY() != referenceTile.getCode().getY()) {
           aligned.setY(
               referenceTile.getBounds().getY()
                   + getOrientedJDiff(referenceTile, tile) * aligned.getHeight());
         }
         tile.setBounds(aligned);
       }
     }
   }
 }
 public void applyAndSync(
     Bbox bounds, TileFunction<RasterTile> onDelete, TileFunction<RasterTile> onUpdate) {
   MapViewState viewState = rasterLayer.getMapModel().getMapView().getViewState();
   boolean panning = lastViewState == null || viewState.isPannableFrom(lastViewState);
   if (!panning || isDirty()) {
     for (RasterTile tile : tiles.values()) {
       onDelete.execute(tile);
     }
     tiles.clear();
     tileBounds = null;
     dirty = false;
   }
   lastViewState = rasterLayer.getMapModel().getMapView().getViewState();
   if (tileBounds == null || !tileBounds.contains(bounds)) {
     fetchAndUpdateTiles(bounds, onUpdate);
   } else {
     updateTiles(bounds, onUpdate);
   }
 }
 private Bbox convertToLatLon(Bbox bounds) {
   // convert corners
   Coordinate orig = convertToLatLon(bounds.getOrigin());
   Coordinate end = convertToLatLon(bounds.getEndPoint());
   return new Bbox(orig.getX(), orig.getY(), end.getX() - orig.getX(), end.getY() - orig.getY());
 }
 private int calcZoomLevelFromBounds(Bbox google) {
   return calcZoomLevel(map.getWidth() / google.getWidth());
 }
 private Bbox worldToPan(Bbox bounds) {
   Matrix t = rasterLayer.getMapModel().getMapView().getWorldToPanTransformation();
   return bounds.transform(t);
 }