package com.revolsys.record.io.format.kml;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.Point;
import com.revolsys.io.AbstractRecordWriter;
import com.revolsys.io.IoConstants;
import com.revolsys.record.Record;
import com.revolsys.record.io.format.xml.XmlWriter;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.util.Exceptions;
import com.revolsys.util.Property;
import com.revolsys.util.number.Doubles;
public class KmlRecordWriter extends AbstractRecordWriter implements Kml22Constants {
private static final Map<Class<?>, String> TYPE_MAP = new HashMap<>();
public static final GeometryFactory GEOMETRY_FACTORY_3D = GeometryFactory
.floating(Kml22Constants.COORDINATE_SYSTEM_ID, 2);
public static final GeometryFactory GEOMETRY_FACTORY_2D = GeometryFactory
.floating(Kml22Constants.COORDINATE_SYSTEM_ID, 2);
static {
TYPE_MAP.put(Double.class, "decimal");
TYPE_MAP.put(Integer.class, "decimal");
TYPE_MAP.put(BigDecimal.class, "decimal");
TYPE_MAP.put(java.sql.Date.class, "dateTime");
TYPE_MAP.put(java.util.Date.class, "dateTime");
TYPE_MAP.put(String.class, "string");
TYPE_MAP.put(Geometry.class, "wktGeometry");
}
private String defaultStyleUrl;
private boolean opened;
private String styleUrl;
private final java.io.Writer writer;
public KmlRecordWriter(final java.io.Writer out) {
this.writer = out;
}
@Override
public void close() {
open();
try {
if (!Boolean.TRUE.equals(getProperty(IoConstants.SINGLE_OBJECT_PROPERTY))) {
this.writer.write("</Document>\n");
}
this.writer.write("</kml>\n");
this.writer.close();
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
@Override
public void flush() {
try {
this.writer.flush();
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
public boolean isKmlWriteNulls() {
return super.isWriteNulls();
}
@Override
public void open() {
if (!this.opened) {
try {
writeHeader();
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
}
public void setKmlWriteNulls(final boolean writeNulls) {
super.setWriteNulls(writeNulls);
}
@Override
public void setProperty(final String name, final Object value) {
super.setProperty(name, value);
if (Kml22Constants.STYLE_URL_PROPERTY.equals(name)) {
String styleUrl;
if (value == null) {
styleUrl = null;
} else {
styleUrl = value.toString();
}
if (Property.hasValue(styleUrl)) {
if (Property.hasValue(this.defaultStyleUrl)) {
this.styleUrl = styleUrl;
} else {
this.defaultStyleUrl = styleUrl;
}
} else {
this.styleUrl = this.defaultStyleUrl;
}
}
}
@Override
public String toString() {
return "KML Writer";
}
@Override
public void write(final Record record) {
try {
open();
this.writer.write("<Placemark>\n");
final RecordDefinition recordDefinition = record.getRecordDefinition();
final int geometryIndex = recordDefinition.getGeometryFieldIndex();
final int idIndex = recordDefinition.getIdFieldIndex();
final String nameAttribute = getProperty(PLACEMARK_NAME_ATTRIBUTE_PROPERTY);
String name = null;
if (nameAttribute != null) {
name = record.getValue(nameAttribute);
}
if (name == null && idIndex != -1) {
final Object id = record.getValue(idIndex);
final String typeName = recordDefinition.getName();
name = typeName + " " + id;
}
if (name != null) {
this.writer.write("<name>");
XmlWriter.writeElementContent(this.writer, name);
this.writer.write("</name>\n");
}
final String snippet = getProperty(SNIPPET_PROPERTY);
if (snippet != null) {
this.writer.write("<Snippet>");
XmlWriter.writeElementContent(this.writer, snippet);
this.writer.write("</Snippet>\n");
}
String description = getProperty(PLACEMARK_DESCRIPTION_PROPERTY);
if (description == null) {
description = getProperty(IoConstants.DESCRIPTION_PROPERTY);
}
if (Property.hasValue(description)) {
this.writer.write("<description>");
this.writer.write("<![CDATA[");
this.writer.write(description);
this.writer.write("]]>");
this.writer.write("<description>");
}
Geometry geometry = null;
GeometryFactory kmlGeometryFactory = GEOMETRY_FACTORY_2D;
final List<Integer> geometryFieldIndexes = recordDefinition.getGeometryFieldIndexes();
if (!geometryFieldIndexes.isEmpty()) {
if (geometryFieldIndexes.size() == 1) {
geometry = record.getValue(geometryFieldIndexes.get(0));
final int axisCount = geometry.getAxisCount();
if (axisCount > 2) {
kmlGeometryFactory = GEOMETRY_FACTORY_2D.convertAxisCount(axisCount);
}
geometry = geometry.convertGeometry(kmlGeometryFactory);
} else {
final List<Geometry> geometries = new ArrayList<>();
for (final Integer geometryFieldIndex : geometryFieldIndexes) {
Geometry part = record.getValue(geometryFieldIndex);
if (part != null) {
final int axisCount = part.getAxisCount();
if (axisCount > 2) {
kmlGeometryFactory = GEOMETRY_FACTORY_2D.convertAxisCount(axisCount);
}
part = part.convertGeometry(kmlGeometryFactory);
if (!part.isEmpty()) {
geometries.add(part);
}
}
}
if (!geometries.isEmpty()) {
geometry = kmlGeometryFactory.geometry(geometries);
}
}
}
writeLookAt(record.getGeometry());
if (Property.hasValue(this.styleUrl)) {
this.writer.write("<styleUrl>");
XmlWriter.writeElementContent(this.writer, this.styleUrl);
this.writer.write("</styleUrl>\n");
} else if (Property.hasValue(this.defaultStyleUrl)) {
this.writer.write("<styleUrl>");
XmlWriter.writeElementContent(this.writer, this.defaultStyleUrl);
this.writer.write("</styleUrl>\n");
}
boolean hasValues = false;
for (final FieldDefinition field : recordDefinition.getFields()) {
final int fieldIndex = field.getIndex();
if (fieldIndex != geometryIndex) {
final String fieldName = field.getName();
final Object value;
if (isWriteCodeValues()) {
value = record.getCodeValue(fieldIndex);
} else {
value = record.getValue(fieldIndex);
}
if (isValueWritable(value)) {
if (!hasValues) {
hasValues = true;
this.writer.write("<ExtendedData>\n");
}
this.writer.write("<Data name=\"");
XmlWriter.writeAttributeContent(this.writer, fieldName);
this.writer.write("\">\n");
this.writer.write("<value>");
if (Property.hasValue(value)) {
XmlWriter.writeElementContent(this.writer, value.toString());
}
this.writer.write("</value>\n");
this.writer.write("</Data>\n");
}
}
}
if (hasValues) {
this.writer.write("</ExtendedData>\n");
}
if (geometry != null) {
GeometryFactory geometryFactory = getProperty(IoConstants.GEOMETRY_FACTORY);
if (geometryFactory == null) {
geometryFactory = geometry.getGeometryFactory();
}
final int axisCount = geometryFactory.getAxisCount();
KmlWriterUtil.writeGeometry(this.writer, geometry, axisCount);
}
this.writer.write("</Placemark>\n");
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
private void writeHeader() throws IOException {
this.opened = true;
this.writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
this.writer.write("<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n");
if (!Boolean.TRUE.equals(getProperty(IoConstants.SINGLE_OBJECT_PROPERTY))) {
this.writer.write("<Document>\n");
final String name = getProperty(DOCUMENT_NAME_PROPERTY);
if (Property.hasValue(name)) {
this.writer.write("<name>");
XmlWriter.writeElementContent(this.writer, name);
this.writer.write("</name>\n");
}
final String snippet = getProperty(SNIPPET_PROPERTY);
if (Property.hasValue(snippet)) {
this.writer.write("<Snippet>");
XmlWriter.writeElementContent(this.writer, snippet);
this.writer.write("</Snippet>\n");
}
final String description = getProperty(DOCUMENT_DESCRIPTION_PROPERTY);
if (Property.hasValue(description)) {
this.writer.write("<description>");
XmlWriter.writeElementContent(this.writer, description);
this.writer.write("</description>\n");
}
this.writer.write("<open>1</open>\n");
final Point point = getProperty(LOOK_AT_POINT_PROPERTY);
if (point != null) {
Number range = getProperty(LOOK_AT_RANGE_PROPERTY);
if (range == null) {
range = 1000;
}
final double[] coordinates = point.convertCoordinates(GEOMETRY_FACTORY_2D);
final double x = coordinates[0];
final double y = coordinates[1];
writeLookAt(x, y, range.longValue());
}
final String style = getProperty(STYLE_PROPERTY);
if (Property.hasValue(style)) {
this.writer.write(style);
}
}
}
public void writeLookAt(final double x, final double y, long range) {
try {
final Number minRange = getProperty(Kml22Constants.LOOK_AT_MIN_RANGE_PROPERTY);
if (minRange != null) {
if (range < minRange.doubleValue()) {
range = minRange.longValue();
}
}
final Number maxRange = getProperty(Kml22Constants.LOOK_AT_MAX_RANGE_PROPERTY);
if (maxRange != null) {
if (range > maxRange.doubleValue()) {
range = maxRange.longValue();
}
}
this.writer.write("<LookAt>\n");
this.writer.write("<longitude>");
this.writer.write(Doubles.toString(x));
this.writer.write("</longitude>\n");
this.writer.write("<latitude>");
this.writer.write(Doubles.toString(y));
this.writer.write("</latitude>\n");
this.writer.write("<altitude>0</altitude>\n");
this.writer.write("<heading>0</heading>\n");
this.writer.write("<tilt>0</tilt>\n");
this.writer.write("<range>");
this.writer.write(Long.toString(range));
this.writer.write("</range>\n");
this.writer.write("</LookAt>\n");
} catch (final IOException e) {
throw Exceptions.wrap(e);
}
}
private void writeLookAt(final Geometry geometry) {
if (geometry != null) {
final BoundingBox boundingBox = geometry.getBoundingBox();
final double centreX = boundingBox.getCentreX();
final double centreY = boundingBox.getCentreY();
final Number configRange = getProperty(LOOK_AT_RANGE_PROPERTY);
final long range;
if (configRange == null) {
range = Kml.getLookAtRange(boundingBox);
} else {
range = configRange.longValue();
}
writeLookAt(centreX, centreY, range);
}
}
}