/* * 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); } }