/*
* 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.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import java.awt.Color;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.geotoolkit.data.kml.model.KmlException;
import static org.geotoolkit.data.kml.xml.KmlConstants.*;
/**
* This class provides utilities for reading and writting KML files.
*
* @author Samuel Andrés
* @module
*/
public class KmlUtilities {
/**
* Transforms a Kml color string to java instance of java.awt.Color class.
* <p>BE CAREFUL : Color object RGB constructors and Kml representation are not in
* same order (a : alpha; b: blue; g: green; r: red) : </p>
* <ul>
* <li>kml : <color>aabbggrr</color>, with a, b, g, r hexadecimal characters between 0 and f.</li>
* <li>Color : Color(r,g,b,a), with r, g, b, a int parameters between 0 and 255.</li>
* </ul>
*
* <pre>
* <simpleType name="colorType">
* <annotation>
* <documentation><![CDATA[
* aabbggrr
* ffffffff: opaque white
* ff000000: opaque]]>
* </documentation>
* </annotation>
* <restriction base="hexBinary">
* <length value="4"/>
* </restriction>
* </simpleType>
* </pre>
*
* @param kmlColor Kml string color hexadecimal representation.
*/
public static Color parseColor(final String kmlColor) throws KmlException {
Color color = null;
if (kmlColor.matches("[0-9a-fA-F]{8}")) {
int r = Integer.parseInt(kmlColor.substring(0, 2), 16);
int g = Integer.parseInt(kmlColor.substring(2, 4), 16);
int b = Integer.parseInt(kmlColor.substring(4, 6), 16);
int a = Integer.parseInt(kmlColor.substring(6, 8), 16);
color = new Color(a, b, g, r);
} else {
throw new KmlException("The color must be a suit of four hexabinaries");
}
return color;
}
/**
* Transforms an instance of java.awt.Color class into a Kml color string.
* <p>BE CAREFUL : Color object RGB constructors and Kml representation are not in
* same order (a : alpha; b: blue; g: green; r: red) : </p>
* <ul>
* <li>kml : <color>aabbggrr</color>, with a, b, g, r hexadecimal characters between 0 and f.</li>
* <li>Color : Color(r,g,b,a), with r, g, b, a int parameters between 0 and 255.</li>
* </ul>
*
* <pre>
* <simpleType name="colorType">
* <annotation>
* <documentation><![CDATA[
* aabbggrr
* ffffffff: opaque white
* ff000000: opaque]]>
* </documentation>
* </annotation>
* <restriction base="hexBinary">
* <length value="4"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static String toKmlColor(final Color color) {
final String r = Integer.toHexString(color.getRed());
final String g = Integer.toHexString(color.getGreen());
final String b = Integer.toHexString(color.getBlue());
final String a = Integer.toHexString(color.getAlpha());
final StringBuilder sb = new StringBuilder();
if (a.length() == 1) {
sb.append('0');
}
sb.append(a);
if (b.length() == 1) {
sb.append('0');
}
sb.append(b);
if (g.length() == 1) {
sb.append('0');
}
sb.append(g);
if (r.length() == 1) {
sb.append('0');
}
sb.append(r);
return sb.toString();
}
/**
* Checks value for Anglepos180 element.
*
* <pre>
* <simpleType name="anglepos180Type">
* <restriction base="double">
* <minInclusive value="0"/>
* <maxInclusive value="180.0"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static double checkAnglePos180(double angle) {
if (angle < 0 || angle > 180) {
throw new IllegalArgumentException("This angle type requires a value "
+ "between 0 and 180 degrees. You've intented an initialization with "
+ angle + " degree(s)");
}
return angle;
}
/**
* Checks value for Anglepos90 element.
*
* <pre>
* <simpleType name="angle90posType">
* <restriction base="double">
* <minInclusive value="0"/>
* <maxInclusive value="90.0"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static double checkAnglePos90(double angle) {
if (angle < 0 || angle > 90) {
throw new IllegalArgumentException("This angle type requires a value "
+ "between 0 and 90 degrees. You've intented an initialization with "
+ angle + " degree(s)");
}
return angle;
}
/**
* Checks value for Angle180 element.
*
* <pre>
* <simpleType name="angle180Type">
* <restriction base="double">
* <minInclusive value="-180"/>
* <maxInclusive value="180.0"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static double checkAngle180(double angle) {
if (angle < -180 || angle > 180) {
throw new IllegalArgumentException("This angle type requires a value "
+ "between -180 and 180 degrees. You've intented an initialization with "
+ angle + " degree(s)");
}
return angle;
}
/**
* Checks value for Angle180 element.
*
* <pre>
* <simpleType name="angle90Type">
* <restriction base="double">
* <minInclusive value="-90"/>
* <maxInclusive value="90.0"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static double checkAngle90(double angle) {
if (angle < -90 || angle > 90) {
throw new IllegalArgumentException("This angle type requires a value "
+ "between -90 and 90 degrees. You've intented an initialization with "
+ angle + " degree(s)");
}
return angle;
}
/**
* Checks value for Angle360 element.
*
* <pre>
* <simpleType name="angle360Type">
* <restriction base="double">
* <minInclusive value="-360"/>
* <maxInclusive value="360.0"/>
* </restriction>
* </simpleType>
* </pre>
*/
public static double checkAngle360(double angle) {
if (angle < -360 || angle > 360) {
throw new IllegalArgumentException("This angle type requires a value "
+ "between -360 and 360 degrees. You've intented an initialization with "
+ angle + " degree(s)");
}
return angle;
}
/**
* Retrieves a coma separated StringBuilder from a 2D/3D coordinate.
*/
public static StringBuilder toString(Coordinate coordinate) {
final StringBuilder sb = new StringBuilder();
sb.append(coordinate.x);
sb.append(',');
sb.append(coordinate.y);
if (!Double.isNaN(coordinate.z)) {
sb.append(',');
sb.append(coordinate.z);
}
return sb;
}
/**
* Retrieves a XML list (space separated values) of coordinates.
*/
public static String toString(CoordinateSequence coordinates) {
final StringBuilder sb = new StringBuilder();
for (int i=0, n=coordinates.size(); i<n; i++) {
sb.append(toString(coordinates.getCoordinate(i)));
if (i != n-1) {
sb.append(' ');
}
}
return sb.toString();
}
/**
* Retrieves Coordinate (2D/3D) from coma separated values.
*/
public static Coordinate toCoordinate(String coordinates) {
final String[] coordinatesList = coordinates.split(",");
final Coordinate c = new Coordinate();
c.x = Double.valueOf(coordinatesList[0].trim());
c.y = Double.valueOf(coordinatesList[1].trim());
if(coordinatesList.length == 3){
c.z = Double.valueOf(coordinatesList[2].trim());
}
return c;
}
/**
* Retrieves XML formated time.
*/
public static StringBuilder appendXMLFormatedTime(
int hours, int minutes, int seconds, int milli, boolean forceTime, StringBuilder time)
{
if (!(hours == 0 && minutes == 0 && seconds == 0 && milli == 0) || forceTime) {
time.append('T');
if(hours < 10) {
time.append('0');
}
time.append(hours);
time.append(':');
if(minutes < 10) {
time.append('0');
}
time.append(minutes);
time.append(':');
if(seconds < 10) {
time.append('0');
}
time.append(seconds);
if (milli != 0) {
time.append('.');
time.append(milli);
}
}
return time;
}
/**
* Retrieves XML formated timezone.
*/
public static StringBuilder appendXMLFormatedTimeZone(int zoneOffset, StringBuilder timeZone) {
int minutesOffset = zoneOffset / (60 * 1000);
final int hoursOffset = Math.abs(minutesOffset / 60);
minutesOffset = (minutesOffset % 60)*60;
if (zoneOffset == 0) {
timeZone.append('Z');
} else {
if(zoneOffset > 0)
timeZone.append('+');
else if (zoneOffset < 0)
timeZone.append('-');
if (hoursOffset < 10)
timeZone.append('0');
timeZone.append(hoursOffset);
timeZone.append(':');
if (minutesOffset < 10)
timeZone.append('0');
timeZone.append(minutesOffset);
}
return timeZone;
}
/**
* Retrieves XML formated date.
*/
public static StringBuilder appendXMLFormatedDate(
int year, int month, int day, boolean forceDay, StringBuilder date)
{
date.append(year);
if (day > 1 || forceDay){
date.append('-');
if(month < 10)
date.append('0');
date.append(month);
date.append('-');
if(day < 10)
date.append('0');
date.append(day);
} else if (month > 1){
date.append('-');
if(month < 10)
date.append('0');
date.append(month);
}
return date;
}
/**
* Retrieves XML formated datetime from Calendar.
*/
public static String getXMLFormatedCalendar(Calendar calendar, boolean forceDateTime) {
StringBuilder date = null;
final int day = calendar.get(Calendar.DAY_OF_MONTH);
final int month = calendar.get(Calendar.MONTH)+1;
final int year;
if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
year = -calendar.get(Calendar.YEAR);
} else {
year = calendar.get(Calendar.YEAR);
}
date = appendXMLFormatedDate(year, month, day, forceDateTime, new StringBuilder());
date = appendXMLFormatedTime(calendar.get(
Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND),
calendar.get(Calendar.MILLISECOND),
forceDateTime, date);
if (calendar.getTimeZone() != null) {
date = appendXMLFormatedTimeZone(calendar.get(Calendar.ZONE_OFFSET), date);
}
return date.toString();
}
/**
*
* @return true if d is not infinite nor NaN.
*/
public static boolean isFiniteNumber(double d) {
return !Double.isInfinite(d) && !Double.isNaN(d);
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractGeometry element.
*/
public static boolean isAbstractGeometry(String eName) {
return (TAG_MULTI_GEOMETRY.equals(eName)
|| TAG_LINE_STRING.equals(eName)
|| TAG_POLYGON.equals(eName)
|| TAG_POINT.equals(eName)
|| TAG_LINEAR_RING.equals(eName)
|| TAG_MODEL.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractFeature element.
*/
public static boolean isAbstractFeature(String eName) {
return (TAG_FOLDER.equals(eName)
|| TAG_GROUND_OVERLAY.equals(eName)
|| TAG_PHOTO_OVERLAY.equals(eName)
|| TAG_NETWORK_LINK.equals(eName)
|| TAG_DOCUMENT.equals(eName)
|| TAG_SCREEN_OVERLAY.equals(eName)
|| TAG_PLACEMARK.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractContainer element.
*/
public static boolean isAbstractContainer(String eName) {
return (TAG_FOLDER.equals(eName)
|| TAG_DOCUMENT.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractOverlay element.
*/
public static boolean isAbstractOverlay(String eName) {
return (TAG_GROUND_OVERLAY.equals(eName)
|| TAG_PHOTO_OVERLAY.equals(eName)
|| TAG_SCREEN_OVERLAY.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractView element.
*/
public static boolean isAbstractView(String eName) {
return (TAG_LOOK_AT.equals(eName)
|| TAG_CAMERA.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractTimePrimitive element.
*/
public static boolean isAbstractTimePrimitive(String eName) {
return (TAG_TIME_STAMP.equals(eName)
|| TAG_TIME_SPAN.equals(eName));
}
/**
* @param eName the tag name.
* @return true if the tag name is an AbstractStyleSelector element.
*/
public static boolean isAbstractStyleSelector(String eName) {
return (TAG_STYLE.equals(eName)
|| TAG_STYLE_MAP.equals(eName));
}
public static boolean isAbstractSubStyle(String eName) {
return (TAG_BALLOON_STYLE.equals(eName)
|| TAG_LIST_STYLE.equals(eName)
|| isAbstractColorStyle(eName));
}
public static boolean isAbstractColorStyle(String eName) {
return (TAG_ICON_STYLE.equals(eName)
|| TAG_LABEL_STYLE.equals(eName)
|| TAG_POLY_STYLE.equals(eName)
|| TAG_LINE_STYLE.equals(eName));
}
public static boolean isAbstractObject(String eName) {
// Traiter le cas particuloer du TAG_ICON qui peut être un basicLink
return (isAbstractFeature(eName)
|| isAbstractGeometry(eName)
|| isAbstractStyleSelector(eName)
|| isAbstractSubStyle(eName)
|| isAbstractView(eName)
|| TAG_PAIR.equals(eName)
|| TAG_LINK.equals(eName)
|| TAG_VIEW_VOLUME.equals(eName)
|| TAG_REGION.equals(eName)
|| TAG_LOD.equals(eName)
|| TAG_ORIENTATION.equals(eName)
|| TAG_SCHEMA_DATA.equals(eName));
}
public static boolean isAbstractLatLonBox(String eName) {
return (TAG_LAT_LON_ALT_BOX.equals(eName)
|| TAG_LAT_LON_BOX.equals(eName));
}
private KmlUtilities() {
}
}