/**
* 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.common;
import com.kitfox.svg.app.beans.SVGIcon;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.jai.InterpolationBicubic2;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
import net.opengis.ows._2.OnlineResourceType;
import net.opengis.se._2_0.core.ExternalGraphicType;
import net.opengis.se._2_0.core.MarkGraphicType;
import net.opengis.se._2_0.core.VariableOnlineResourceType;
import org.orbisgis.coremap.map.MapTransform;
import org.orbisgis.coremap.renderer.se.AbstractSymbolizerNode;
import org.orbisgis.coremap.renderer.se.SymbolizerNode;
import org.orbisgis.coremap.renderer.se.graphic.ExternalGraphicSource;
import org.orbisgis.coremap.renderer.se.graphic.MarkGraphicSource;
import org.orbisgis.coremap.renderer.se.graphic.ViewBox;
import org.orbisgis.coremap.renderer.se.parameter.ParameterException;
import org.orbisgis.coremap.renderer.se.parameter.SeParameterFactory;
import org.orbisgis.coremap.renderer.se.parameter.real.RealParameter;
import org.orbisgis.coremap.renderer.se.visitors.FeaturesVisitor;
/**
* An {@code OnlineResource} is used to keep a reference to an graphic resource
* that is stored on disk, or in a remote location, as an image.</p>
* <p>An online resource is directly dependant on an URL that will be used to
* retrieve the image we need.
* @author Maxence Laurent, Alexis Guéganno
* @todo implements MarkGraphicSource
*/
public class OnlineResource extends AbstractSymbolizerNode implements ExternalGraphicSource, MarkGraphicSource {
private URI uri;
private PlanarImage rawImage;
private SVGIcon svgIcon;
private Double effectiveWidth;
private Double effectiveHeight;
private Double svgInitialWidth;
private Double svgInitialHeight;
/**
* Build a new {@code OnlineResource}
*/
public OnlineResource() {
uri = null;
svgIcon = null;
}
/**
* Build a new {@code OnlineResource} with the given String, that is supposed
* to be an URL.
* @param url
* @throws URISyntaxException
* If {@code url} can't be used to build an {@code URL} instance.
*/
public OnlineResource(String url) throws URISyntaxException {
this.uri = new URI(url);
svgIcon = null;
}
/**
* Build an {@code OnlineResource} from the given {@code OnlineResourceType}
* .
* @param onlineResource
* @throws URISyntaxException
* If the href embedded in {@code onlineResource} can't be used to build an
* {@code URL} instance.
*/
public OnlineResource(OnlineResourceType onlineResource) throws URISyntaxException {
this.uri = new URI(onlineResource.getHref());
svgIcon = null;
}
/**
* Get the {@code URL} contained in this {@code OnlineResource}.
* @return
* An {@code URL} instance, that points to the location where to find the
* resource.
*/
public URI getUri() {
return uri;
}
/**
* Set the {@code URL} contained in this {@code OnlineResource}.
* @param url
* @throws URISyntaxException
*/
public void setUri(String url) throws URISyntaxException {
if (url == null || url.isEmpty()) {
this.uri = null;
} else {
this.uri = new URI(url);
}
}
/**
* Get the boundnig box of this {@code OnlineResource} as a {@code
* Rectangle2D.Double} instance.
* @param viewBox
* @param mt
* @param mimeType
* @return
* @throws ParameterException
*/
public Rectangle2D.Double getJAIBounds(ViewBox viewBox, Map<String,Object> map, MapTransform mt, String mimeType) throws ParameterException {
try {
if (rawImage == null) {
rawImage = JAI.create("url", uri);
Logger.getLogger(OnlineResource.class.getName()).log(Level.INFO, "Download ExternalGraphic from: {0}", uri);
}
} catch (Exception ex) {
throw new ParameterException(ex);
}
double width = rawImage.getWidth();
double height = rawImage.getHeight();
if (viewBox != null && mt != null && viewBox.usable()) {
FeaturesVisitor fv = new FeaturesVisitor();
viewBox.acceptVisitor(fv);
if (map == null && !fv.getResult().isEmpty()) {
throw new ParameterException("View box depends on feature"); // TODO I18n
}
try {
Point2D dim = viewBox.getDimensionInPixel(map, height, width, mt.getScaleDenominator(), mt.getDpi());
effectiveWidth = dim.getX();
effectiveHeight = dim.getY();
if (effectiveWidth > 0 && effectiveHeight > 0) {
return new Rectangle2D.Double(-effectiveWidth / 2, -effectiveHeight / 2, effectiveWidth, effectiveHeight);
} else {
effectiveWidth = null;
effectiveHeight = null;
}
} catch (Exception ex) {
throw new ParameterException(ex);
}
}
// Others cases => native image bounds
return new Rectangle2D.Double(-width / 2, -height / 2, width, height);
}
/**
*
* @param viewBox
* @param mt
* @param mimeType
* @return
* @throws ParameterException
*/
public Rectangle2D.Double getSvgBounds(ViewBox viewBox, Map<String,Object> map,
MapTransform mt, String mimeType) throws ParameterException {
/*
* Fetch SVG if not already done
*/
if (svgIcon == null) {
svgIcon = new SVGIcon();
svgIcon.setSvgURI(uri);
svgIcon.setAntiAlias(true);
this.svgInitialHeight = (double) svgIcon.getIconHeight();
this.svgInitialWidth = (double) svgIcon.getIconWidth();
}
if (viewBox != null && mt != null && viewBox.usable()) {
FeaturesVisitor fv = new FeaturesVisitor();
viewBox.acceptVisitor(fv);
if (map == null && !fv.getResult().isEmpty()) {
throw new ParameterException("View box depends on feature"); // TODO I18n
}
Point2D dim = viewBox.getDimensionInPixel(map, svgInitialWidth, svgInitialHeight, mt.getScaleDenominator(), mt.getDpi());
effectiveWidth = dim.getX();
effectiveHeight = dim.getY();
if (effectiveHeight > 0 && effectiveWidth > 0) {
return new Rectangle2D.Double(-effectiveWidth / 2, -effectiveHeight / 2, effectiveWidth, effectiveHeight);
} else {
double width = svgInitialWidth;
double height = svgInitialHeight;
effectiveWidth = null;
effectiveHeight = null;
return new Rectangle2D.Double(-width / 2, -height / 2, width, height);
}
} else {
double width = svgInitialWidth;
double height = svgInitialHeight;
return new Rectangle2D.Double(-width / 2, -height / 2, width, height);
}
}
@Override
public Rectangle2D.Double updateCacheAndGetBounds(ViewBox viewBox, Map<String,Object> map,
MapTransform mt, String mimeType) throws ParameterException {
effectiveWidth = null;
effectiveHeight = null;
if (mimeType != null && mimeType.equalsIgnoreCase("image/svg+xml")) {
return getSvgBounds(viewBox, map, mt, mimeType);
} else {
return getJAIBounds(viewBox, map, mt, mimeType);
}
}
/**
* Draw the svg linked to this {@code Onlineresource} in the Graphics2D g2.
* @param g2
* @param at
* @param opacity
*/
public void drawSVG(Graphics2D g2, AffineTransform at, double opacity) {
AffineTransform fat = new AffineTransform(at);
if (effectiveHeight != null && effectiveWidth != null) {
svgIcon.setPreferredSize(new Dimension((int) (effectiveWidth + 0.5), (int) (effectiveHeight + 0.5)));
fat.concatenate(AffineTransform.getTranslateInstance(-effectiveWidth / 2, -effectiveHeight / 2));
} else {
svgIcon.setPreferredSize(new Dimension((int) (svgInitialWidth + 0.5), (int) (svgInitialHeight + 0.5)));
fat.concatenate(AffineTransform.getTranslateInstance(-svgInitialWidth / 2, -svgInitialHeight / 2));
}
svgIcon.setScaleToFit(true);
AffineTransform atMedia = new AffineTransform(g2.getTransform());
g2.transform(fat);
svgIcon.paintIcon((Component) null, g2, 0, 0);
g2.setTransform(atMedia);
}
/**
* Draw the image linked to this {@code Onlineresource} in the Graphics2D
* g2.
* @param g2
* @param at
* @param mt
* @param opacity
*/
public void drawJAI(Graphics2D g2, AffineTransform at, MapTransform mt, double opacity) {
AffineTransform fat = new AffineTransform(at);
double width = rawImage.getWidth();
double height = rawImage.getHeight();
if (effectiveHeight != null && effectiveWidth != null) {
double ratioX = effectiveWidth / width;
double ratioY = effectiveHeight / height;
RenderedOp img;
if (ratioX > 1.0 || ratioY > 1.0) {
img = JAI.create("scale", rawImage,
(float) ratioX, (float) ratioY,
0.0f, 0.0f,
InterpolationBicubic2.getInstance(InterpolationBicubic2.INTERP_BICUBIC_2),
mt.getRenderingHints());
} else {
img = JAI.create("SubsampleAverage", rawImage, ratioX, ratioY, mt.getRenderingHints());
}
fat.concatenate(AffineTransform.getTranslateInstance(-img.getWidth() / 2.0, -img.getHeight() / 2.0));
g2.drawRenderedImage(img, fat);
} else {
fat.concatenate(AffineTransform.getTranslateInstance(-width / 2.0, -height / 2.0));
g2.drawRenderedImage(rawImage, fat);
}
}
@Override
public void draw(Graphics2D g2, Map<String,Object> map,AffineTransform at, MapTransform mt, double opacity, String mimeType) {
if (mimeType != null && mimeType.equalsIgnoreCase("image/svg+xml")) {
drawSVG(g2, at, opacity);
} else {
drawJAI(g2, at, mt, opacity);
}
}
/**
* @deprecated
*/
public RenderedImage getSvgImage(ViewBox viewBox, Map<String,Object> map, MapTransform mt, String mimeType)
throws IOException, ParameterException {
SVGIcon icon = new SVGIcon();
icon.setSvgURI(uri);
BufferedImage img;
if (viewBox != null && mt != null && viewBox.usable()) {
FeaturesVisitor fv = new FeaturesVisitor();
viewBox.acceptVisitor(fv);
if (map == null && !fv.getResult().isEmpty()) {
throw new ParameterException("View box depends on feature"); // TODO I18n
}
double width = icon.getIconWidth();
double height = icon.getIconHeight();
Point2D dim = viewBox.getDimensionInPixel(map, height, width, mt.getScaleDenominator(), mt.getDpi());
double widthDst = dim.getX();
double heightDst = dim.getY();
if (widthDst > 0 && heightDst > 0) {
img = new BufferedImage((int) (widthDst + 0.5), (int) (heightDst + 0.5), BufferedImage.TYPE_4BYTE_ABGR);
icon.setPreferredSize(new Dimension((int) (widthDst + 0.5), (int) (heightDst + 0.5)));
icon.setScaleToFit(true);
} else {
img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR);
}
} else {
img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR);
}
icon.setAntiAlias(true);
Graphics2D g2 = (Graphics2D) img.getGraphics();
if (mt != null) {
g2.addRenderingHints(mt.getRenderingHints());
}
icon.paintIcon((Component) null, g2, 0, 0);
return img;
}
/**
* @deprecated
* @param viewBox
* @param mt
* @param mimeType
* @return
* @throws IOException
* @throws ParameterException
*/
public PlanarImage getJAIImage(ViewBox viewBox, Map<String,Object> map, MapTransform mt, String mimeType)
throws IOException, ParameterException {
try {
if (rawImage == null) {
rawImage = JAI.create("url", uri);
Logger.getLogger(OnlineResource.class.getName()).log(Level.INFO, "Download ExternalGraphic from: {0}", uri);
}
} catch (Exception ex) {
throw new IOException(ex);
}
PlanarImage img = rawImage;
if (viewBox != null && mt != null && viewBox.usable()) {
FeaturesVisitor fv = new FeaturesVisitor();
viewBox.acceptVisitor(fv);
if (map == null && !fv.getResult().isEmpty()) {
throw new ParameterException("View box depends on feature"); // TODO I18n
}
try {
double width = img.getWidth();
double height = img.getHeight();
Point2D dim = viewBox.getDimensionInPixel(map, height, width, mt.getScaleDenominator(), mt.getDpi());
double widthDst = dim.getX();
double heightDst = dim.getY();
if (widthDst > 0 && heightDst > 0) {
double ratio_x = widthDst / width;
double ratio_y = heightDst / height;
if (ratio_x > 1.0 || ratio_y > 1.0) {
return JAI.create("scale", rawImage,
(float) ratio_x, (float) ratio_y,
0.0f, 0.0f,
InterpolationBicubic2.getInstance(InterpolationBicubic2.INTERP_BICUBIC_2),
mt.getRenderingHints());
} else {
//return JAI.create("SubsampleAverage", pb, mt.getRenderingHints());
return JAI.create("SubsampleAverage", img, ratio_x, ratio_y, mt.getRenderingHints());
}
} else {
return img;
}
} catch (Exception ex) {
throw new ParameterException(ex);
}
} else {
return img;
}
}
@Override
public void setJAXBSource(ExternalGraphicType e) {
VariableOnlineResourceType o = new VariableOnlineResourceType();
o.setHref(SeParameterFactory.createParameterValueType(uri.toString()));
e.setOnlineResource(o);
}
/**
* Get the {@code Font} linked with this {@code OnlineResource}.
* @return
* A {@code Font} instance if the url identifies a valid font, null
* otherwise.
*/
public Font getFont() {
InputStream iStream = null;
try {
iStream = uri.toURL().openStream();
return Font.createFont(Font.TRUETYPE_FONT, iStream);
} catch (FontFormatException ex) {
} catch (IOException ex) {
} finally {
try {
iStream.close();
} catch (IOException ex) {
}
}
return null;
}
private Shape getTrueTypeGlyph(ViewBox viewBox, Map<String,Object> map, Double scale,
Double dpi, RealParameter markIndex) throws ParameterException, IOException {
try {
InputStream iStream = uri.toURL().openStream();
Font font = Font.createFont(Font.TRUETYPE_FONT, iStream);
iStream.close();
double value = markIndex.getValue(map);
char[] data = {(char) value};
String text = String.copyValueOf(data);
// Scale is used to have an high resolution
AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
FontRenderContext fontCtx = new FontRenderContext(at, true, true);
TextLayout tl = new TextLayout(text, font, fontCtx);
Shape glyphOutline = tl.getOutline(at);
Rectangle2D bounds2D = glyphOutline.getBounds2D();
double width = bounds2D.getWidth();
double height = bounds2D.getHeight();
if (viewBox != null && viewBox.usable()) {
Point2D dim = viewBox.getDimensionInPixel(map, height, width, scale, dpi);
if (Math.abs(dim.getX()) <= 0 || Math.abs(dim.getY()) <= 0) {
return null;
}
at = AffineTransform.getScaleInstance(dim.getX() / width,
dim.getY() / height);
fontCtx = new FontRenderContext(at, true, true);
tl = new TextLayout(text, font, fontCtx);
glyphOutline = tl.getOutline(at);
}
Rectangle2D gb = glyphOutline.getBounds2D();
at = AffineTransform.getTranslateInstance(-gb.getCenterX(), -gb.getCenterY());
return at.createTransformedShape(glyphOutline);
} catch (FontFormatException ex) {
Logger.getLogger(OnlineResource.class.getName()).log(Level.SEVERE, null, ex);
throw new ParameterException(ex);
}
}
@Override
public Shape getShape(ViewBox viewBox, Map<String,Object> map, Double scale,
Double dpi, RealParameter markIndex, String mimeType) throws ParameterException, IOException {
if (mimeType != null) {
if (mimeType.equalsIgnoreCase("application/x-font-ttf")) {
return getTrueTypeGlyph(viewBox, map, scale, dpi, markIndex);
}
}
throw new ParameterException("Unknown MIME type: " + mimeType);
}
//@Override
public void setJAXBSource(MarkGraphicType m) {
VariableOnlineResourceType o = new VariableOnlineResourceType();
o.setHref(SeParameterFactory.createParameterValueType(uri.toString()));
m.setOnlineResource(o);
}
@Override
public double getDefaultMaxWidth(Map<String,Object> map,
Double scale, Double dpi, RealParameter markIndex, String mimeType)
throws IOException, ParameterException {
if (mimeType != null) {
if (mimeType.equalsIgnoreCase("application/x-font-ttf")) {
return getTrueTypeGlyphMaxSize(map, /*scale, dpi,*/ markIndex);
}
}
return 0.0;
}
private double getTrueTypeGlyphMaxSize(Map<String,Object> map,
/*Double scale, Double dpi,*/ RealParameter markIndex)
throws IOException, ParameterException {
try {
InputStream iStream = uri.toURL().openStream();
Font font = Font.createFont(Font.TRUETYPE_FONT, iStream);
iStream.close();
double value = markIndex.getValue(map);
char[] data = {(char) value};
String text = String.copyValueOf(data);
// Scale is used to have an high resolution
AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
FontRenderContext fontCtx = new FontRenderContext(at, true, true);
TextLayout tl = new TextLayout(text, font, fontCtx);
Shape glyphOutline = tl.getOutline(at);
Rectangle2D bounds2D = glyphOutline.getBounds2D();
return Math.max(bounds2D.getWidth(), bounds2D.getHeight());
} catch (FontFormatException ex) {
Logger.getLogger(OnlineResource.class.getName()).log(Level.SEVERE, null, ex);
throw new ParameterException(ex);
}
}
@Override
public List<SymbolizerNode> getChildren() {
List<SymbolizerNode> ret = new ArrayList<SymbolizerNode>();
return ret;
}
}