blob: 51d08f2f55e21a23da88f59ebe4cc777a6919417 [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.style.se1;
import java.util.List;
import java.util.ArrayList;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElements;
import jakarta.xml.bind.annotation.XmlRootElement;
// Specific to the main branch:
import org.apache.sis.filter.Expression;
/**
* A "graphic symbol" with an inherent shape, color(s), and possibly size.
* A "graphic" can be very informally defined as "a little picture"
* and can be of either a raster or vector-graphic source type.
*
* <!-- Following list of authors contains credits to OGC GeoAPI 2 contributors. -->
* @author Johann Sorel (Geomatys)
* @author Chris Dillard (SYS Technologies)
* @author Martin Desruisseaux (Geomatys)
*
* @param <R> the type of data to style, such as {@code Feature} or {@code Coverage}.
*/
@XmlType(name = "GraphicType", propOrder = {
"graphicalSymbols",
"opacity",
"size",
"rotation",
"anchorPoint",
"displacement"
})
@XmlRootElement(name = "Graphic")
public class Graphic<R> extends StyleElement<R> implements Translucent<R> {
/**
* List of external image files or marks that comprise this graphic.
* All elements of the list must be instances of either {@link Mark} or {@link ExternalGraphic}.
* If empty it is to be treated a single default mark.
*
* @see #graphicalSymbols()
*/
@XmlElements({
@XmlElement(name = "Mark", type = Mark.class),
@XmlElement(name = "ExternalGraphic", type = ExternalGraphic.class)
})
private List<GraphicalSymbol<R>> graphicalSymbols;
/**
* Level of translucency as a floating point number, or {@code null} for the default value.
*
* @see #getOpacity()
* @see #setOpacity(Expression)
*
* @todo Needs a JAXB adapter to {@code ParameterValueType}.
*/
@XmlElement(name = "Opacity")
protected Expression<R, ? extends Number> opacity;
/**
* Absolute size of the graphic as a floating point number, or {@code null} for the default value.
*
* @see #getSize()
* @see #setSize(Expression)
*
* @todo Needs a JAXB adapter to {@code ParameterValueType}.
*/
@XmlElement(name = "Size")
protected Expression<R, ? extends Number> size;
/**
* Rotation angle of the graphic when it is drawn, or {@code null} for the default value.
*
* @see #getRotation()
* @see #setRotation(Expression)
*
* @todo Needs a JAXB adapter to {@code ParameterValueType}.
*/
@XmlElement(name = "Rotation")
protected Expression<R, ? extends Number> rotation;
/**
* Location to use for anchoring the graphic to the geometry, or {@code null} for lazily constructed default.
*
* @see #getAnchorPoint()
* @see #setAnchorPoint(AnchorPoint)
*/
@XmlElement(name = "AnchorPoint")
protected AnchorPoint<R> anchorPoint;
/**
* Displacement from the "hot-spot" point, or {@code null} for lazily constructed default.
*
* @see #getDisplacement()
* @see #setDisplacement(Displacement)
*/
@XmlElement(name = "Displacement")
protected Displacement<R> displacement;
/**
* For JAXB unmarshalling only.
*/
private Graphic() {
// Thread-local factory will be used.
}
/**
* Creates a graphic initialized to opaque default mark, default size and no rotation.
*
* @param factory the factory to use for creating expressions and child elements.
*/
public Graphic(final StyleFactory<R> factory) {
super(factory);
graphicalSymbols = new ArrayList<>();
}
/**
* Creates a shallow copy of the given object.
* For a deep copy, see {@link #clone()} instead.
*
* @param source the object to copy.
*/
public Graphic(final Graphic<R> source) {
super(source);
graphicalSymbols = new ArrayList<>(source.graphicalSymbols);
opacity = source.opacity;
size = source.size;
rotation = source.rotation;
anchorPoint = source.anchorPoint;
displacement = source.displacement;
}
/**
* Returns the list of external image files or marks that comprise this graphic.
* All elements of the list shall be instances of either {@link Mark} or {@link ExternalGraphic}.
* The list may contain multiple external URLs and marks with the semantic that they all provide
* the equivalent graphic in different {@linkplain GraphicalSymbol#getFormat() formats}.
*
* <p>If the list is empty, it is to be treated as a single default mark.
* That default is a square with with a 50%-gray fill and a black outline,
* with a size of 6 pixels, unless an explicit {@linkplain #getSize() size} is specified.</p>
*
* <p>The returned collection is <em>live</em>:
* changes in that collection are reflected into this object, and conversely.</p>
*
* @return list of marks or external graphics, as a live collection.
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<GraphicalSymbol<R>> graphicalSymbols() {
return graphicalSymbols;
}
/**
* Indicates the level of translucency as a floating point number between 0 and 1 (inclusive).
* A value of zero means completely transparent. A value of 1.0 means completely opaque.
*
* @return the level of translucency as a floating point number between 0 and 1 (inclusive).
*
* @see Fill#getOpacity()
* @see Stroke#getOpacity()
* @see RasterSymbolizer#getOpacity()
*/
@Override
public Expression<R, ? extends Number> getOpacity() {
return defaultToOne(opacity);
}
/**
* Sets the level of translucency as a floating point number between 0 and 1 (inclusive).
* If this method is never invoked, then the default value is literal 1 (totally opaque).
* That default value is standardized by OGC 05-077r4.
*
* @param value new level of translucency, or {@code null} for resetting the default value.
*/
@Override
public void setOpacity(final Expression<R, ? extends Number> value) {
opacity = value;
}
/**
* Returns the absolute size of the graphic as a floating point number.
* If a size is specified, the height of the graphic will be scaled to
* that size and the aspect ratio will be used for computing the width.
* The unit of measurement is given by {@link Symbolizer#getUnitOfMeasure()}.
* Value shall be positive.
*
* <h4>Default value</h4>
* The default size of an image format is the inherent size of the image.
* The default size of an image format without an inherent size is defined
* to be 16 pixels in height and the corresponding aspect in width.
* If no image or mark is specified, then the size of the default mark is 6 pixels.
* That default value is standardized by OGC 05-077r4.
*
* @return absolute size of the graphic as a floating point number, or {@code null} for the default value.
*/
public Expression<R, ? extends Number> getSize() {
final var value = size;
if (value == null && graphicalSymbols.isEmpty()) {
return factory.six;
}
return value;
}
/**
* Sets the absolute size of the graphic as a floating point number.
* If this method is never invoked, then the default value is {@code null}.
*
* @param value new absolute size of the graphic, or {@code null} for the default value.
*/
public void setSize(final Expression<R, ? extends Number> value) {
size = value;
}
/**
* Returns the rotation angle of the graphic when it is drawn.
* The rotation is in the clockwise direction around the graphic center point in decimal degrees.
* Negative values mean counter-clockwise rotation. The default value is 0.0 (no rotation).
*
* <p>The point within the graphic about which it is rotated is format dependent.
* If a format does not include an inherent rotation point,
* then the point of rotation should be the centroid.</p>
*
* @return the rotation angle of the graphic when it is drawn.
*/
public Expression<R, ? extends Number> getRotation() {
return defaultToZero(rotation);
}
/**
* Sets the rotation angle of the graphic when it is drawn.
* If this method is never invoked, then the default value is literal 0.
*
* @param value new rotation angle of the graphic, or {@code null} for resetting the default value.
*/
public void setRotation(final Expression<R, ? extends Number> value) {
rotation = value;
}
/**
* Returns the location inside of a graphic to use for anchoring the graphic to the main-geometry point.
* The coordinates are given as (<var>x</var>,<var>y</var>) floating-point numbers
* relative the graphic bounding box, where (0,0) is the lower-left corner and
* (1,1) is the upper-right corner.
*
* <p>The returned object is <em>live</em>:
* changes in the returned instance will be reflected in this graphic, and conversely.</p>
*
* @return the anchor point.
*/
public AnchorPoint<R> getAnchorPoint() {
if (anchorPoint == null) {
anchorPoint = factory.createAnchorPoint();
}
return anchorPoint;
}
/**
* Sets the location inside of a graphic to use for anchoring the graphic to the main-geometry point.
* The given instance is stored by reference, it is not cloned. If this method is never invoked,
* then the default value is a {@linkplain AnchorPoint#AnchorPoint() default anchor point}.
*
* @param value new anchor point, or {@code null} for resetting the default value.
*/
public void setAnchorPoint(final AnchorPoint<R> value) {
anchorPoint = value;
}
/**
* Returns the two-dimensional displacement from the "hot-spot" point.
* This element may be used to avoid over-plotting of multiple graphic symbols for the same point.
* The unit of measurement is given by {@link Symbolizer#getUnitOfMeasure()}.
* Positive values are to the right of the point.
* The default displacement is <var>x</var> = 0, <var>y</var> = 0,
*
* <p>If displacement is used in conjunction with size and/or rotation
* then the graphic symbol shall be scaled and/or rotated before it is displaced.</p>
*
* @return displacement from the "hot-spot" point.
*
* @see PolygonSymbolizer#getDisplacement()
* @see PointPlacement#getDisplacement()
*/
public Displacement<R> getDisplacement() {
if (displacement == null) {
displacement = factory.createDisplacement();
}
return displacement;
}
/**
* Sets the two-dimensional displacement from the "hot-spot" point.
* The given instance is stored by reference, it is not cloned. If this method is never invoked,
* then the default value is a {@linkplain Displacement#Displacement() default displacement}.
*
* @param value new displacement from the "hot-spot" point, or {@code null} for resetting the default value.
*/
public void setDisplacement(final Displacement<R> value) {
displacement = value;
}
/**
* Returns all properties contained in this class.
* This is used for {@link #equals(Object)} and {@link #hashCode()} implementations.
*/
@Override
final Object[] properties() {
return new Object[] {graphicalSymbols, opacity, size, rotation, anchorPoint, displacement};
}
/**
* Returns a deep clone of this object. All style elements are cloned,
* but expressions are not on the assumption that they are immutable.
*
* @return deep clone of all style elements.
*/
@Override
public Graphic<R> clone() {
final var clone = (Graphic<R>) super.clone();
clone.selfClone();
return clone;
}
/**
* Clones the mutable style fields of this element.
*/
private void selfClone() {
graphicalSymbols = new ArrayList<>(graphicalSymbols);
graphicalSymbols.replaceAll(GraphicalSymbol::clone);
if (anchorPoint != null) anchorPoint = anchorPoint .clone();
if (displacement != null) displacement = displacement.clone();
}
}