/**
* OrbisGIS is a java GIS application dedicated to research in GIScience.
* OrbisGIS is developed by the GIS group of the DECIDE team of the
* Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
*
* The GIS group of the DECIDE team is located at :
*
* Laboratoire Lab-STICC – CNRS UMR 6285
* Equipe DECIDE
* UNIVERSITÉ DE BRETAGNE-SUD
* Institut Universitaire de Technologie de Vannes
* 8, Rue Montaigne - BP 561 56017 Vannes Cedex
*
* OrbisGIS is distributed under GPL 3 license.
*
* Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
* Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285)
*
* This file is part of OrbisGIS.
*
* OrbisGIS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* OrbisGIS 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
*
* For more information, please consult: <http://www.orbisgis.org/>
* or contact directly:
* info_at_ orbisgis.org
*/
package org.orbisgis.coremap.renderer.se.fill;
import net.opengis.se._2_0.thematic.DensityFillType;
import net.opengis.se._2_0.thematic.ObjectFactory;
import org.orbisgis.coremap.map.MapTransform;
import org.orbisgis.coremap.renderer.se.GraphicNode;
import org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle;
import org.orbisgis.coremap.renderer.se.SymbolizerNode;
import org.orbisgis.coremap.renderer.se.graphic.GraphicCollection;
import org.orbisgis.coremap.renderer.se.parameter.ParameterException;
import org.orbisgis.coremap.renderer.se.parameter.SeParameterFactory;
import org.orbisgis.coremap.renderer.se.parameter.real.RealLiteral;
import org.orbisgis.coremap.renderer.se.parameter.real.RealParameter;
import org.orbisgis.coremap.renderer.se.parameter.real.RealParameterContext;
import org.orbisgis.coremap.renderer.se.stroke.PenStroke;
import javax.xml.bind.JAXBElement;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A {@code Fill} implementation where the content of a shape is painted according
* to a given density and to a given mark or hatch type.</p>
* <p>If the hatches are used, ie if {@code isHatched()) is {@code true}, the inner
* {@code PenStroke} and orientation are used. Otherwise, the shape is filled with
* repeated mark, registered as a {@code GraphicCollection} instance.</p>
* <p>In every cases, the needed coverage percentage must be specified. If not set,
* It will be defaulted to {@code DEFAULT_PERCENTAGE}.
* @author Alexis Guéganno, Maxence Laurent
*/
public final class DensityFill extends Fill implements GraphicNode {
private boolean isHatched;
private PenStroke hatches;
private RealParameter orientation;
private GraphicCollection mark;
private RealParameter percentageCovered;
//Some constants we don't want to be considered as magic numbers.
private static final double ONE_HUNDRED = 100;
private static final double FIFTY = 50;
private static final double ONE_HALF= 0.5;
/**
* The default covered percentage.
*/
public static final double DEFAULT_PERCENTAGE = 0.2;
/**
* Build a default {@code DensityFill}
*/
public DensityFill() {
this.setHatches(new PenStroke());
this.setHatchesOrientation(new RealLiteral(HatchedFill.DEFAULT_ALPHA));
this.setPercentageCovered(new RealLiteral(DEFAULT_PERCENTAGE));
}
/**
* Build a new {@code DensityFill}, using the {@code JAXBElement} given in
* argument.
* @param f
* @throws org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle
*/
DensityFill(JAXBElement<DensityFillType> f) throws InvalidStyle {
DensityFillType t = f.getValue();
if (t.getPenStroke() != null) {
this.setHatches(new PenStroke(t.getPenStroke()));
if (t.getOrientation() != null) {
this.setHatchesOrientation(SeParameterFactory.createRealParameter(t.getOrientation()));
}
} else if (t.getGraphic() != null) {
this.setGraphicCollection(new GraphicCollection(t.getGraphic(), this));
}
if (t.getPercentage() != null) {
this.setPercentageCovered(SeParameterFactory.createRealParameter(t.getPercentage()));
}
}
/**
* Set the {@link PenStroke} used to draw the hatches in this {@code
* DensityFill}.
* @param hatches
*/
public void setHatches(PenStroke hatches) {
this.hatches = hatches;
if (hatches != null) {
this.isHatched = true;
this.setGraphicCollection(null);
hatches.setParent(this);
}
}
/**
* Get the {@link PenStroke} used to draw the hatches in this {@code
* DensityFill}.
* @return
*/
public PenStroke getHatches() {
return hatches;
}
/**
* Set the orientation of the hatches associated to this {@code DensityFill}.
* @param orientation angle in degree
*/
public void setHatchesOrientation(RealParameter orientation) {
this.orientation = orientation;
if (this.orientation != null) {
this.orientation.setContext(RealParameterContext.REAL_CONTEXT);
this.orientation.setParent(this);
}
}
/**
* Get the orientation of the hatches associated to this {@code DensityFill}.
* @return
*/
public RealParameter getHatchesOrientation() {
return orientation;
}
@Override
public void setGraphicCollection(GraphicCollection mark) {
this.mark = mark;
if (mark != null) {
this.isHatched = false;
mark.setParent(this);
setHatches(null);
}
}
@Override
public GraphicCollection getGraphicCollection() {
return mark;
}
/**
* After using this method, marks will be preferred on hatches to render this
* {@code DensityFill}
*/
public void useMarks() {
isHatched = false;
}
/**
*
* @return {@code true} if hatches are used to render this {@code
* DensityFill}, false otherwise.
*/
public boolean useHatches() {
return isHatched;
}
/**
*
* @param percent percentage covered by the marks/hatches [0;100]
*/
public void setPercentageCovered(RealParameter percent) {
this.percentageCovered = percent;
if (this.percentageCovered != null) {
this.percentageCovered.setContext(RealParameterContext.PERCENTAGE_CONTEXT);
this.percentageCovered.setParent(this);
}
}
/**
* Get the percentage covered by the marks/hatches.
* @return
* A {@code RealParameter} that is in a {@link RealParameterContext#PERCENTAGE_CONTEXT}
* if not null.
*/
public RealParameter getPercentageCovered() {
return percentageCovered;
}
@Override
public void draw(Graphics2D g2, Map<String,Object> map, Shape shp, boolean selected, MapTransform mt) throws ParameterException, IOException {
if (isHatched) {
double alpha = HatchedFill.DEFAULT_ALPHA;
double pDist;
if (this.orientation != null) {
alpha = this.orientation.getValue(map);
}
// Stroke width
double sWidth = hatches.getWidthInPixel(map, mt);
double percentage = 0.0;
if (percentageCovered != null) {
percentage = percentageCovered.getValue(map) * ONE_HUNDRED;
}
if (percentage > ONE_HUNDRED) {
percentage = ONE_HUNDRED;
}
// Perpendiculat dist bw two hatches
pDist = ONE_HUNDRED * sWidth / percentage;
HatchedFill.drawHatch(g2, map, shp, selected, mt, alpha, pDist, hatches, 0.0);
} else {
Paint painter = getPaint(map, selected, mt);
if (painter != null) {
g2.setPaint(painter);
g2.fill(shp);
}
}
}
@Override
public Paint getPaint(Map<String,Object> map, boolean selected, MapTransform mt) throws ParameterException, IOException {
double percentage = 0.0;
if (percentageCovered != null) {
percentage = percentageCovered.getValue(map) * ONE_HUNDRED;
}
if (percentage > ONE_HUNDRED) {
percentage = ONE_HUNDRED;
}
if (percentage > ONE_HALF) {
Paint painter = null;
if (isHatched && hatches != null) {
return null;
} else if (mark != null) {
Rectangle2D bounds = mark.getBounds(map, selected, mt);
double ratio = Math.sqrt(ONE_HUNDRED / percentage);
double gapX = bounds.getWidth()*ratio - bounds.getWidth();
double gapY = bounds.getHeight()*ratio - bounds.getHeight();
painter = GraphicFill.getPaint(map, selected, mt, mark, gapX, gapY, bounds);
} else {
throw new ParameterException("Neither marks or hatches are defined");
}
return painter;
}
return null;
}
private double getTextureSize(double markSize, double percentage) {
double size = ONE_HUNDRED * (markSize) / percentage;
if (percentage > FIFTY) {
size -= (size - markSize) / 2.0;
}
return size + ONE_HALF;
}
@Override
public DensityFillType getJAXBType() {
DensityFillType f = new DensityFillType();
if (isHatched) {
if (hatches != null) {
f.setPenStroke(hatches.getJAXBType());
}
if (orientation != null) {
f.setOrientation(orientation.getJAXBParameterValueType());
}
} else {
if (mark != null) {
f.setGraphic(mark.getJAXBElement());
}
}
if (percentageCovered != null) {
f.setPercentage(percentageCovered.getJAXBParameterValueType());
}
return f;
}
@Override
public List<SymbolizerNode> getChildren() {
List<SymbolizerNode> ls = new ArrayList<SymbolizerNode>();
if (isHatched) {
if (hatches != null) {
ls.add(hatches);
}
if (orientation != null) {
ls.add(orientation);
}
} else {
if (mark != null) {
ls.add(mark);
}
}
if (percentageCovered != null) {
ls.add(percentageCovered);
}
return ls;
}
@Override
public JAXBElement<DensityFillType> getJAXBElement() {
ObjectFactory of = new ObjectFactory();
return of.createDensityFill(this.getJAXBType());
}
}