/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.data.kml.map;
import com.vividsolutions.jts.geom.Geometry;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import javax.swing.JLabel;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.processing.Operations;
import org.geotoolkit.data.kml.model.AbstractGeometry;
import org.geotoolkit.data.kml.model.AbstractStyleSelector;
import org.geotoolkit.data.kml.model.BalloonStyle;
import org.geotoolkit.data.kml.model.BasicLink;
import org.geotoolkit.data.kml.model.Icon;
import org.geotoolkit.data.kml.model.IconStyle;
import org.geotoolkit.data.kml.model.Kml;
import org.geotoolkit.data.kml.model.KmlModelConstants;
import org.geotoolkit.data.kml.model.LabelStyle;
import org.geotoolkit.data.kml.model.LatLonAltBox;
import org.geotoolkit.data.kml.model.LatLonBox;
import org.geotoolkit.data.kml.model.LineString;
import org.geotoolkit.data.kml.model.LineStyle;
import org.geotoolkit.data.kml.model.LinearRing;
import org.geotoolkit.data.kml.model.MultiGeometry;
import org.geotoolkit.data.kml.model.Pair;
import org.geotoolkit.data.kml.model.Point;
import org.geotoolkit.data.kml.model.PolyStyle;
import org.geotoolkit.data.kml.model.Polygon;
import org.geotoolkit.data.kml.model.Region;
import org.geotoolkit.data.kml.model.Style;
import org.geotoolkit.data.kml.model.StyleMap;
import org.geotoolkit.data.kml.model.StyleState;
import org.geotoolkit.data.kml.model.Vec2;
import org.geotoolkit.data.kml.xml.KmlConstants;
import org.geotoolkit.data.kml.xsd.Cdata;
import org.geotoolkit.display.canvas.AbstractCanvas2D;
import org.geotoolkit.display.canvas.RenderingContext;
import org.geotoolkit.display.PortrayalException;
import org.geotoolkit.display2d.canvas.J2DCanvas;
import org.geotoolkit.display2d.canvas.RenderingContext2D;
import org.geotoolkit.display2d.container.stateless.StatelessContextParams;
import org.geotoolkit.display2d.primitive.GraphicJ2D;
import org.geotoolkit.display2d.primitive.jts.JTSGeometryJ2D;
import org.geotoolkit.display2d.style.labeling.DefaultLabelLayer;
import org.geotoolkit.display2d.style.labeling.DefaultPointLabelDescriptor;
import org.geotoolkit.display2d.style.labeling.LabelLayer;
import org.apache.sis.geometry.GeneralEnvelope;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.map.GraphicBuilder;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.coverage.grid.GridCoverageBuilder;
import org.geotoolkit.display.SearchArea;
import org.geotoolkit.display.VisitFilter;
import org.geotoolkit.display.canvas.Canvas;
import org.geotoolkit.display2d.primitive.ProjectedGeometry;
import org.opengis.display.primitive.Graphic;
import org.opengis.feature.Feature;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
/**
* Render KML layer in default geotoolkit rendering engine.
*
* @author Samuel Andrés
* @author Johann Sorel (Geomatys)
* @module
*/
final class KMLGraphicBuilder implements GraphicBuilder<GraphicJ2D> {
private static final int LEGEND_OFFSET = 10;
private static final int LEGEND_HEIGHT_EXT = 30;
private static final int LEGEND_WIDTH_EXT = 30;
private static final int LEGEND_HEIGHT_INT = 22;
private static final int LEGEND_WIDTH_INT = 22;
private static BufferedImage ICON_FOLDER;
private static BufferedImage ICON_PLACEMARK;
private static BufferedImage ICON_PLACEMARK_LINE_STRING;
private static BufferedImage ICON_PLACEMARK_LINEAR_RING;
private static BufferedImage ICON_PLACEMARK_POLYGON;
private static BufferedImage ICON_OVERLAY;
private static final Font FONT = new Font("KmlMapLayerFont", Font.ROMAN_BASELINE, 12);
private static FontMetrics FONT_METRICS;
/**
* One instance for all KML map layers. Object is concurrent.
*/
static final KMLGraphicBuilder INSTANCE;
static {
KMLGraphicBuilder builder = null;
try {
builder = new KMLGraphicBuilder();
} catch (IOException ex) {
Logging.getLogger("org.geotoolkit.data.kml.map").log(Level.SEVERE, "Error initializing KML graphic builder", ex);
}
INSTANCE = builder;
}
private KMLGraphicBuilder() throws IOException {
ICON_FOLDER = ImageIO.read(KmlMapLayer.class.getResourceAsStream("folder.png"));
ICON_PLACEMARK = ImageIO.read(KmlMapLayer.class.getResourceAsStream("flag.png"));
ICON_PLACEMARK_LINE_STRING = ImageIO.read(KmlMapLayer.class.getResourceAsStream("lineString.png"));
ICON_PLACEMARK_LINEAR_RING = ImageIO.read(KmlMapLayer.class.getResourceAsStream("linearRing.png"));
ICON_PLACEMARK_POLYGON = ImageIO.read(KmlMapLayer.class.getResourceAsStream("polygon.png"));
ICON_OVERLAY = ImageIO.read(KmlMapLayer.class.getResourceAsStream("overlay.png"));
// Font metrics initialization.
final Graphics2D g = (Graphics2D) new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics();
g.setFont(FONT);
FONT_METRICS = g.getFontMetrics();
}
@Override
public Collection<GraphicJ2D> createGraphics(MapLayer layer, Canvas canvas) {
if (layer instanceof KmlMapLayer && canvas instanceof AbstractCanvas2D) {
return Collections.singleton((GraphicJ2D) new KMLGraphic((J2DCanvas) canvas, (KmlMapLayer) layer));
} else {
return Collections.emptyList();
}
}
@Override
public Class<GraphicJ2D> getGraphicType() {
return GraphicJ2D.class;
}
@Override
public Image getLegend(MapLayer layer) throws PortrayalException {
final KmlMapLayer kmllayer = (KmlMapLayer) layer;
final KmlCache cache = new KmlCache(kmllayer.kml);
int width = 0, height = 0, y = 0;
final List<Image> images = new ArrayList<>();
try {
images.add(legendFeature(kmllayer.kml.getAbstractFeature(),cache));
} catch (IOException ex) {
throw new PortrayalException(ex);
}
for (Image img : images) {
width = Math.max(width, img.getWidth(null));
height += img.getHeight(null);
}
final BufferedImage image = new BufferedImage(
width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
for (Image img : images) {
graphic.drawImage(img, 0, y, null);
y += img.getHeight(null);
}
return image;
}
private static Image legendFeature(Feature feature, KmlCache cache) throws IOException {
Image image = null;
if (KmlModelConstants.TYPE_CONTAINER.isAssignableFrom(feature.getType())) {
image = legendContainer(feature,cache);
} else if (KmlModelConstants.TYPE_OVERLAY.isAssignableFrom(feature.getType())) {
image = legendOverlay(feature,cache);
} else if (feature.getType().equals(KmlModelConstants.TYPE_PLACEMARK)) {
image = legendPlacemark(feature,cache);
} else if (feature.getType().equals(KmlModelConstants.TYPE_NETWORK_LINK)) {
//return legendNetworkLink(feature);
}
return image;
}
private static Image legendContainer(Feature container, KmlCache cache) throws IOException {
Image image = null;
if (container.getType().equals(KmlModelConstants.TYPE_FOLDER)) {
image = legendFolder(container,cache);
} else if (container.getType().equals(KmlModelConstants.TYPE_DOCUMENT)) {
image = legendDocument(container,cache);
}
return image;
}
private static Image legendPlacemark(Feature placemark, KmlCache cache) throws IOException {
int nameWidth = 0;
legendCommonFeature(placemark,cache);
final String featureName = (String) placemark.getPropertyValue(KmlConstants.TAG_NAME);
if (featureName != null) {
nameWidth = FONT_METRICS.stringWidth(featureName);
}
// Apply styles
final Style s = retrieveStyle(placemark,cache);
final BufferedImage image = new BufferedImage(
LEGEND_WIDTH_EXT + nameWidth,
LEGEND_HEIGHT_EXT,
BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
graphic.setFont(FONT);
final AbstractGeometry geometry = (AbstractGeometry) placemark.getPropertyValue(KmlConstants.TAG_GEOMETRY);
if (s != null && s.getIconStyle() != null) {
final IconStyle iconStyle = s.getIconStyle();
final BasicLink bl = iconStyle.getIcon();
if (bl != null) {
if (bl.getHref() != null) {
final URL img = new URL(bl.getHref());
final BufferedImage buff = ImageIO.read(img);
graphic.drawImage(buff, 0, 4, LEGEND_WIDTH_INT, LEGEND_HEIGHT_INT, null);
}
}
} else if (geometry instanceof LineString) {
graphic.drawImage(ICON_PLACEMARK_LINE_STRING, 0, 4, null);
} else if (geometry instanceof LinearRing) {
graphic.drawImage(ICON_PLACEMARK_LINEAR_RING, 0, 4, null);
} else if (geometry instanceof Polygon) {
graphic.drawImage(ICON_PLACEMARK_POLYGON, 0, 4, null);
} else {
graphic.drawImage(ICON_PLACEMARK, 0, 4, null);
}
if (featureName != null) {
graphic.setColor(Color.BLACK);
graphic.drawString(featureName, LEGEND_WIDTH_EXT, LEGEND_HEIGHT_INT);
}
return image;
}
private static void legendCommonFeature(Feature feature, KmlCache cache) {
for (final Object value : (Iterable<?>) feature.getPropertyValue(KmlConstants.TAG_STYLE_SELECTOR)) {
indexStyleSelector((AbstractStyleSelector) value, cache);
}
}
private static void legendCommonContainer(Feature container, KmlCache cache) {
legendCommonFeature(container,cache);
}
private static Image legendFolder(Feature folder, KmlCache cache) throws IOException {
int width = 0, height = ICON_FOLDER.getHeight(), y = ICON_FOLDER.getHeight();
final List<Image> images = new ArrayList<>();
int nameWidth = 0;
legendCommonContainer(folder,cache);
final String featureName = (String) folder.getPropertyValue(KmlConstants.TAG_NAME);
if (featureName != null) {
nameWidth = FONT_METRICS.stringWidth(featureName);
}
Iterator<?> i = ((Iterable<?>) folder.getPropertyValue(KmlConstants.TAG_FEATURES)).iterator();
while (i.hasNext()) {
images.add(legendFeature((Feature) i.next(), cache));
}
for (Image img : images) {
width = Math.max(width, img.getWidth(null));
height += img.getHeight(null);
}
width = Math.max(width + LEGEND_OFFSET, LEGEND_WIDTH_EXT + nameWidth);
final BufferedImage image = new BufferedImage(
width,
height,
BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
graphic.drawImage(ICON_FOLDER, 0, 4, null);
for (Image img : images) {
graphic.drawImage(img, LEGEND_OFFSET, y, null);
y += img.getHeight(null);
}
if (featureName != null) {
graphic.setColor(Color.BLACK);
graphic.drawString(featureName, LEGEND_WIDTH_EXT, LEGEND_HEIGHT_INT);
}
return image;
}
private static Image legendDocument(Feature document, KmlCache cache) throws IOException {
int width = 0, height = 0, y = 0;
List<Image> images = new ArrayList<>();
legendCommonContainer(document,cache);
Iterator<?> i = ((Iterable<?>) document.getPropertyValue(KmlConstants.TAG_FEATURES)).iterator();
while (i.hasNext()) {
images.add(legendFeature((Feature) i.next(), cache));
}
for (Image img : images) {
width = Math.max(width, img.getWidth(null));
height += img.getHeight(null);
}
final BufferedImage image = new BufferedImage(width + LEGEND_OFFSET, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
for (Image img : images) {
graphic.drawImage(img, LEGEND_OFFSET, y, null);
y += img.getHeight(null);
}
return image;
}
private static Image legendOverlay(Feature overlay, KmlCache cache) throws IOException {
Image image = null;
if (overlay.getType().equals(KmlModelConstants.TYPE_GROUND_OVERLAY)) {
image = legendGroundOverlay(overlay,cache);
} else if (overlay.getType().equals(KmlModelConstants.TYPE_SCREEN_OVERLAY)) {
image = legendScreenOverlay(overlay,cache);
// } else if (overlay.getType().equals(KmlModelConstants.TYPE_PHOTO_OVERLAY)){
// portrayPhotoOverlay(overlay);
}
return image;
}
private static Image legendGroundOverlay(Feature groundOverlay, KmlCache cache) throws IOException {
int nameWidth = 0;
final String featureName = (String) groundOverlay.getPropertyValue(KmlConstants.TAG_NAME);
if (featureName != null) {
nameWidth = FONT_METRICS.stringWidth(featureName);
}
final BufferedImage image = new BufferedImage(
LEGEND_WIDTH_EXT + nameWidth,
LEGEND_HEIGHT_EXT,
BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
graphic.drawImage(ICON_OVERLAY, 0, 4, null);
if (featureName != null) {
graphic.setColor(Color.BLACK);
graphic.drawString(featureName, LEGEND_WIDTH_EXT, LEGEND_HEIGHT_INT);
}
return image;
}
private static Image legendScreenOverlay(Feature screenOverlay, KmlCache cache) throws IOException {
int nameWidth = 0;
final String featureName = (String) screenOverlay.getPropertyValue(KmlConstants.TAG_NAME);
if (featureName != null) {
nameWidth = FONT_METRICS.stringWidth(featureName);
}
final BufferedImage image = new BufferedImage(
LEGEND_WIDTH_EXT + nameWidth,
LEGEND_HEIGHT_EXT,
BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphic = (Graphics2D) image.getGraphics();
graphic.drawImage(ICON_OVERLAY, 0, 4, null);
if (featureName != null) {
graphic.setColor(Color.BLACK);
graphic.drawString(featureName, LEGEND_WIDTH_EXT, LEGEND_HEIGHT_INT);
}
return image;
}
/*
* -------------------------------------------------------------------------
* RETRIEVE STYLES METHODS
* -------------------------------------------------------------------------
*/
private static Style retrieveStyle(Feature feature, KmlCache cache) {
Style styleSelector = null;
final Iterator<?> i = ((Iterable<?>) feature.getPropertyValue(KmlConstants.TAG_STYLE_SELECTOR)).iterator();
if (i.hasNext()) {
final Object value = i.next();
if (value instanceof Style) {
styleSelector = (Style) value;
} else if (value instanceof StyleMap) {
styleSelector = retrieveStyle((StyleMap) styleSelector, StyleState.NORMAL,cache);
}
} else {
final Object value = feature.getPropertyValue(KmlConstants.TAG_STYLE_URL);
if (value != null) {
final String uri = value.toString();
styleSelector = cache.styles.get(uri);
if (styleSelector == null) {
final StyleMap styleMap = cache.styleMaps.get(uri);
styleSelector = retrieveStyle(styleMap, StyleState.NORMAL,cache);
}
}
}
return styleSelector;
}
private static Style retrieveStyle(StyleMap styleMap, StyleState styleState, KmlCache cache) {
Style s = null;
if (styleMap != null) {
for (Pair pair : styleMap.getPairs()) {
if (styleState.equals(pair.getKey())) {
final AbstractStyleSelector styleSelector = pair.getAbstractStyleSelector();
if (styleSelector instanceof StyleMap) {
s = retrieveStyle((StyleMap) styleSelector, styleState,cache);
} else if (styleSelector != null) {
s = (Style) styleSelector;
break;
}
if (s == null) {
s = cache.styles.get(pair.getStyleUrl().toString());
if (s == null
&& cache.styleMaps.get(pair.getStyleUrl().toString()) != null) {
s = retrieveStyle(cache.styleMaps.get(pair.getStyleUrl().toString()), styleState,cache);
}
}
}
}
}
return s;
}
/*
* -------------------------------------------------------------------------
* INDEX STYLES METHODS
* -------------------------------------------------------------------------
*/
private static void indexStyleSelector(AbstractStyleSelector styleSelector, KmlCache cache) {
if (styleSelector instanceof Style) {
indexStyle((Style) styleSelector, cache);
} else if (styleSelector instanceof StyleMap) {
indexStyleMap((StyleMap) styleSelector, cache);
}
}
private static void indexStyle(Style style, KmlCache cache) {
if (style.getIdAttributes().getId() != null) {
cache.styles.put("#" + style.getIdAttributes().getId(), style);
}
}
private static void indexStyleMap(StyleMap styleMap, KmlCache cache) {
if (styleMap.getIdAttributes().getId() != null) {
cache.styleMaps.put("#" + styleMap.getIdAttributes().getId(), styleMap);
}
}
private static class KmlCache{
final Kml kml;
final Map<String, Style> styles = new HashMap<>();
final Map<String, StyleMap> styleMaps = new HashMap<>();
KmlCache(Kml kml){
this.kml = kml;
}
}
private static class KMLGraphic extends GraphicJ2D {
private final KmlCache cache;
private RenderingContext2D context2d;
private KMLGraphic(J2DCanvas canvas, KmlMapLayer layer) {
super(canvas);
cache = new KmlCache(layer.kml);
}
@Override
public void paint(RenderingContext2D context2D) {
this.context2d = context2D;
cache.styles.clear();
cache.styleMaps.clear();
try {
portrayKml(cache.kml);
context2D.getLabelRenderer(true).portrayLabels();
} catch (TransformException | IOException ex) {
Logging.getLogger("org.geotoolkit.data.kml.map").log(Level.SEVERE, null, ex);
}
cache.styles.clear();
cache.styleMaps.clear();
}
@Override
public List<Graphic> getGraphicAt(RenderingContext context, SearchArea mask, VisitFilter filter, List<Graphic> graphics) {
return graphics;
}
private void portrayKml(Kml kml) throws IOException {
if (kml.getAbstractFeature() != null) {
portrayFeature(kml.getAbstractFeature());
}
}
private void portrayFeature(Feature feature) throws IOException {
if (KmlModelConstants.TYPE_CONTAINER.isAssignableFrom(feature.getType())) {
portrayAbstractContainer(feature);
} else if (KmlModelConstants.TYPE_OVERLAY.isAssignableFrom(feature.getType())) {
portrayOverlay(feature);
} else if (feature.getType().equals(KmlModelConstants.TYPE_PLACEMARK)) {
portrayPlacemark(feature);
}
}
private void portrayCommonFeature(Feature feature) {
Iterator<?> i = ((Iterable<?>) feature.getPropertyValue(KmlConstants.TAG_STYLE_SELECTOR)).iterator();
while (i.hasNext()) {
indexStyleSelector((AbstractStyleSelector) i.next(), cache);
}
}
private void portrayPlacemark(Feature placemark) throws IOException {
portrayCommonFeature(placemark);
context2d.switchToObjectiveCRS();
// Apply styles
final Style s = retrieveStyle(placemark,cache);
com.vividsolutions.jts.geom.Point centroid = null;
// display geometries
final AbstractGeometry geometry = (AbstractGeometry) placemark.getPropertyValue(KmlConstants.TAG_GEOMETRY);
if (geometry != null) {
portrayGeometry(geometry, s);
if (geometry instanceof Geometry) {
centroid = ((Geometry) geometry).getCentroid();
} else if (geometry instanceof MultiGeometry) {
centroid = ((MultiGeometry) geometry).getCentroid();
}
}
double x = Double.NaN;
double y = Double.NaN;
if (centroid != null) {
x = centroid.getX();
y = centroid.getY();
} else {
final Region region = ((Region) placemark.getPropertyValue(KmlConstants.TAG_REGION));
if (region != null) {
final LatLonAltBox latLonAltBox = region.getLatLonAltBox();
x = (latLonAltBox.getEast() + latLonAltBox.getWest()) / 2;
y = (latLonAltBox.getNorth() + latLonAltBox.getSouth()) / 2;
}
}
if (x != Double.NaN && y != Double.NaN) {
portrayBalloonStyle(x, y, s, false, placemark);
portrayLabelStyle(x, y, s,
(String) placemark.getPropertyValue(KmlConstants.TAG_NAME),
centroid);
if (false) {
portrayFlag(x, y); // portray flag at Placemark center (set off)
}
}
}
private void portrayAbstractContainer(Feature container) throws IOException {
if (container.getType().equals(KmlModelConstants.TYPE_FOLDER)) {
portrayFolder(container);
} else if (container.getType().equals(KmlModelConstants.TYPE_DOCUMENT)) {
portrayDocument(container);
}
}
private void portrayCommonContainer(Feature container) {
portrayCommonFeature(container);
}
private void portrayFolder(Feature folder) throws IOException {
portrayCommonContainer(folder);
Iterator<?> i = ((Iterable<?>) folder.getPropertyValue(KmlConstants.TAG_FEATURES)).iterator();
while (i.hasNext()) {
portrayFeature((Feature) i.next());
}
}
private void portrayDocument(Feature document) throws IOException {
portrayCommonContainer(document);
Iterator<?> i = ((Iterable<?>) document.getPropertyValue(KmlConstants.TAG_FEATURES)).iterator();
while (i.hasNext()) {
portrayFeature((Feature) i.next());
}
}
private void portrayOverlay(Feature overlay) throws IOException {
if (overlay.getType().equals(KmlModelConstants.TYPE_GROUND_OVERLAY)) {
portrayGroundOverlay(overlay);
} else if (overlay.getType().equals(KmlModelConstants.TYPE_SCREEN_OVERLAY)) {
portrayScreenOverlay(overlay);
// } else if (overlay.getType().equals(KmlModelConstants.TYPE_PHOTO_OVERLAY)){
// portrayPhotoOverlay(overlay);
}
}
private void portrayCommonOverlay(Feature overlay) {
portrayCommonFeature(overlay);
}
private void portrayGroundOverlay(Feature groundOverlay) throws IOException {
portrayCommonOverlay(groundOverlay);
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
// Display image
final Icon icon = (Icon) groundOverlay.getPropertyValue(KmlConstants.TAG_ICON);
final URL iconURL = new URL(icon.getHref());
final BufferedImage image = ImageIO.read(iconURL);
final LatLonBox latLonBox = (LatLonBox) groundOverlay.getPropertyValue(KmlConstants.TAG_LAT_LON_BOX);
final double north = latLonBox.getNorth();
final double east = latLonBox.getEast();
final double south = latLonBox.getSouth();
final double west = latLonBox.getWest();
final GeneralEnvelope envelope = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
envelope.setRange(0, west, east);
envelope.setRange(1, south, north);
final GridCoverageBuilder gcb = new GridCoverageBuilder();
gcb.setEnvelope(envelope);
gcb.setRenderedImage(image);
final GridCoverage2D coverage = gcb.getGridCoverage2D();
final GridCoverage2D resampled = (GridCoverage2D) Operations.DEFAULT.resample(coverage, context2d.getObjectiveCRS2D());
context2d.switchToObjectiveCRS();
final RenderedImage renderedImg = resampled.getRenderedImage();
final MathTransform2D trs2D = resampled.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT);
if (trs2D instanceof AffineTransform) {
graphic.drawRenderedImage(renderedImg, (AffineTransform) trs2D);
}
// Apply styles
final Style localStyle = retrieveStyle(groundOverlay,cache);
portrayBalloonStyle((east + west) / 2, (north + south) / 2, localStyle, false, groundOverlay);
}
private void portrayScreenOverlay(Feature screenOverlay) throws IOException {
portrayCommonOverlay(screenOverlay);
Icon icon = (Icon) screenOverlay.getPropertyValue(KmlConstants.TAG_ICON);
final URL img = new URL(icon.getHref());
context2d.switchToDisplayCRS();
final BufferedImage image = ImageIO.read(img);
final Graphics2D graphic = context2d.getGraphics();
final Vec2 overlayXY = (Vec2) screenOverlay.getPropertyValue(KmlConstants.TAG_OVERLAY_XY);
final Vec2 screenXY = (Vec2) screenOverlay.getPropertyValue(KmlConstants.TAG_SCREEN_XY);
final Vec2 size = (Vec2) screenOverlay.getPropertyValue(KmlConstants.TAG_SIZE);
final int width = (int) size.getX();
final int height = (int) size.getY();
final double coeffX = image.getWidth() / size.getX();
final double coeffY = image.getHeight() / size.getY();
final int x = (int) screenXY.getX() - (int) (overlayXY.getX() / coeffX);
final int y = context2d.getCanvasDisplayBounds().height
- (int) screenXY.getY() - height - (int) (overlayXY.getY() / coeffY);
graphic.drawImage(image, x, y, width, height, null);
// Apply styles
final Style localStyle = retrieveStyle(screenOverlay,cache);
portrayBalloonStyle(x + width, y, localStyle, true, screenOverlay);
}
private void portrayGeometry(AbstractGeometry geometry, Style style) throws IOException {
if (geometry instanceof MultiGeometry) {
portrayMultiGeometry((MultiGeometry) geometry, style);
} else if (geometry instanceof LineString) {
portrayLineString((LineString) geometry, style);
} else if (geometry instanceof Polygon) {
portrayPolygon((Polygon) geometry, style);
} else if (geometry instanceof Point) {
portrayPoint((Point) geometry, style);
} else if (geometry instanceof LinearRing) {
portrayLinearRing((LinearRing) geometry, style);
}
}
private void portrayLineString(LineString lineString, Style style) {
// MathTransform
MathTransform transform = null;
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
com.vividsolutions.jts.geom.Geometry ls = null;
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
ls = JTS.transform((com.vividsolutions.jts.geom.LineString) lineString, transform);
} catch (MismatchedDimensionException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
final LineStyle lineStyle = style.getLineStyle();
if (lineStyle != null) {
graphic.setColor(lineStyle.getColor());
graphic.setStroke(new BasicStroke((float) (lineStyle.getWidth())));
}
final Shape shape = new JTSGeometryJ2D(ls);
graphic.draw(shape);
}
private void portrayMultiGeometry(MultiGeometry multiGeometry, Style style) throws IOException {
for (AbstractGeometry geometry : multiGeometry.getGeometries()) {
portrayGeometry(geometry, style);
}
}
private void portrayPolygon(Polygon polygon, Style style) {
// MathTransform
MathTransform transform = null;
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
com.vividsolutions.jts.geom.Geometry pol = null;
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
pol = JTS.transform((com.vividsolutions.jts.geom.Polygon) polygon, transform);
} catch (MismatchedDimensionException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
final Shape shape = new JTSGeometryJ2D(pol);
// Apply styles
if (style != null) {
final PolyStyle polyStyle = style.getPolyStyle();
if (polyStyle != null) {
graphic.setColor(polyStyle.getColor());
graphic.setStroke(new BasicStroke((float) 0.05));
if (style.getPolyStyle().getFill()) {
graphic.fill(shape);
}
if (polyStyle.getOutline() && style.getLineStyle() != null) {
final LineStyle lineStyle = style.getLineStyle();
graphic.setColor(lineStyle.getColor());
graphic.draw(shape);
}
}
}
}
private void portrayPoint(Point point, Style style) throws IOException {
// MathTransform
MathTransform transform;
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
// Apply styles
if (style != null) {
final IconStyle iconStyle = style.getIconStyle();
if (iconStyle != null) {
graphic.setColor(iconStyle.getColor());
final BasicLink icon = iconStyle.getIcon();
final URL img = new URL(icon.getHref());
final BufferedImage image = ImageIO.read(img);
com.vividsolutions.jts.geom.Point p = (com.vividsolutions.jts.geom.Point) point;
final double[] tab = new double[]{p.getX(), p.getY()};
try {
transform.transform(tab, 0, tab, 0, 1);
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
graphic.drawImage(image, (int) tab[0], (int) tab[1], null);
}
}
}
private void portrayLinearRing(LinearRing linearRing, Style style) {
// MathTransform
MathTransform transform = null;
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
com.vividsolutions.jts.geom.Geometry lr = null;
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
lr = JTS.transform((com.vividsolutions.jts.geom.LinearRing) linearRing, transform);
} catch (MismatchedDimensionException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
if (style != null) {
final LineStyle lineStyle = style.getLineStyle();
if (lineStyle != null) {
graphic.setColor(lineStyle.getColor());
graphic.setStroke(new BasicStroke((float) (lineStyle.getWidth())));
}
}
final Shape shape = new JTSGeometryJ2D(lr);
graphic.draw(shape);
}
private void portrayBalloonStyle(double x, double y, Style style,
boolean fixedToScreen, Feature informationResource)
{
// MathTransform
MathTransform transform = null;
//Fixed to screen for ScrenOverlays
if (!fixedToScreen) {
try {
transform = context2d.getMathTransform(
CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
}
context2d.switchToDisplayCRS();
final Graphics2D graphic = context2d.getGraphics();
final int linewidth = FONT_METRICS.getHeight();
if (style != null) {
final BalloonStyle balloonStyle = style.getBalloonStyle();
if (balloonStyle != null) {
final int length = balloonStyle.getText().toString().length();
int begin = 0, interligne = 0, balloonWidth = 0, balloonLines = 0;
final boolean cdata = balloonStyle.getText() instanceof Cdata;
JLabel jep = null;
// balloon general dimensions
do {
final int end = Math.min(begin + linewidth, length);
balloonWidth = Math.max(balloonWidth,
FONT_METRICS.stringWidth(balloonStyle.getText().toString().substring(begin, end)));
begin += linewidth;
balloonLines++;
} while (begin + linewidth < length);
if (begin < length) {
balloonLines++;
}
final double[] tab = new double[]{x, y};
// Fixed to screen for ScrenOverlays
if (!fixedToScreen) {
try {
transform.transform(tab, 0, tab, 0, 1);
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
}
// acurate dimensions / position
int left, right, top, bottom, vExc, hExc, coeff;
if (cdata) {
String content = retrieveBalloonInformations(balloonStyle.getText().toString(), informationResource);
if ("<html>".equals(content.substring(0, 5))) {
jep = new JLabel(content);
} else {
jep = new JLabel("<html>" + content + "</html>");
}
jep.setOpaque(false);
Dimension preferredDim = jep.getPreferredSize();
jep.setSize(preferredDim);
coeff = (preferredDim.height + preferredDim.width) / 4;
left = (int) tab[0] - (preferredDim.width + 2) / 2;
right = left + preferredDim.width + 1;
top = (int) tab[1] - (preferredDim.height + 2) - coeff;
bottom = (int) tab[1] - coeff;
right = left + preferredDim.width + 1;
vExc = (preferredDim.width + 2) / 10;
hExc = (preferredDim.height + 2) / 7;
} else {
coeff = ((balloonLines + 1) * FONT_METRICS.getHeight() + balloonWidth) / 4;
left = (int) tab[0] - (balloonWidth + 2) / 2;
right = (int) tab[0] + (balloonWidth + 2) / 2;
top = (int) tab[1] - ((balloonLines + 1) * FONT_METRICS.getHeight() + 2) - coeff;
bottom = (int) tab[1] - coeff;
vExc = (balloonWidth + 2) / 10;
hExc = (balloonLines * FONT_METRICS.getHeight() + 2) / 7;
}
// Print balloon structure
final Path2D p = new java.awt.geom.Path2D.Double();
p.moveTo(left, top);
// top and right sides
p.curveTo(left + (right - left) / 9, top - vExc,
left + 8 * (right - left) / 9, top - vExc, right, top);
p.curveTo(right + hExc, bottom + 5 * (top - bottom) / 6,
right + hExc, bottom + (top - bottom) / 6, right, bottom);
// bottom
p.curveTo(left + 8 * (right - left) / 9, bottom + vExc,
left + 7 * (right - left) / 9, bottom,
left + 2 * (right - left) / 3, bottom + vExc);
p.lineTo((int) tab[0], (int) tab[1]);
p.lineTo(left + (right - left) / 3, bottom + vExc);
p.curveTo(left + 2 * (right - left) / 9, bottom,
left + (right - left) / 9, bottom + vExc, left, bottom);
// left side
p.curveTo(left - hExc, bottom + (top - bottom) / 6, left - hExc,
bottom + 5 * (top - bottom) / 6, left, top);
p.closePath();
// balloon color
graphic.setColor(balloonStyle.getBgColor());
graphic.fill(p);
graphic.setColor(new Color(40, 40, 40));
graphic.draw(p);
// print balloon text
graphic.setColor(balloonStyle.getTextColor());
if (cdata) {
graphic.translate(left, top);
jep.paint(graphic);
graphic.translate(-left, -top);
} else {
begin = 0;
interligne = FONT_METRICS.getHeight();
while (begin + linewidth < length) {
graphic.drawString(
balloonStyle.getText().toString().substring(
begin, begin + linewidth), left + 1, top + interligne);
begin += linewidth;
interligne += FONT_METRICS.getHeight();
}
if (begin < length) {
graphic.drawString(
balloonStyle.getText().toString().substring(
begin, length), left + 1, top + interligne);
}
}
}
}
}
private void portrayLabelStyle(double x, double y, Style style, String content, Geometry geom) {
if (content == null) {
return;
}
// MathTransform
MathTransform transform;
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
if (style != null) {
final LabelStyle labelStyle = style.getLabelStyle();
if (labelStyle != null) {
// graphic.setFont(new Font(FONT.getName(),
// FONT.getStyle(), FONT.getSize() * (int) labelStyle.getScale()));
// graphic.setColor(labelStyle.getColor());
// double[] tab = new double[]{x, y};
// try {
// transform.transform(tab, 0, tab, 0, 1);
// } catch (TransformException ex) {
// context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
// return;
// }
final StatelessContextParams params = new StatelessContextParams(null, null);
params.update(context2d);
final ProjectedGeometry projectedGeometry = new ProjectedGeometry(params);
projectedGeometry.setDataGeometry(geom, null);
final LabelLayer labelLayer = new DefaultLabelLayer(false, false);
// Paint textPaint;
// textPaint.
final float displayFactor = 0.1f;//0.1f pour corriger l'erreur d'affichage (devrait être fixé normalement à 1)
labelLayer.labels().add(
new DefaultPointLabelDescriptor(content,
new Font(FONT.getName(),
FONT.getStyle(), FONT.getSize() * (int) labelStyle.getScale()),
labelStyle.getColor(), 0, null, 0.5f, 0.5f,
(float) x * displayFactor, (float) y * displayFactor,
0, context2d.getObjectiveCRS(), projectedGeometry));
context2d.getLabelRenderer(true).append(labelLayer);
//graphic.drawString(content, (int) tab[0], (int) tab[1]);
}
}
}
private void portrayFlag(double x, double y) throws IOException {
MathTransform transform;
final Graphics2D graphic = context2d.getGraphics();
try {
transform = context2d.getMathTransform(CommonCRS.WGS84.normalizedGeographic(), context2d.getDisplayCRS());
} catch (FactoryException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
final BufferedImage image = ICON_PLACEMARK;
double[] tab = new double[]{x, y};
try {
transform.transform(tab, 0, tab, 0, 1);
} catch (TransformException ex) {
context2d.getMonitor().exceptionOccured(ex, Level.WARNING);
return;
}
graphic.drawImage(image, (int) tab[0], (int) tab[1] - LEGEND_HEIGHT_INT,
LEGEND_WIDTH_INT, LEGEND_HEIGHT_INT, null);
}
private String retrieveBalloonInformations(String toInspect, Feature informationResource) {
Object value = informationResource.getPropertyValue(KmlConstants.TAG_NAME);
if (value != null) {
toInspect = toInspect.replaceAll("\\$\\[" + KmlConstants.TAG_NAME + "\\]", value.toString());
}
value = informationResource.getPropertyValue(KmlConstants.TAG_DESCRIPTION);
if (value != null) {
toInspect = toInspect.replaceAll("\\$\\[" + KmlConstants.TAG_DESCRIPTION + "\\]", value.toString());
}
return toInspect;
}
@Override
public Object getUserObject() {
return null;
}
@Override
public Envelope getEnvelope() {
return null;
}
}
}