/*
* Copyright (C) 2014 Alec Dhuse
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.mapImportExport;
import co.foldingmap.Logger;
import co.foldingmap.map.vector.MultiGeometry;
import co.foldingmap.map.vector.LatLonAltBox;
import co.foldingmap.map.vector.VectorLayer;
import co.foldingmap.map.vector.CoordinateList;
import co.foldingmap.map.vector.VectorObjectList;
import co.foldingmap.map.vector.VectorObject;
import co.foldingmap.map.vector.Coordinate;
import co.foldingmap.map.vector.MapPoint;
import co.foldingmap.map.vector.LineString;
import co.foldingmap.map.vector.Polygon;
import co.foldingmap.map.themes.PolygonStyle;
import co.foldingmap.map.themes.MapTheme;
import co.foldingmap.map.themes.ColorStyle;
import co.foldingmap.map.themes.IconStyle;
import co.foldingmap.map.themes.LineStyle;
import co.foldingmap.map.labeling.PointLabel;
import co.foldingmap.map.labeling.MapLabel;
import co.foldingmap.map.labeling.LineStringLabel;
import co.foldingmap.map.labeling.PolygonLabel;
import co.foldingmap.map.labeling.LabelManager;
import co.foldingmap.map.DigitalMap;
import co.foldingmap.map.Layer;
import co.foldingmap.map.MapView;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import org.apache.commons.codec.binary.Base64;
/**
*
* @author Alec
*/
public class SvgExporter {
private int indentCount;
public SvgExporter() {
indentCount = 0;
}
/** Adds an Indent to the indent counter.
* This counter is used to properly indent XML tags for easy reading in
* the exported SVG file.
*/
private void addIndent() {
indentCount++;
}
private void exportLayer(BufferedWriter outputStream,
Layer layer,
MapTheme theme,
float maxY) {
if (layer instanceof VectorLayer) {
exportVectorLayer(outputStream, (VectorLayer) layer, theme, maxY);
} else {
Logger.log(Logger.ERR, "Export of layers other than VectorLayer not supported at this time.");
}
}
private void exportVectorLayer(BufferedWriter outputStream,
VectorLayer layer,
MapTheme theme,
float maxY) {
VectorObjectList<VectorObject> lineStrings;
try {
outputStream.write(getIndent());
outputStream.write("<g\n");
addIndent();
outputStream.write(getIndent());
outputStream.write("id=\"");
outputStream.write(layer.getName());
outputStream.write("\">\n\n");
removeIndent();
//Get a list of all LineStrings including those in MultiGeometries
lineStrings = new VectorObjectList<VectorObject>();
lineStrings.addAll(layer.getObjectList().getLineStrings());
for (VectorObject object: layer.getObjectList().getMultiGeometries())
lineStrings.addAll(((MultiGeometry) object).getComponentObjects().getLineStrings());
//Draw LineString outlines first
for (VectorObject object: lineStrings) {
LineStyle ls = theme.getLineStyle(object.getObjectClass());
if (ls != null) {
if (ls.isOutlined())
writeSvgLine(outputStream, object.getName(), object.getCoordinateList(), ls, true);
}
}
for (VectorObject object: layer.getObjectList())
exportVectorObject(outputStream, object, theme, maxY);
outputStream.write("</g>\n");
removeIndent();
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.exportVectorLayer(ObjectOutputStream, VectorLayer, MapTheme) - " + e);
}
}
public void exportVectorObject(BufferedWriter outputStream,
VectorObject object,
MapTheme theme,
float maxY) {
ColorStyle style;
style = theme.getStyle(object.getObjectClass());
if (object instanceof Polygon) {
writePath(outputStream, object.getName(), object.getCoordinateList(), style, true);
} else if (object instanceof MapPoint) {
writeImage(outputStream, (IconStyle) style, object.getCoordinateList().get(0), object.getName());
} else if (object instanceof LineString) {
writeSvgLine(outputStream, object.getName(), object.getCoordinateList(), style, false);
} else if (object instanceof MultiGeometry) {
try {
outputStream.write(getIndent());
outputStream.write("<g id=\"");
outputStream.write(object.getName());
outputStream.write("\">\n\n");
addIndent();
for (VectorObject subObject: ((MultiGeometry) object).getComponentObjects())
exportVectorObject(outputStream, subObject, theme, maxY);
removeIndent();
outputStream.write(getIndent());
outputStream.write("</g>\n");
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.exportVectorObject() while exporting MultiGeometry: " + e);
}
} else {
writePath(outputStream, object.getName(), object.getCoordinateList(), style, false);
}
}
private void exportMapLabel(BufferedWriter outputStream,
MapLabel label) {
Color outlineColor, fillColor;
float strokeSize, x, y;
Font labelFont;
String style, fontStyle;
try {
labelFont = label.getFont();
strokeSize = 0;
//construct style
if (label.getFillColor() != null) {
fillColor = label.getFillColor();
} else {
fillColor = Color.WHITE;
}
if (label.getOutlineColor() != null) {
outlineColor = label.getOutlineColor();
} else {
outlineColor = Color.BLACK;
}
if (labelFont.getStyle() == Font.BOLD) {
fontStyle = "font-weight=\"bold\"";
} else if (labelFont.getStyle() == Font.PLAIN) {
fontStyle = "font-style=\"normal\"";
} else if (labelFont.getStyle() == Font.ITALIC) {
fontStyle = "font-style=\"italic\"";
} else {
fontStyle = "";
}
style = "font-family=\"" + labelFont.getFamily() + "\" font-size=\"" + labelFont.getSize() + "\" " + fontStyle + " ";
outputStream.write(getIndent());
outputStream.write("<g " + style + ">\n");
addIndent();
style = "fill:#" + getHexColor(fillColor) + ";stroke:#" + getHexColor(outlineColor) + "stroke-width:" + Float.toString(strokeSize) + "pg;";
if (label instanceof PointLabel) {
PointLabel pointLabel = (PointLabel) label;
x = pointLabel.getLine1StartPoint().x;
y = pointLabel.getLine1StartPoint().y;
outputStream.write(getIndent());
outputStream.write("<text x=\"");
outputStream.write(Float.toString(x));
outputStream.write("\" y=\"");
outputStream.write(Float.toString(y));
outputStream.write("\" style=\"");
outputStream.write(style);
outputStream.write("\">\n");
addIndent();
outputStream.write(getIndent());
outputStream.write("<tspan x=\"");
outputStream.write(Float.toString(x));
outputStream.write("\" y=\"");
outputStream.write(Float.toString(y));
outputStream.write("\">");
outputStream.write(pointLabel.getLine1Text());
outputStream.write("</tspan>\n");
if (pointLabel.getLine2Text() != null && pointLabel.getLine2Text().length() > 0) {
x = pointLabel.getLine2StartPoint().x;
y = pointLabel.getLine2StartPoint().y;
if (x != 0 && y != 0) {
outputStream.write(getIndent());
outputStream.write("<tspan x=\"");
outputStream.write(Float.toString(x));
outputStream.write("\" y=\"");
outputStream.write(Float.toString(y));
outputStream.write("\">");
outputStream.write(pointLabel.getLine2Text());
outputStream.write("</tspan>\n");
}
}
removeIndent();
outputStream.write(getIndent());
outputStream.write("</text>\n");
removeIndent();
outputStream.write(getIndent());
outputStream.write("</g>\n");
} else if (label instanceof LineStringLabel) {
LineStringLabel lineLabel = (LineStringLabel) label;
} else if (label instanceof PolygonLabel) {
PolygonLabel polyLabel = (PolygonLabel) label;
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.exportMapLabel(BufferedWriter, MapLabel) - " + e);
}
}
public void exportMap(DigitalMap mapData, File outputFile) {
BufferedWriter outputStream;
float height, width, maxX;
LabelManager labelManager;
LatLonAltBox mapBounds;
Layer currentLayer;
MapView mapView;
try {
width = 1200;
mapView = mapData.getLastMapView();
mapBounds = mapData.getBoundary();
outputStream = new BufferedWriter(new FileWriter(outputFile));
labelManager = mapView.getLabelManager();
//mapView.getMapProjection().setReference(new Coordinate(0, 90, -180));
mapData.calculateCoordinateLocations(mapView);
height = mapView.getY(mapBounds.getSouthWestCoordinate());
maxX = mapView.getX(mapBounds.getNorthEastCoordinate(), MapView.NO_WRAP);
outputStream.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
outputStream.write("<!-- Created with FoldingMap (http://www.foldingmap.co/) -->\n\n");
outputStream.write("<svg\n");
addIndent();
outputStream.write(getIndent());
outputStream.write("xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
outputStream.write(getIndent());
outputStream.write("xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n");
outputStream.write(getIndent());
outputStream.write("xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
outputStream.write(getIndent());
outputStream.write("xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
outputStream.write(getIndent());
outputStream.write("xmlns=\"http://www.w3.org/2000/svg\"\n");
//height and width
outputStream.write(getIndent());
outputStream.write("width=\"");
outputStream.write(Float.toString(width));
outputStream.write("\"\n");
outputStream.write(getIndent());
outputStream.write("height=\"");
outputStream.write(Float.toString(height));
outputStream.write("\"\n");
outputStream.write(getIndent());
outputStream.write("id=\"");
outputStream.write(mapData.getName());
outputStream.write("\">\n\n");
//loop backwards through the layers, since the first layer is on the bottom.
for (int i = mapData.getLayers().size() - 1; i >= 0; i--) {
currentLayer = mapData.getLayer(i);
exportLayer(outputStream, currentLayer, mapData.getTheme(), height);
}
//write Labels
for (MapLabel label: labelManager.getLabels()) {
exportMapLabel(outputStream, label);
}
removeIndent();
outputStream.write("</svg>");
outputStream.close();
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.exportMap(DigitalMap) - " + e);
}
}
/**
* Generates the SVG style string from a given ColorStyle
*
* @param style
* @return
*/
private String generateLineStyleString(ColorStyle style, boolean isOutline) {
Color strokeColor;
float width;
LineStyle lineStyle;
StringBuilder sb;
lineStyle = (LineStyle) style;
sb = new StringBuilder();
width = lineStyle.getLineWidth();
if (isOutline) {
width += 1.2f;
strokeColor = lineStyle.getOutlineColor();
} else {
strokeColor = lineStyle.getFillColor();
}
sb.append("fill-opacity:0;");
sb.append("stroke:#");
sb.append(getHexColor(strokeColor));
sb.append(";");
sb.append("stroke-width:");
sb.append(Float.toString(width));
sb.append("px;");
sb.append("stroke-linecap:round;");
return sb.toString();
}
/**
* Generates the SVG style string from a given ColorStyle
*
* @param style
* @return
*/
private String generateStyleString(ColorStyle style) {
float width;
StringBuilder sb = new StringBuilder();
if (style instanceof PolygonStyle) {
width = ((PolygonStyle) style).getLineWidth();
} else if (style instanceof LineStyle) {
width = ((LineStyle) style).getLineWidth();
} else {
width = 1;
}
//Fill Color
if (style.isFilled()) {
sb.append("fill:#");
sb.append(getHexColor(style.getFillColor()));
sb.append(";");
sb.append("fill-opacity:1;");
} else {
sb.append("fill:none;");
}
//Outline Color
if (style.getOutlineStyles().size() > 0) {
sb.append("stroke:#");
sb.append(getHexColor(style.getOutlineColor()));
sb.append(";");
sb.append("stroke-opacity:1;");
} else {
//no stroke, outlines will be seperate objects
sb.append("stroke:none");
sb.append("stroke-opacity:0");
}
//Stroke Width
if (style.getOutlineStyles().size() > 1) {
//no stroke, outlines will be seperate objects
sb.append("stroke-width:0px;");
//TODO: Implement multiple outlines
} else {
sb.append("stroke:");
sb.append(width);
sb.append("px;");
}
sb.append("stroke-linecap:butt;");
sb.append("stroke-linejoin:miter");
return sb.toString();
}
/**
* Returns the hex version of a Color object.
* Output is red, green, blue.
*
* @param c
* @return
*/
public static String getHexColor(Color c) {
String hexColor, b, g, r;
b = Integer.toHexString(c.getBlue());
g = Integer.toHexString(c.getGreen());
r = Integer.toHexString(c.getRed());
if (b.length() == 1)
b = "0" + b;
if (g.length() == 1)
g = "0" + g;
if (r.length() == 1)
r = "0" + r;
hexColor = r + g + b;
return hexColor;
}
private String getIndent() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentCount; i++)
sb.append("\t");
return sb.toString();
}
private void removeIndent() {
indentCount--;
}
private void writeImage(BufferedWriter outputStream,
IconStyle style,
Coordinate c,
String id) {
BufferedImage bi;
byte[] imageBytes;
ByteArrayOutputStream baos;
float x, y, height, width;
ImageIcon image;
OutputStream os64;
Point2D.Float point;
String imageEnc;
try {
point = c.getCenterPoint();
if (style.getImageFile() == null) {
//No image
outputStream.write(getIndent());
outputStream.write("<circle cx=\"");
outputStream.write(Float.toString(point.x - 2));
outputStream.write("\" cy=\"");
outputStream.write(Float.toString(point.y - 2));
outputStream.write("\" r=\"2\" stroke=\"#");
outputStream.write(getHexColor(style.getOutlineColor()));
outputStream.write("\" stroke-width=\"1\" fill=\"");
outputStream.write(getHexColor(style.getFillColor()));
outputStream.write("\"/>\n");
} else {
//Has image
//first encode image into a string
baos = new ByteArrayOutputStream();
bi = ImageIO.read(style.getImageFile());
ImageIO.write(bi, "PNG", baos);
imageBytes = baos.toByteArray();
imageEnc = Base64.encodeBase64String(imageBytes);
baos.close();
image = style.getObjectImage();
height = image.getIconHeight();
width = image.getIconWidth();
x = point.x - (width / 2.0f);
y = point.y - (height / 2.0f);
outputStream.write(getIndent());
outputStream.write("<image\n");
addIndent();
//y coordiante
outputStream.write(getIndent());
outputStream.write("y=\"");
outputStream.write(Float.toString(y));
outputStream.write("\"\n");
//x coordiante
outputStream.write(getIndent());
outputStream.write("x=\"");
outputStream.write(Float.toString(x));
outputStream.write("\"\n");
//id
outputStream.write(getIndent());
outputStream.write("id=\"");
outputStream.write(id);
outputStream.write("\"\n");
//xlink
outputStream.write(getIndent());
outputStream.write("xlink:href=\"data:image/png;base64,");
outputStream.write(imageEnc);
outputStream.write("\"\n");
//x coordiante
outputStream.write(getIndent());
outputStream.write("height=\"");
outputStream.write(Float.toString(height));
outputStream.write("\"\n");
//y coordiante
outputStream.write(getIndent());
outputStream.write("width=\"");
outputStream.write(Float.toString(width));
outputStream.write("\" />\n");
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.writeImage() - " + e);
}
}
private void writePath(BufferedWriter outputStream,
String id,
CoordinateList<Coordinate> coordinates,
ColorStyle style,
boolean closePath) {
Coordinate c;
String styleString;
try {
styleString = generateStyleString(style);
outputStream.write(getIndent());
outputStream.write("<path\n");
addIndent();
outputStream.write(getIndent());
outputStream.write("style=\"");
outputStream.write(styleString);
outputStream.write("\"\n");
//write out points
outputStream.write(getIndent());
outputStream.write("d=\"M ");
for (int i = 0; i < coordinates.size(); i++) {
c = coordinates.get(i);
if (i > 0) {
outputStream.write("L ");
}
outputStream.write(Float.toString(c.getCenterPoint().x));
outputStream.write(" ");
outputStream.write(Float.toString(c.getCenterPoint().y ));
if (i+1 < coordinates.size())
outputStream.write(" ");
}
if (closePath) outputStream.write(" Z");
outputStream.write("\"\n");
//write out id
outputStream.write(getIndent());
outputStream.write("id=\"");
outputStream.write(id);
outputStream.write("\"");
//close tag
outputStream.write(" />\n");
removeIndent();
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.writePath(BufferedWriter, String, CoordinateList, ColorStyle - " + e);
}
}
private void writeSvgLine(BufferedWriter outputStream,
String id,
CoordinateList<Coordinate> coordinates,
ColorStyle style,
boolean isOutline) {
Coordinate c;
String styleString;
try {
styleString = generateLineStyleString(style, isOutline);
addIndent();
outputStream.write(getIndent());
outputStream.write("<polyline points=\"");
for (int i = 0; i < coordinates.size(); i++) {
c = coordinates.get(i);
outputStream.write(Float.toString(c.getCenterPoint().x));
outputStream.write(",");
outputStream.write(Float.toString(c.getCenterPoint().y ));
if (i+1 < coordinates.size()) outputStream.write(" ");
}
outputStream.write("\"\n");
outputStream.write(getIndent());
outputStream.write("style=\"");
outputStream.write(styleString);
outputStream.write("\" />\n");
removeIndent();
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in SvgExporter.writePolyLine(BufferedWriter, String, CoordinateList, ColorStyle - " + e);
}
}
}