/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
* (C) 2004-2010, Refractions Research Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.tile.impl;
import java.util.Arrays;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.tile.Tile;
import org.geotools.tile.TileFactory;
import org.geotools.tile.TileService;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import com.vividsolutions.jts.geom.Coordinate;
/**
* This class is responsible for finding the right zoom-level for a given map
* extent.
*
* @author to.srwn
* @since 12
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/tile-client
* /src/main/java/org/geotools/tile/impl/ScaleZoomLevelMatcher.java $
*/
public class ScaleZoomLevelMatcher {
/** the CRS of the map (MapCrs) */
private CoordinateReferenceSystem crsMap;
/** the CRS used for the tile cutting (TileCrs) */
private CoordinateReferenceSystem crsTiles;
/** Transformation: MapCrs -> TileCrs (mostly WGS_84) */
private MathTransform transformMapToTileCrs;
/**
* Transformation: TileCrs (mostly WGS_84) -> MapCrs (needed for the blank
* tiles)
**/
private MathTransform transformTileCrsToMap;
/** the extent that should be drawn in TileCrs */
private ReferencedEnvelope mapExtentTileCrs;
/** the extent that should be drawn in MapCrs */
private ReferencedEnvelope mapExtentMapCrs;
/** the current map-scale */
private double scale;
private static int DPI;
static {
try {
DPI = 96;// TODO ?????Toolkit.getDefaultToolkit().getd
// Display.getDefault().getDPI().x;
} catch (Exception exc) {
DPI = 96;
}
}
public ScaleZoomLevelMatcher(CoordinateReferenceSystem crsMap,
CoordinateReferenceSystem crsTiles,
MathTransform transformMapToTileCrs,
MathTransform transformTileCrsToMap,
ReferencedEnvelope mapExtentTileCrs,
ReferencedEnvelope mapExtentMapCrs, double scale) {
this.crsMap = crsMap;
this.crsTiles = crsTiles;
this.transformMapToTileCrs = transformMapToTileCrs;
this.transformTileCrsToMap = transformTileCrsToMap;
this.mapExtentTileCrs = mapExtentTileCrs;
this.mapExtentMapCrs = mapExtentMapCrs;
this.scale = scale;
}
public static ScaleZoomLevelMatcher createMatcher(
ReferencedEnvelope mapExtentMapCrs, double scale,
TileService wmtSource) throws Exception {
CoordinateReferenceSystem crsMap = mapExtentMapCrs
.getCoordinateReferenceSystem();
CoordinateReferenceSystem crsTiles = wmtSource.getTileCrs(); // the CRS
// used for
// the tile
// cutting
// Transformation: MapCrs -> TileCrs (mostly WGS_84)
MathTransform transformMapToTileCrs = getTransformation(crsMap,
crsTiles);
// Transformation: TileCrs (mostly WGS_84) -> MapCrs (needed for the
// blank tiles)
MathTransform transformTileCrsToMap = getTransformation(crsTiles,
crsMap);
// Get the mapExtent in the tiles CRS
ReferencedEnvelope mapExtentTileCrs = getProjectedEnvelope(
mapExtentMapCrs, crsTiles, transformMapToTileCrs);
return new ScaleZoomLevelMatcher(crsMap, crsTiles,
transformMapToTileCrs, transformTileCrsToMap, mapExtentTileCrs,
mapExtentMapCrs, scale);
}
/**
* Re-Projects the given envelope to destinationCRS using transformation.
*
* @param envelope
* @param destinationCRS
* @param transformation
* @return
* @throws Exception
*/
public static ReferencedEnvelope getProjectedEnvelope(
ReferencedEnvelope envelope,
CoordinateReferenceSystem destinationCRS,
MathTransform transformation) throws Exception {
CoordinateReferenceSystem sourceCRS = envelope
.getCoordinateReferenceSystem();
if (sourceCRS.equals(destinationCRS)) {
// no need to reproject
return envelope;
} else {
// Reproject envelope: first try JTS.transform, if that fails use
// ReferencedEnvelope.transform
try {
return new ReferencedEnvelope(JTS.transform(envelope,
transformation), destinationCRS);
} catch (Exception exc) {
return envelope.transform(destinationCRS, false);
}
}
}
/**
* Returns the transformation to convert between these two CRS's.
*
* @param fromCRS
* @param toCRS
* @return
* @throws Exception
*/
public static MathTransform getTransformation(
CoordinateReferenceSystem fromCRS, CoordinateReferenceSystem toCRS)
throws Exception {
if (!fromCRS.equals(toCRS)) {
return CRS.findMathTransform(fromCRS, toCRS);
}
return null;
}
public CoordinateReferenceSystem getCrsMap() {
return crsMap;
}
public CoordinateReferenceSystem getCrsTiles() {
return crsTiles;
}
public ReferencedEnvelope getMapExtentTileCrs() {
return mapExtentTileCrs;
}
public double getScale() {
return scale;
}
/**
* Finds out the best fitting zoom-level for a given map-scale.
*
* @param service
* @param tempScaleList
* @return
*/
public int getZoomLevelFromScale(TileService service, double[] tempScaleList) {
double[] scaleList = service.getScaleList();
// Start with the most detailed zoom-level and search the best-fitting
// one
int zoomLevel = scaleList.length - 1;
getOptimumScaleFromZoomLevel(zoomLevel, service, tempScaleList);
for (int i = scaleList.length - 2; i >= 0; i--) {
if (Double.isNaN(scaleList[i])) {
break;
} else if (getScale() < getOptimumScaleFromZoomLevel(i, service,
tempScaleList)) {
break;
}
zoomLevel = i;
if (getScale() > getOptimumScaleFromZoomLevel(i + 1, service,
tempScaleList)) {
zoomLevel = i;
}
}
return zoomLevel;
}
/**
* Calculates the "best" scale for a given zoom-level by calculating the
* scale for a tile in the center of the map-extent and by taking the mapCrs
* in account. "Best" scale is the scale where a 256x256 tile has also this
* size when displayed in uDig.
*
* @param zoomLevel
* @param service
* @param tempScaleList
* @return
*/
public double getOptimumScaleFromZoomLevel(int zoomLevel,
TileService service, double[] tempScaleList) {
// check if we have calculated this already
if (!Double.isNaN(tempScaleList[zoomLevel])) {
return tempScaleList[zoomLevel];
}
try {
ReferencedEnvelope centerTileBounds = getBoundsOfCenterTileInMapCrs(
zoomLevel, service);
double _scale = RendererUtilities.calculateScale(centerTileBounds,
service.getTileWidth(), service.getTileHeight(), DPI);
// cache the scale
tempScaleList[zoomLevel] = _scale;
return _scale;
} catch (Exception exc) {
exc.printStackTrace();
}
// in case of error, return fallback zoom-level
return service.getScaleList()[zoomLevel];
}
public double getOptimumScaleFromZoomLevel(int zoomLevel,
TileService wmtSource) {
double[] tempScaleList = new double[wmtSource.getScaleList().length];
Arrays.fill(tempScaleList, Double.NaN);
return getOptimumScaleFromZoomLevel(zoomLevel, wmtSource, tempScaleList);
}
/**
* Returns the bounds of the tile which covers the center of the map extent
* in the CRS of the map.
*
* @param zoomLevel
* @param wmtSource
* @return
* @throws Exception
*/
private ReferencedEnvelope getBoundsOfCenterTileInMapCrs(int zoomLevel,
TileService wmtSource) throws Exception {
Tile centerTile = getCenterTile(zoomLevel, wmtSource);
ReferencedEnvelope boundsInTileCrs = centerTile.getExtent();
ReferencedEnvelope boundsInMapCrs = projectTileToMapCrs(boundsInTileCrs);
return boundsInMapCrs;
}
/**
* Returns the tile which covers the center of the map extent.
*
* @param zoomLevel
* @param wmtSource
* @return
*/
private Tile getCenterTile(int zoomLevel, TileService wmtSource) {
TileFactory tileFactory = wmtSource.getTileFactory();
ZoomLevel zoomLevelInstance = tileFactory.getZoomLevel(zoomLevel,
wmtSource);
// get the coordinates of the map centre (in TileCrs)
Coordinate centerPoint = mapExtentTileCrs.centre();
return tileFactory.findTileAtCoordinate(centerPoint.x, centerPoint.y,
zoomLevelInstance, wmtSource);
}
public ReferencedEnvelope projectTileToMapCrs(
ReferencedEnvelope boundsInTileCrs) throws Exception {
// assert(boundsInTileCrs.getCoordinateReferenceSystem().equals(crsTiles));
return getProjectedEnvelope(boundsInTileCrs, crsMap,
transformTileCrsToMap);
}
public ReferencedEnvelope projectMapToTileCrs(
ReferencedEnvelope boundsInMapCrs) throws Exception {
return getProjectedEnvelope(boundsInMapCrs, crsTiles,
transformMapToTileCrs);
}
}