/*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import org.geotoolkit.nio.ZipUtilities;
import org.geotoolkit.storage.coverage.CoverageReference;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.io.CoverageReader;
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.Boundary;
import org.geotoolkit.data.kml.model.EnumAltitudeMode;
import org.geotoolkit.data.kml.model.Icon;
import org.geotoolkit.data.kml.model.Kml;
import org.geotoolkit.data.kml.model.LabelStyle;
import org.geotoolkit.data.kml.model.LatLonBox;
import org.geotoolkit.data.kml.model.LineStyle;
import org.geotoolkit.data.kml.model.PolyStyle;
import org.geotoolkit.data.kml.model.Style;
import org.geotoolkit.data.kml.xml.KmlWriter;
import org.geotoolkit.display2d.GO2Utilities;
import org.geotoolkit.map.CoverageMapLayer;
import org.geotoolkit.map.FeatureMapLayer;
import org.geotoolkit.map.MapContext;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CommonCRS;
import org.geotoolkit.style.MutableFeatureTypeStyle;
import org.geotoolkit.style.MutableRule;
import org.geotoolkit.style.MutableStyle;
import org.opengis.filter.expression.Expression;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.style.ExtensionSymbolizer;
import org.opengis.style.LineSymbolizer;
import org.opengis.style.PointSymbolizer;
import org.opengis.style.PolygonSymbolizer;
import org.opengis.style.RasterSymbolizer;
import org.opengis.style.Rule;
import org.opengis.style.Symbolizer;
import org.opengis.style.TextSymbolizer;
import static java.nio.file.StandardOpenOption.*;
import static java.nio.file.StandardOpenOption.CREATE;
import org.opengis.feature.Feature;
import org.opengis.feature.PropertyType;
import org.geotoolkit.data.kml.xml.KmlConstants;
/**
*
* @author Samuel Andrés
* @module
*/
public class KmzContextInterpreter {
private static final KmlFactory KML_FACTORY = DefaultKmlFactory.getInstance();
private static final AtomicInteger increment = new AtomicInteger();
private static final List<Entry<Rule, URI>> IDENTIFICATORS_MAP = new ArrayList<>();
private final Path tempDirectory;
private final Path filesDirectory;
public KmzContextInterpreter() throws IOException {
tempDirectory = Files.createTempDirectory("geotk_kmz");
filesDirectory = tempDirectory.resolve("files");
Files.createDirectories(filesDirectory);
}
public void writeKmz(MapContext context, Path kmzOutput)
throws Exception {
final Kml kml = KML_FACTORY.createKml();
final Feature folder = KML_FACTORY.createFolder();
kml.setAbstractFeature(folder);
// Creating KML file
final Path docKml = tempDirectory.resolve("doc.kml");
final List<Feature> fs = new ArrayList<>();
for (final MapLayer layer : context.layers()) {
this.writeStyle(layer.getStyle(), folder);
if (layer instanceof CoverageMapLayer) {
fs.add(writeCoverageMapLayer((CoverageMapLayer) layer));
} else if (layer instanceof FeatureMapLayer) {
fs.add(writeFeatureMapLayer((FeatureMapLayer) layer));
}
}
folder.setPropertyValue(KmlConstants.TAG_FEATURES, fs);
// Writing KML file
final KmlWriter writer = new KmlWriter();
writer.setOutput(docKml);
writer.write(kml);
writer.dispose();
// Creating KMZ
ZipUtilities.zip(kmzOutput, ZipOutputStream.DEFLATED, 9, null, filesDirectory, docKml);
}
/**
* Retrieves a style identifier.
*/
private String getIdentificator() {
return "id" + increment.incrementAndGet();
}
//--------------------------------------------------------------------------
// STYLES CONVERSION METHODS
//--------------------------------------------------------------------------
/**
* Writes KML styles elements mapping a layer style.
*
* <p style="color: red; font-weight: bold; font-style: italic;">BE CAREFUL:
* SLD Styles reference thei associated features BUT KML specification is different
* because each feature references its own style.</p>
*/
private Feature writeStyle(MutableStyle style, Feature container) throws URISyntaxException {
final List<MutableFeatureTypeStyle> featureTypeStyles = style.featureTypeStyles();
for (int i = 0, num = featureTypeStyles.size(); i < num; i++) {
container = this.writeFeatureTypeStyle(featureTypeStyles.get(i), container);
}
return container;
}
/**
* Writes a feature type style.
*/
private Feature writeFeatureTypeStyle(MutableFeatureTypeStyle featureTypeStyle, Feature container)
throws URISyntaxException
{
final List<MutableRule> rules = featureTypeStyle.rules();
final List<AbstractStyleSelector> fs = new ArrayList<>();
for (int i = 0, num = rules.size(); i < num; i++) {
fs.add(this.writeRule(rules.get(i)));
}
container.setPropertyValue(KmlConstants.TAG_STYLE_SELECTOR, fs);
return container;
}
/**
* Retrieves a KML StyleSelector element mapping SLD Rule.
*/
private AbstractStyleSelector writeRule(MutableRule rule) throws URISyntaxException {
final Style styleSelector = KML_FACTORY.createStyle();
final List<Symbolizer> symbolizers = rule.symbolizers();
for (int i = 0, num = symbolizers.size(); i < num; i++) {
this.writeSymbolizer(symbolizers.get(i), styleSelector);
}
// Links rule filter with Style URI
final String id = this.getIdentificator();
styleSelector.setIdAttributes(KML_FACTORY.createIdAttributes(id, null));
IDENTIFICATORS_MAP.add(new SimpleEntry<Rule, URI>(rule, new URI("#" + id)));
return styleSelector;
}
/**
* Writes KML color styles mapping SLD Symbolizers.
* Color styles are written into KML Style selector.
*/
private AbstractStyleSelector writeSymbolizer(
Symbolizer symbolizer, Style styleSelector) {
if (symbolizer instanceof ExtensionSymbolizer) {
}
// LineSymbolizer mapping
else if (symbolizer instanceof LineSymbolizer) {
final LineSymbolizer lineSymbolizer = (LineSymbolizer) symbolizer;
final LineStyle lineStyle = ((styleSelector.getLineStyle() == null)
? KML_FACTORY.createLineStyle() : styleSelector.getLineStyle());
lineStyle.setWidth((Double) this.writeExpression(
lineSymbolizer.getStroke().getWidth(), Double.class, null));
lineStyle.setColor((Color) this.writeExpression(
lineSymbolizer.getStroke().getColor(), Color.class, null));
styleSelector.setLineStyle(lineStyle);
}
// PointSymbolizezr mapping
else if (symbolizer instanceof PointSymbolizer) {
// PointSymbolizer pointSymbolizer = (PointSymbolizer) symbolizer;
// IconStyle iconStyle = KML_FACTORY.createIconStyle();
// GraphicalSymbol gs = ((GraphicalSymbol) pointSymbolizer.getGraphic().graphicalSymbols().get(0));
// gs.
}
// PolygonSymbolizer mapping
else if (symbolizer instanceof PolygonSymbolizer) {
final PolygonSymbolizer polygonSymbolizer = (PolygonSymbolizer) symbolizer;
final PolyStyle polyStyle = KML_FACTORY.createPolyStyle();
// Fill
if (polygonSymbolizer.getFill() == null) {
polyStyle.setFill(false);
} else {
polyStyle.setFill(true);
polyStyle.setColor((Color) this.writeExpression(
polygonSymbolizer.getFill().getColor(), Color.class, null));
}
// Outline
if (polygonSymbolizer.getStroke() == null) {
polyStyle.setOutline(false);
} else if(styleSelector.getLineStyle() == null) {
polyStyle.setOutline(true);
final LineStyle lineStyle = KML_FACTORY.createLineStyle();
lineStyle.setColor((Color) this.writeExpression(
polygonSymbolizer.getStroke().getColor(), Color.class, null));
lineStyle.setWidth((Double) this.writeExpression(
polygonSymbolizer.getStroke().getWidth(), Double.class, null));
styleSelector.setLineStyle(lineStyle);
}
styleSelector.setPolyStyle(polyStyle);
} else if (symbolizer instanceof RasterSymbolizer) {
} else if (symbolizer instanceof TextSymbolizer) {
final TextSymbolizer textSymbolizer = (TextSymbolizer) symbolizer;
final LabelStyle labelStyle = KML_FACTORY.createLabelStyle();
if (textSymbolizer.getFont() != null) {
textSymbolizer.getFont().getSize();
}
if (textSymbolizer.getFill() != null) {
labelStyle.setColor((Color) this.writeExpression(
textSymbolizer.getFill().getColor(), Color.class, null));
}
styleSelector.setLabelStyle(labelStyle);
}
return styleSelector;
}
/**
* Writes a static expression.
*/
private Object writeExpression(Expression expression, Class<?> type, Object object) {
if (GO2Utilities.isStatic(Expression.NIL)) {
return expression.evaluate(object, type);
}
return null;
}
//--------------------------------------------------------------------------
// FEATURES TRANSFORMATIONS
//--------------------------------------------------------------------------
/**
* Transforms a FeatureMapLAyer in KML Folder.
*/
private Feature writeFeatureMapLayer(final FeatureMapLayer featureMapLayer) throws URISyntaxException {
final Feature folder = KML_FACTORY.createFolder();
final List<Feature> fs = new ArrayList<>();
for (final Feature f : featureMapLayer.getCollection()) {
fs.add(writeFeature(f));
}
folder.setPropertyValue(KmlConstants.TAG_FEATURES, fs);
return folder;
}
/**
* Transforms a feature into KML feature (Placemak if original
* features contents a geometry, or Folder otherwise).
*/
private Feature writeFeature(final Feature feature) throws URISyntaxException {
Feature kmlFeature = null;
for (final PropertyType type : feature.getType().getProperties(true)) {
final Object val = feature.getPropertyValue(type.getName().toString());
if (val instanceof Feature) {
kmlFeature = KML_FACTORY.createFolder();
kmlFeature.setPropertyValue(KmlConstants.TAG_FEATURES, writeFeature((Feature) val));
} else if (val instanceof Geometry) {
kmlFeature = KML_FACTORY.createPlacemark();
kmlFeature.setPropertyValue(KmlConstants.TAG_GEOMETRY, val);
} else {
//System.out.println("PAS FEATURE.");
}
}
// Search feature style URI
for (Entry<Rule, URI> e : IDENTIFICATORS_MAP) {
final Rule rule = e.getKey();
if (rule.getFilter().evaluate(feature)) {
kmlFeature.setPropertyValue(KmlConstants.TAG_STYLE_URL, e.getValue());
for (Symbolizer s : rule.symbolizers()) {
if (s instanceof TextSymbolizer) {
final Expression label = ((TextSymbolizer) s).getLabel();
if (label != null) {
kmlFeature.setPropertyValue(KmlConstants.TAG_NAME, writeExpression(label, String.class, feature));
}
}
}
break;
}
}
return kmlFeature;
}
/**
* Transforms a JTS Geometry into KML Geometry.
*/
private AbstractGeometry writeGeometry(Geometry geometry) {
final AbstractGeometry resultat;
if (geometry instanceof GeometryCollection) {
final List<AbstractGeometry> liste = new ArrayList<>();
if (geometry instanceof MultiPolygon) {
final MultiPolygon multipolygon = (MultiPolygon) geometry;
for (int i = 0, num = multipolygon.getNumGeometries(); i < num; i++) {
liste.add(this.writeGeometry(multipolygon.getGeometryN(i)));
}
}
resultat = KML_FACTORY.createMultiGeometry();
((org.geotoolkit.data.kml.model.MultiGeometry) resultat).setGeometries(liste);
} else if (geometry instanceof Polygon) {
final Polygon polygon = (Polygon) geometry;
final Boundary externBound = KML_FACTORY.createBoundary(
(org.geotoolkit.data.kml.model.LinearRing) writeGeometry(polygon.getExteriorRing()), null, null);
final List<Boundary> internBounds = new ArrayList<>();
for (int i = 0, num = polygon.getNumInteriorRing(); i < num; i++) {
internBounds.add(KML_FACTORY.createBoundary((org.geotoolkit.data.kml.model.LinearRing) this.writeGeometry(polygon.getInteriorRingN(i)), null, null));
}
resultat = KML_FACTORY.createPolygon(externBound, internBounds);
} else if (geometry instanceof LineString) {
if (geometry instanceof LinearRing) {
resultat = KML_FACTORY.createLinearRing(((LinearRing) geometry).getCoordinateSequence());
} else {
resultat = KML_FACTORY.createLineString(((LineString) geometry).getCoordinateSequence());
}
} else {
resultat = null;
}
return resultat;
}
/**
* Transforms a CoverageMapLayer into KML GroundOverlay.
*/
private Feature writeCoverageMapLayer(CoverageMapLayer coverageMapLayer) throws Exception {
final Feature groundOverlay = KML_FACTORY.createGroundOverlay();
final CoordinateReferenceSystem targetCrs = CommonCRS.WGS84.normalizedGeographic();
final CoverageReference ref = coverageMapLayer.getCoverageReference();
final CoverageReader reader = ref.acquireReader();
final GridCoverage2D coverage = (GridCoverage2D) reader.read(ref.getImageIndex(), null);
ref.recycle(reader);
final GridCoverage2D targetCoverage =
(GridCoverage2D) Operations.DEFAULT.resample(coverage, targetCrs);
//targetCoverage.show();
// Creating image file and Writting referenced image into.
final Path img = filesDirectory.resolve(targetCoverage.getName().toString()+".png");
try (OutputStream outputStream = Files.newOutputStream(img, CREATE, WRITE, TRUNCATE_EXISTING)) {
ImageIO.write(targetCoverage.getRenderedImage(), "png", outputStream);
}
final Icon image = KML_FACTORY.createIcon(KML_FACTORY.createLink());
image.setHref(filesDirectory.getFileName().toString() + File.separator + targetCoverage.getName());
groundOverlay.setPropertyValue(KmlConstants.TAG_NAME, targetCoverage.getName().toString());
groundOverlay.setPropertyValue(KmlConstants.TAG_ICON, image);
groundOverlay.setPropertyValue(KmlConstants.TAG_ALTITUDE, 1.0);
groundOverlay.setPropertyValue(KmlConstants.TAG_ALTITUDE_MODE, EnumAltitudeMode.CLAMP_TO_GROUND);
final LatLonBox latLonBox = KML_FACTORY.createLatLonBox();
latLonBox.setNorth(targetCoverage.getEnvelope2D().getMaxY());
latLonBox.setSouth(targetCoverage.getEnvelope2D().getMinY());
latLonBox.setEast(targetCoverage.getEnvelope2D().getMaxX());
latLonBox.setWest(targetCoverage.getEnvelope2D().getMinX());
groundOverlay.setPropertyValue(KmlConstants.TAG_LAT_LON_BOX, latLonBox);
return groundOverlay;
}
}