Package org.geomajas.layer.osm

Source Code of org.geomajas.layer.osm.TiledRasterLayerService

/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 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.layer.osm;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.CrsTransform;
import org.geomajas.global.Api;
import org.geomajas.global.ExceptionCode;
import org.geomajas.global.GeomajasException;
import org.geomajas.layer.tile.RasterTile;
import org.geomajas.layer.tile.TileCode;
import org.geomajas.service.GeoService;
import org.geotools.geometry.jts.JTS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
* Utility service which helps to handle a tiled raster layer.
*
* @author Joachim Van der Auwera
* @since 1.8.0
*/
@Api
@Component
public class TiledRasterLayerService {

  private final Logger log = LoggerFactory.getLogger(TiledRasterLayerService.class);

  public static final String MERCATOR = "EPSG:900913";
 
  public static final double EQUATOR_IN_METERS = 40075016.686;
  public static final double HALF_EQUATOR_IN_METERS = 40075016.686 / 2;
  public static final double MAP_UNIT_PER_GOOGLE_METER_DEFAULT = 0.653;

  public static final int MAX_ZOOM_LEVEL = 31;

  private static final int[] POWERS_OF_TWO;

  @Autowired
  private GeoService geoService;

  static {
    POWERS_OF_TWO = new int[MAX_ZOOM_LEVEL + 1];
    int b = 1;
    for (int c = 0; c < POWERS_OF_TWO.length; c++) {
      POWERS_OF_TWO[c] = b;
      b *= 2;
    }
  }

  /**
   * Paint raster layer.
   *
   * @param tileServiceState state (as this is a singleton)
   * @param boundsCrs crs for bounds
   * @param bounds bounds for which tiles are needed
   * @param scale scale for rendering
   * @return list of raster tiles
   * @throws GeomajasException oops
   * @since 1.8.0
   */
  @Api
  public List<RasterTile> paint(TiledRasterLayerServiceState tileServiceState, CoordinateReferenceSystem boundsCrs,
      Envelope bounds, double scale) throws GeomajasException {
    try {
      CrsTransform layerToMap = geoService.getCrsTransform(tileServiceState.getCrs(), boundsCrs);
      CrsTransform mapToLayer = geoService.getCrsTransform(boundsCrs, tileServiceState.getCrs());

      bounds = clipBounds(tileServiceState, bounds);
      if (bounds.isNull()) {
        return new ArrayList<RasterTile>();
      }

      // find the center of the map in map coordinates (positive y-axis)
      Coordinate boundsCenter = new Coordinate((bounds.getMinX() + bounds.getMaxX()) / 2,
          (bounds.getMinY() + bounds.getMaxY()) / 2);

      // find zoomlevel
      // scale in pix/m should just above the given scale so we have at least one
      // screen pixel per google pixel ! (otherwise text unreadable)
      int zoomLevel = getBestZoomLevelForScaleInPixPerMeter(tileServiceState, mapToLayer, boundsCenter, scale);
      log.debug("zoomLevel={}", zoomLevel);

      // find the google indices for the center
      // google indices determine the row and column of the 256x256 image
      // in the big google square for the given zoom zoomLevel
      // the resulting indices are floating point values as the center
      // is not coincident with an image corner !!!!
      Coordinate indicesCenter;
      indicesCenter = getTileIndicesFromMap(mapToLayer, boundsCenter, zoomLevel);

      // Calculate the width in map units of the image that contains the
      // center
      Coordinate indicesUpperLeft = new Coordinate(Math.floor(indicesCenter.x), Math.floor(indicesCenter.y));
      Coordinate indicesLowerRight = new Coordinate(indicesUpperLeft.x + 1, indicesUpperLeft.y + 1);
      Coordinate mapUpperLeft = getMapFromTileIndices(layerToMap, indicesUpperLeft, zoomLevel);
      Coordinate mapLowerRight =
          getMapFromTileIndices(layerToMap, indicesLowerRight, zoomLevel);
      double width = Math.abs(mapLowerRight.x - mapUpperLeft.x);
      if (0 == width) {
        width = 1.0;
      }
      double height = Math.abs(mapLowerRight.y - mapUpperLeft.y);
      if (0 == height) {
        height = 1.0;
      }

      // Calculate the position and indexes of the center image corner
      // in map space
      double xCenter = boundsCenter.x - (indicesCenter.x - indicesUpperLeft.x) * width;
      double yCenter = boundsCenter.y + (indicesCenter.y - indicesUpperLeft.y) * height;
      int iCenter = (int) indicesUpperLeft.x;
      int jCenter = (int) indicesUpperLeft.y;

      // Calculate the position and indexes of the upper left image corner
      // that just falls off the screen
      double xMin = xCenter;
      int iMin = iCenter;
      while (xMin > bounds.getMinX() && iMin > 0) {
        xMin -= width;
        iMin--;
      }
      double yMax = yCenter;
      int jMin = jCenter;
      while (yMax < bounds.getMaxY() && jMin > 0) {
        yMax += height;
        jMin--;
      }
      // Calculate the indexes of the lower right corner
      // that just falls off the screen
      int levelMax = POWERS_OF_TWO[zoomLevel] - 1;
      double xMax = xCenter;
      int iMax = iCenter;
      while (xMax < bounds.getMaxX() && iMax <= levelMax) {
        xMax += width;
        iMax++;
      }
      double yMin = yCenter;
      int jMax = jCenter;
      while (yMin > bounds.getMinY() && jMax <= levelMax) {
        yMin -= height;
        jMax++;
      }
      Coordinate upperLeft = new Coordinate(xMin, yMax);

      // calculate the images
      List<RasterTile> result = new ArrayList<RasterTile>();
      if (log.isDebugEnabled()) {
        log.debug("bounds =" + bounds);
        log.debug("tilebounds " + xMin + ", " + xMax + ", " + yMin + ", " + yMax);
        log.debug("indices " + iMin + ", " + iMax + ", " + jMin + ", " + jMax);
      }
      int xScreenUpperLeft = (int) Math.round(upperLeft.x * scale);
      int yScreenUpperLeft = (int) Math.round(upperLeft.y * scale);
      int screenWidth = (int) Math.round(scale * width);
      int screenHeight = (int) Math.round(scale * height);
      for (int i = iMin; i < iMax; i++) {
        for (int j = jMin; j < jMax; j++) {
          // Using screen coordinates:
          int x = xScreenUpperLeft + (i - iMin) * screenWidth;
          int y = yScreenUpperLeft - (j - jMin) * screenHeight;

          RasterTile image = new RasterTile(new Bbox(x, -y, screenWidth, screenHeight),
              tileServiceState.getId() + "." + zoomLevel + "." + i + "," + j);
          image.setCode(new TileCode(zoomLevel, i, j));
          String url = tileServiceState.getUrlSelectionStrategy().next();
          url = url.replace("${level}", Integer.toString(zoomLevel));
          url = url.replace("${x}", Integer.toString(i));
          url = url.replace("${y}", Integer.toString(j));
          image.setUrl(url);
          log.debug("adding image {}", image);
          result.add(image);
        }
      }
      return result;
    } catch (TransformException e) {
      throw new GeomajasException(e, ExceptionCode.RENDER_TRANSFORMATION_FAILED);
    }
  }

  private Envelope clipBounds(TiledRasterLayerServiceState tileServiceState, Envelope bounds) {
    return bounds.intersection(tileServiceState.getMaxBounds());
  }

  private int getBestZoomLevelForScaleInPixPerMeter(TiledRasterLayerServiceState tileServiceState,
      CrsTransform layerToGoogle, Coordinate mapPosition,
      double scale) {
    double scaleRatio = MAP_UNIT_PER_GOOGLE_METER_DEFAULT;
    try {
      Coordinate mercatorCenter = JTS.transform(mapPosition, new Coordinate(), layerToGoogle);
      Coordinate dx = JTS.transform(new Coordinate(mapPosition.x + 1, mapPosition.y), new Coordinate(),
          layerToGoogle);
      scaleRatio = 1.0 / (dx.x - mercatorCenter.x);
    } catch (TransformException e) {
      log.warn("calculateMapUnitPerGoogleMeter() : transformation failed", e);
    }
    double scaleInPixPerMeter = scale * scaleRatio;
    double screenResolution = 1.0 / scaleInPixPerMeter;
    double[] resolutions = tileServiceState.getResolutions();
    if (screenResolution >= resolutions[0]) {
      return 0;
    } else if (screenResolution <= resolutions[tileServiceState.getMaxZoomLevel()]) {
      return tileServiceState.getMaxZoomLevel();
    } else {
      for (int i = 0; i < tileServiceState.getMaxZoomLevel(); i++) {
        double upper = resolutions[i];
        double lower = resolutions[i + 1];
        if (screenResolution <= upper && screenResolution >= lower) {
          if ((upper - screenResolution) > 2 * (screenResolution - lower)) {
            return i + 1;
          } else {
            return i;
          }
        }
      }
    }
    // should not occur !!!!
    return MAX_ZOOM_LEVEL;
  }

  private Coordinate getMapFromTileIndices(CrsTransform mapToLayer, Coordinate indices, int zoomLevel)
      throws TransformException {
    double xMeter = EQUATOR_IN_METERS * indices.x / POWERS_OF_TWO[zoomLevel] - HALF_EQUATOR_IN_METERS;
    double yMeter = -EQUATOR_IN_METERS * indices.y / POWERS_OF_TWO[zoomLevel] + HALF_EQUATOR_IN_METERS;
    return JTS.transform(new Coordinate(xMeter, yMeter), new Coordinate(), mapToLayer);
  }

  private Coordinate getTileIndicesFromMap(CrsTransform mapToLayer, Coordinate centerMapCoor, int zoomLevel)
      throws TransformException {
    Coordinate centerLayerCoor = JTS.transform(centerMapCoor, new Coordinate(), mapToLayer);
    double xIndex = (centerLayerCoor.x + HALF_EQUATOR_IN_METERS) * POWERS_OF_TWO[zoomLevel] / EQUATOR_IN_METERS;
    double yIndex = (-centerLayerCoor.y + HALF_EQUATOR_IN_METERS) * POWERS_OF_TWO[zoomLevel] / EQUATOR_IN_METERS;
    return new Coordinate(xIndex, yIndex);
  }

}
TOP

Related Classes of org.geomajas.layer.osm.TiledRasterLayerService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.