blob: de6e0df7fc800665c4f9947356c049c2170af0a7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.geometry;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.internal.metadata.ReferencingServices;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.referencing.CRS;
import org.apache.sis.util.Utilities;
/**
* Applies union or intersection operations on a sequence of envelopes.
* This utility class infers the a common coordinate reference system
* for performing the reduce operation.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
*
* @see Envelopes#union(Envelope...)
* @see Envelopes#intersect(Envelope...)
*
* @since 1.0
* @module
*/
class EnvelopeReducer {
/**
* A reducer performing the {@linkplain GeneralEnvelope#add(Envelope) union} operation.
*
* @see Envelopes#union(Envelope...)
*/
static final EnvelopeReducer UNION = new EnvelopeReducer("union");
/**
* A reducer performing the {@linkplain GeneralEnvelope#intersect(Envelope) intersection} operation.
*
* @see Envelopes#intersect(Envelope...)
*/
static final EnvelopeReducer INTERSECT = new EnvelopeReducer("intersect") {
@Override void reduce(GeneralEnvelope result, Envelope other) {
result.intersect(other);
}
@Override void reduce(DefaultGeographicBoundingBox result, GeographicBoundingBox other) {
result.intersect(other);
}
};
/**
* The public method from {@link Envelopes} which is using this envelope reducer.
*/
private final String caller;
/**
* Creates a new reducer. We should have a singleton instance for each type of reduce operation.
*/
EnvelopeReducer(final String caller) {
this.caller = caller;
}
/**
* Applies the reduce operation on the given {@code result} envelope.
* The result is modified in-place.
*/
void reduce(GeneralEnvelope result, Envelope other) {
result.add(other);
}
/**
* Applies the reduce operation on the given {@code result} bounding box.
* The result is modified in-place.
*/
void reduce(DefaultGeographicBoundingBox result, GeographicBoundingBox other) {
result.add(other);
}
/**
* Reduces all given envelopes, transforming them to a common CRS if necessary.
* If all envelopes use the same CRS (ignoring metadata) or if the CRS of all envelopes is {@code null},
* then the reduce operation is performed without transforming any envelope. Otherwise all envelopes are
* transformed to a {@linkplain CRS#suggestCommonTarget common CRS} before reduction.
* The CRS of the returned envelope may different than the CRS of all given envelopes.
*
* @param envelopes the envelopes for which to perform the reduce operation. Null elements are ignored.
* @return result of reduce operation, or {@code null} if the given array does not contain non-null elements.
* @throws TransformException if this method can not determine a common CRS, or if a transformation failed.
*/
final GeneralEnvelope reduce(final Envelope[] envelopes) throws TransformException {
/*
* First, compute the unions or intersections of all envelopes having a common CRS
* without performing any reprojection. In the common case where all envelopes use
* the same CRS, this will result in an array having only one non-null element.
*/
final GeneralEnvelope[] reduced = new GeneralEnvelope[envelopes.length];
int count = 0;
merge: for (final Envelope envelope : envelopes) {
if (envelope != null) {
final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
for (int i=0; i<count; i++) {
final GeneralEnvelope previous = reduced[i];
if (Utilities.equalsIgnoreMetadata(crs, previous.getCoordinateReferenceSystem())) {
reduce(previous, envelope);
continue merge;
}
}
reduced[count++] = new GeneralEnvelope(envelope);
}
}
switch (count) {
case 0: return null;
case 1: return reduced[0];
}
/*
* Compute the geographic bounding box of all remaining elements to reduce. This will be used for
* choosing a common CRS. Note that if a warning is logged, ReferencingServices.setBounds(…) will
* pretend that warning come from Envelopes.<caller>. This is related to Envelopes.findOperation(…)
* since the purpose of this bounding box is to find a coordinate operation.
*/
final ReferencingServices converter = ReferencingServices.getInstance();
CoordinateReferenceSystem[] crs = new CoordinateReferenceSystem[count];
DefaultGeographicBoundingBox more = new DefaultGeographicBoundingBox();
DefaultGeographicBoundingBox bbox = null;
for (int i=0; i<count; i++) {
final GeneralEnvelope e = reduced[i];
crs[i] = e.getCoordinateReferenceSystem();
if (converter.setBounds(e, more, caller) != null) { // See above comment about logging.
if (bbox == null) {
bbox = more;
more = new DefaultGeographicBoundingBox();
} else {
reduce(bbox, more);
}
}
}
/*
* Now transform all remaining envelopes, so we can perform final reduction.
*/
final CoordinateReferenceSystem target = CRS.suggestCommonTarget(bbox, crs);
if (target == null) {
throw new TransformException(Resources.format(Resources.Keys.CanNotFindCommonCRS));
}
GeneralEnvelope result = null;
for (int i=0; i<count; i++) {
final Envelope other = Envelopes.transform(reduced[i], target);
if (result == null) {
result = GeneralEnvelope.castOrCopy(other);
} else {
reduce(result, other);
}
}
return result;
}
}