/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012, Geomatys * * 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.geotoolkit.coverage.finder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Level; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; import org.geotoolkit.storage.coverage.GridMosaic; import org.geotoolkit.storage.coverage.Pyramid; import org.geotoolkit.storage.coverage.PyramidSet; import org.geotoolkit.coverage.io.DisjointCoverageDomainException; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.opengis.util.FactoryException; import org.apache.sis.geometry.Envelopes; import org.apache.sis.util.Utilities; import org.apache.sis.util.logging.Logging; /** * Define {@link Pyramid} and {@link GridMosaic} search rules. * * @author RĂ©mi Marechal (Geomatys). * @author Johann Sorel (Geomatys). */ public abstract class CoverageFinder { /** * Default epsilon used to compare mosaics scales. */ public static final double DEFAULT_EPSILON = 1E-12; protected CoverageFinder() { } /** * Find the most appropriate mosaic in the pyramid with the given information. * Result GridMosaic can be on a different scale that the requested one. * * @param pyramid * @param scale * @param tolerance * @param env * @param maxTileNumber optional max number of tile in mosaic found. If mosaic found have mode tiles * result will be null. Parameter can be null. * @return GridMosaic that match or null. */ public abstract GridMosaic findMosaic(final Pyramid pyramid, final double scale, final double tolerance, final Envelope env, Integer maxTileNumber) throws FactoryException; /** * Find all mosaics in the pyramid which match given resolution and envelope. * * @param pyramid * @param resolution * @param tolerance * @param env * @return List of GridMosaic * @throws IllegalArgumentException if no mosaic found when requested with bad resolution. * @throws DisjointCoverageDomainException if no mosaic intersect requested envelope */ public List<GridMosaic> findMosaics(final Pyramid pyramid, final double resolution, final double tolerance, final Envelope env) throws DisjointCoverageDomainException { final List<GridMosaic> mosaics = new ArrayList<>(pyramid.getMosaics()); Collections.sort(mosaics, SCALE_COMPARATOR); Collections.reverse(mosaics); final List<GridMosaic> result = new ArrayList<>(); //find the most accurate resolution final double[] scales = pyramid.getScales(); if (scales.length == 0) return result; double bestScale = scales[0]; for (double d : pyramid.getScales()) { if (d > resolution) { //scale is greater but closer to wanted resolution bestScale = d < bestScale ? d : bestScale; } else if ( d > bestScale ) { //found a better resolution bestScale = d; } } int notIntersected = 0; //-- search mosaics for (GridMosaic candidate : mosaics) { //-- check the mosaic intersect the searched envelope final GeneralEnvelope clip = new GeneralEnvelope(candidate.getEnvelope()); if (!clip.intersects(env)) { notIntersected++; continue; } final double scale = candidate.getScale(); if (scale != bestScale) continue; result.add(candidate); } if (result.isEmpty()) { //-- determine distinction between no intersection with mosaic boundaries if (notIntersected != 0) throw new DisjointCoverageDomainException("No mosaics intersects following requested envelope : "+env.toString()); //-- and scale not found. throw new IllegalArgumentException("Impossible to find appropriate mosaic at following requested resolution : "+resolution +"\n The available(s) mosaic(s) resolution(s) is(are) : "+Arrays.toString(scales)); } return result; } /** * Find the most appropriate pyramid in given pyramid set and given crs. * Returned Pyramid may not have the given crs. * * @param set : pyramid set to search in * @param crs searched crs * @return Pyramid, never null except if the pyramid set is empty */ public final Pyramid findPyramid(final PyramidSet set, final CoordinateReferenceSystem crs) throws FactoryException { final CoordinateReferenceSystem crs2D = CRS.getHorizontalComponent(crs); final Envelope crsBound1 = org.geotoolkit.referencing.CRS.getEnvelope(crs2D); double ratio = Double.NEGATIVE_INFINITY; // envelope with crs geographic. final GeneralEnvelope intersection = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic()); final List<Pyramid> results = new ArrayList<>(); if (crsBound1 != null) { final GeneralEnvelope crsBound = new GeneralEnvelope(crsBound1); noValidityDomainFound : for(Pyramid pyramid : set.getPyramids()) { double ratioTemp = 0; Envelope pyramidBound = org.geotoolkit.referencing.CRS.getEnvelope( CRS.getHorizontalComponent(pyramid.getCoordinateReferenceSystem())); if (pyramidBound == null) { results.add(pyramid); continue noValidityDomainFound; } // compute sum of recovery ratio // from crs validity domain area on pyramid crs validity domain area try { pyramidBound = Envelopes.transform(pyramidBound, crs2D); } catch (TransformException ex) { Logging.getLogger("org.geotoolkit.storage.coverage").log(Level.WARNING, ex.getMessage(), ex); } if (!crsBound.intersects(pyramidBound, true)) continue;// no intersection intersection.setEnvelope(crsBound); intersection.intersect(pyramidBound); for (int d = 0; d < 2; d++) {// dim = 2 because extend geographic2D. final double pbs = pyramidBound.getSpan(d); // if intersect a slice part of gridEnvelope. // avoid divide by zero if (pbs <= 1E-12) continue; ratioTemp += intersection.getSpan(d) / pbs; } if (ratioTemp > ratio + DEFAULT_EPSILON) { ratio = ratioTemp; results.clear(); results.add(pyramid); } else if (Math.abs(ratio - ratioTemp) <= DEFAULT_EPSILON) { results.add(pyramid); } } } else { results.addAll(set.getPyramids()); } //paranoiac test if (results.isEmpty()){ //could not find any proper candidates if(set.getPyramids().isEmpty()){ return null; }else{ return set.getPyramids().iterator().next(); } } if (results.size() == 1) return results.get(0); // if several equal ratio. for (Pyramid pyramid : results) { final CoordinateReferenceSystem pyCrs = CRS.getHorizontalComponent(pyramid.getCoordinateReferenceSystem()); if (CRS.findOperation(pyCrs, crs2D, null).getMathTransform().isIdentity() || Utilities.equalsIgnoreMetadata(crs2D, pyCrs) || Utilities.equalsApproximatively(crs2D, pyCrs)) { return pyramid; } } // return first in list. impossible to define the most appropriate crs. return results.get(0); } /** * Sort Grid Mosaic according to there scale, then on additional dimensions. */ public static final Comparator<GridMosaic> SCALE_COMPARATOR = new Comparator<GridMosaic>() { @Override public int compare(final GridMosaic m1, final GridMosaic m2) { final double res = m1.getScale() - m2.getScale(); if(res == 0){ //same scale check additional axes final DirectPosition m1ul = m1.getUpperLeftCorner(); final DirectPosition m2ul = m2.getUpperLeftCorner(); for(int i=2,n=m1ul.getDimension();i<n;i++){ final double ord1 = m1ul.getOrdinate(i); final double ord2 = m2ul.getOrdinate(i); final int c = Double.valueOf(ord1).compareTo(ord2); if(c != 0) return c; } return 0; }else if(res > 0){ return 1; }else{ return -1; } } }; }