blob: 64e55b0e1024cb671cdabcaac63d9cea9bb92342 [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.metadata.iso.identification;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.opengis.util.InternationalString;
import org.opengis.metadata.identification.RepresentativeFraction;
import org.opengis.metadata.identification.Resolution;
import org.apache.sis.internal.jaxb.Context;
import org.apache.sis.internal.jaxb.gco.GO_Distance;
import org.apache.sis.internal.jaxb.gco.GO_Real;
import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
import org.apache.sis.metadata.iso.ISOMetadata;
import org.apache.sis.measure.ValueRange;
import org.apache.sis.util.resources.Messages;
import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
/**
* Level of detail expressed as a scale factor or a ground distance.
* The following properties are mandatory or conditional (i.e. mandatory under some circumstances)
* in a well-formed metadata according ISO 19115:
*
* <div class="preformat">{@code MD_Resolution}
* {@code   ├─angularDistance……} Angular sampling measure.
* {@code   ├─distance………………………} Ground sample distance.
* {@code   ├─equivalentScale……} Level of detail expressed as the scale of a comparable hardcopy map or chart.
* {@code   │   └─denominator……} The number below the line in a vulgar fraction.
* {@code   ├─levelOfDetail…………} Brief textual description of the spatial resolution of the resource.
* {@code   └─vertical………………………} Vertical sampling distance.</div>
*
* ISO 19115 defines {@code Resolution} as an <cite>union</cite> (in the C/C++ sense):
* only one of the properties in this class can be set to a non-empty value.
* Setting any property to a non-empty value discard all the other ones.
* See the {@linkplain #DefaultResolution(Resolution) constructor javadoc}
* for information about which property has precedence on copy operations.
*
* <h2>Limitations</h2>
* <ul>
* <li>Instances of this class are not synchronized for multi-threading.
* Synchronization, if needed, is caller's responsibility.</li>
* <li>Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases.
* Serialization support is appropriate for short term storage or RMI between applications running the
* same version of Apache SIS. For long term storage, use {@link org.apache.sis.xml.XML} instead.</li>
* </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Touraïvane (IRD)
* @author Cédric Briançon (Geomatys)
* @author Cullen Rombach (Image Matters)
* @version 1.0
*
* @see AbstractIdentification#getSpatialResolutions()
*
* @since 0.3
* @module
*/
@XmlType(name = "MD_Resolution_Type") // No need for propOrder since this structure is a union (see javadoc).
@XmlRootElement(name = "MD_Resolution")
public class DefaultResolution extends ISOMetadata implements Resolution {
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = 4333582736458380544L;
/**
* Enumeration of possible values for {@link #property}.
*/
private static final byte SCALE=1, DISTANCE=2, VERTICAL=3, ANGULAR=4, TEXT=5;
/**
* The names of the mutually exclusive properties.
* The index of each name shall be the value of the above {@code byte} constants minus one.
*/
private static final String[] NAMES = {
"equivalentScale",
"distance",
"vertical",
"angularDistance",
"levelOfDetail"
};
/**
* The names of the setter methods, for logging purpose only.
*/
private static final String[] SETTERS = {
"setEquivalentScale",
"setDistance",
"setVertical",
"setAngularDistance",
"setLevelOfDetail"
};
/**
* Specifies which property is set, or 0 if none.
*/
private byte property;
/**
* Either the scale as a {@link RepresentativeFraction} instance, the distance, the angle,
* or the level of details as an {@link InternationalString} instance.
*/
private Object value;
/**
* Constructs an initially empty resolution.
*/
public DefaultResolution() {
}
/**
* Creates a new resolution initialized to the given scale.
*
* @param scale the scale, or {@code null} if none.
*
* @since 0.4
*/
public DefaultResolution(final RepresentativeFraction scale) {
if (scale != null) {
value = scale;
property = SCALE;
}
}
// Note: there is not yet DefaultResolution(double) method because
// we need to update the Unit Of Measurement package first.
/**
* Constructs a new instance initialized with the values from the specified metadata object.
* This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
* given object are not recursively copied.
*
* <p>If more than one of the {@linkplain #getEquivalentScale() equivalent scale},
* {@linkplain #getDistance() distance}, {@linkplain #getVertical() vertical},
* {@linkplain #getAngularDistance() angular distance} and {@linkplain #getLevelOfDetail() level of detail}
* are specified, then the first of those values is taken and the other values are silently discarded.</p>
*
* <div class="note"><b>Note on properties validation:</b>
* This constructor does not verify the property values of the given metadata (e.g. whether it contains
* unexpected negative values). This is because invalid metadata exist in practice, and verifying their
* validity in this copy constructor is often too late. Note that this is not the only hole, as invalid
* metadata instances can also be obtained by unmarshalling an invalid XML document.
* </div>
*
* @param object the metadata to copy values from, or {@code null} if none.
*
* @see #castOrCopy(Resolution)
*/
public DefaultResolution(final Resolution object) {
super(object);
if (object != null) {
for (byte p=SCALE; p<=TEXT; p++) {
final Object c;
switch (p) {
case SCALE: c = object.getEquivalentScale(); break;
case DISTANCE: c = object.getDistance(); break;
case VERTICAL: c = object.getVertical(); break;
case ANGULAR: c = object.getAngularDistance(); break;
case TEXT: c = object.getLevelOfDetail(); break;
default: throw new AssertionError(p);
}
if (c != null) {
property = p;
value = c;
break;
}
}
}
}
/**
* Returns a SIS metadata implementation with the values of the given arbitrary implementation.
* This method performs the first applicable action in the following choices:
*
* <ul>
* <li>If the given object is {@code null}, then this method returns {@code null}.</li>
* <li>Otherwise if the given object is already an instance of
* {@code DefaultResolution}, then it is returned unchanged.</li>
* <li>Otherwise a new {@code DefaultResolution} instance is created using the
* {@linkplain #DefaultResolution(Resolution) copy constructor}
* and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
* metadata contained in the given object are not recursively copied.</li>
* </ul>
*
* @param object the object to get as a SIS implementation, or {@code null} if none.
* @return a SIS implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
*/
public static DefaultResolution castOrCopy(final Resolution object) {
if (object == null || object instanceof DefaultResolution) {
return (DefaultResolution) object;
}
return new DefaultResolution(object);
}
/**
* Sets the properties identified by the {@code code} argument, if non-null.
* This discards any other properties.
*
* @param code the property which is going to be set.
* @param newValue the new value.
*/
private void setProperty(final byte code, final Object newValue) {
checkWritePermission(value);
if (value != null && property != code) {
if (newValue == null) {
return; // Do not erase the other property.
}
Context.warningOccured(Context.current(), DefaultResolution.class, SETTERS[code-1],
Messages.class, Messages.Keys.DiscardedExclusiveProperty_2, NAMES[property-1], NAMES[code-1]);
}
value = newValue;
property = code;
}
/**
* Returns the level of detail expressed as the scale of a comparable hardcopy map or chart.
*
* @return level of detail expressed as the scale of a comparable hardcopy, or {@code null}.
*/
@Override
@XmlElement(name = "equivalentScale")
public RepresentativeFraction getEquivalentScale() {
return (property == SCALE) ? (RepresentativeFraction) value : null;
}
/**
* Sets the level of detail expressed as the scale of a comparable hardcopy map or chart.
*
* <h4>Effect on other properties</h4>
* If and only if the {@code newValue} is non-null, then this method automatically
* discards all other properties.
*
* @param newValue the new equivalent scale.
*/
public void setEquivalentScale(final RepresentativeFraction newValue) {
setProperty(SCALE, newValue);
}
/**
* Returns the ground sample distance.
*
* @return the ground sample distance, or {@code null}.
*/
@Override
@XmlElement(name = "distance")
@XmlJavaTypeAdapter(GO_Distance.class)
@ValueRange(minimum=0, isMinIncluded=false)
public Double getDistance() {
return (property == DISTANCE) ? (Double) value : null;
}
/**
* Sets the ground sample distance.
*
* <h4>Effect on other properties</h4>
* If and only if the {@code newValue} is non-null, then this method automatically
* discards all other properties.
*
* @param newValue the new distance, or {@code null}.
* @throws IllegalArgumentException if the given value is NaN, zero or negative.
*/
public void setDistance(final Double newValue) {
if (ensurePositive(DefaultResolution.class, "distance", true, newValue)) {
setProperty(DISTANCE, newValue);
}
}
/**
* Returns the vertical sampling distance.
*
* @return the vertical sampling distance, or {@code null}.
*
* @since 0.5
*/
@Override
@XmlElement(name = "vertical")
@XmlJavaTypeAdapter(GO_Real.Since2014.class)
@ValueRange(minimum=0, isMinIncluded=false)
public Double getVertical() {
return (property == VERTICAL) ? (Double) value : null;
}
/**
* Sets the vertical sampling distance.
*
* <h4>Effect on other properties</h4>
* If and only if the {@code newValue} is non-null, then this method automatically
* discards all other properties.
*
* @param newValue the new distance, or {@code null}.
* @throws IllegalArgumentException if the given value is NaN, zero or negative.
*
* @since 0.5
*/
public void setVertical(final Double newValue) {
if (ensurePositive(DefaultResolution.class, "vertical", true, newValue)) {
setProperty(VERTICAL, newValue);
}
}
/**
* Returns the angular sampling measure.
*
* @return the angular sampling measure, or {@code null}.
*
* @since 0.5
*/
@Override
@XmlElement(name = "angularDistance")
@XmlJavaTypeAdapter(GO_Real.Since2014.class)
@ValueRange(minimum=0, isMinIncluded=false)
public Double getAngularDistance() {
return (property == ANGULAR) ? (Double) value : null;
}
/**
* Sets the angular sampling measure.
*
* <h4>Effect on other properties</h4>
* If and only if the {@code newValue} is non-null, then this method automatically
* discards all other properties.
*
* @param newValue the new distance, or {@code null}.
* @throws IllegalArgumentException if the given value is NaN, zero or negative.
*
* @since 0.5
*/
public void setAngularDistance(final Double newValue) {
if (ensurePositive(DefaultResolution.class, "angular", true, newValue)) {
setProperty(ANGULAR, newValue);
}
}
/**
* Returns a brief textual description of the spatial resolution of the resource.
*
* @return textual description of the spatial resolution, or {@code null}.
*
* @since 0.5
*/
@Override
@XmlElement(name = "levelOfDetail")
@XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
public InternationalString getLevelOfDetail() {
return (property == TEXT) ? (InternationalString) value : null;
}
/**
* Sets the textual description of the spatial resolution of the resource.
*
* <h4>Effect on other properties</h4>
* If and only if the {@code newValue} is non-null, then this method automatically
* discards all other properties.
*
* @param newValue the new distance.
*
* @since 0.5
*/
public void setLevelOfDetail(final InternationalString newValue) {
setProperty(TEXT, newValue);
}
}