/* * Copyright (C) 2011 apurv * * 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 nescent.phylogeoref.writer.utility; import de.micromata.opengis.kml.v_2_2_0.AltitudeMode; import de.micromata.opengis.kml.v_2_2_0.Boundary; import de.micromata.opengis.kml.v_2_2_0.ColorMode; import de.micromata.opengis.kml.v_2_2_0.Coordinate; import de.micromata.opengis.kml.v_2_2_0.Document; import de.micromata.opengis.kml.v_2_2_0.Folder; import de.micromata.opengis.kml.v_2_2_0.Icon; import de.micromata.opengis.kml.v_2_2_0.IconStyle; import de.micromata.opengis.kml.v_2_2_0.LabelStyle; import de.micromata.opengis.kml.v_2_2_0.LatLonAltBox; import de.micromata.opengis.kml.v_2_2_0.LineString; import de.micromata.opengis.kml.v_2_2_0.LineStyle; import de.micromata.opengis.kml.v_2_2_0.LinearRing; import de.micromata.opengis.kml.v_2_2_0.Lod; import de.micromata.opengis.kml.v_2_2_0.Pair; import de.micromata.opengis.kml.v_2_2_0.Placemark; import de.micromata.opengis.kml.v_2_2_0.Point; import de.micromata.opengis.kml.v_2_2_0.PolyStyle; import de.micromata.opengis.kml.v_2_2_0.Polygon; import de.micromata.opengis.kml.v_2_2_0.Region; import de.micromata.opengis.kml.v_2_2_0.Style; import de.micromata.opengis.kml.v_2_2_0.StyleMap; import de.micromata.opengis.kml.v_2_2_0.StyleState; import de.micromata.opengis.kml.v_2_2_0.ViewRefreshMode; import static java.lang.System.out; import java.awt.Color; import java.util.List; import java.util.Vector; import nescent.phylogeoref.reader.PhylogenyMould; import org.forester.phylogeny.PhylogenyNode; import org.forester.phylogeny.data.BranchColor; import org.forester.phylogeny.data.BranchData; import org.forester.phylogeny.data.Distribution; import org.forester.phylogeny.data.NodeData; /** * Provides the necessary utility functions while painting a kml. * @author apurv */ public class KmlUtility implements KmlConstants{ private static HTMLParlour parlour = new HTMLParlour(); /** * Creates a new folder inside document with given folderName and description. * @param document * @param folderName * @param description * @return */ public static Folder createFolder(Document document, String folderName, String description){ Folder folder = document.createAndAddFolder(); folder.withName(folderName); folder.withDescription(description); return folder; } /** * Creates a new inner folder inside the specified outer folder with the given description. * @param outerFolder * @param folderName * @param description * @return */ public static Folder createFolder(Folder outerFolder, String folderName, String description){ Folder innerFolder = outerFolder.createAndAddFolder(); innerFolder.withName(folderName); innerFolder.withDescription(description); return innerFolder; } /** * Creates a placemark for the external node, node<br> * More about regions can be found on this page.<br> * @see http://code.google.com/apis/kml/documentation/kml_21tutorial.html#workingregions<br> * * There will be three nested regions created, one will be most distant with just the color.<br> * Second will be with the condensed name. * Third will be for the complete information. * All the three will have the complete balloon information associated. So the user can view it * from any level. * @param folder * @param node * @param mould */ public static void createExternalPlacemark(Folder folder, PhylogenyNode node, PhylogenyMould mould){ if(!hasValidLocation(node)){ return; } //Specify the position of the node. double latitude = getLatitude(node); double longitude = getLongitude(node); double altitude = getAltitude(node); //Create the level 1 folder. Folder outerFolder = folder.createAndAddFolder(); outerFolder.withName(node.getNodeName()); //Create the level 1 placemark. (outermost placemark) Placemark outerPlacemark = outerFolder.createAndAddPlacemark(); String description = parlour.prepareHTMLContent(node, mould); outerPlacemark.setDescription(description); Point p = outerPlacemark.createAndSetPoint(); p.setAltitudeMode(AltitudeMode.ABSOLUTE); p.addToCoordinates(longitude, latitude, altitude); //Specify a region inside this placemark. Region outerRegion = outerFolder.createAndSetRegion(); //Specify the color of this placemark. Style style = outerPlacemark.createAndAddStyle(); IconStyle iconStyle = style.createAndSetIconStyle(); String color = getColor(node); iconStyle.setColor(color); iconStyle.setColorMode(ColorMode.NORMAL); iconStyle.setScale(0.80); //Specify the icon with this placemark. Icon icon = iconStyle.createAndSetIcon(); icon.setHref(PLACEMARK_ICON_HREF); LatLonAltBox latlonBox = outerRegion.createAndSetLatLonAltBox(); latlonBox.setEast(longitude + DELTA_L); latlonBox.setWest(longitude - DELTA_L); latlonBox.setNorth(latitude + DELTA_L); latlonBox.setSouth(latitude - DELTA_L); latlonBox.setMinAltitude(altitude); latlonBox.setMaxAltitude(altitude+DELTA_L); latlonBox.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); Lod lod = outerRegion.createAndSetLod(); lod.setMinLodPixels(MIN_LOD_PIXELS_LEVEL_OUTER); lod.setMaxLodPixels(MAX_LOD_PIXELS_LEVEL_OUTER); lod.setMinFadeExtent(MIN_FADE_EXTENT); lod.setMaxFadeExtent(MAX_FADE_EXTENT); createMiddlePlacemark(outerFolder, node, mould, description); //Multi-Occurrence of same specie. //Remember you cannot place multiple entities in the same placemarks. if(mould.getNumObservations() > 1){ createPolygon(mould.getLatVector(), mould.getLonVector(), folder, node); } } /** * Creates the middle level. * @param outerFolder * @param node * @param mould */ private static void createMiddlePlacemark(Folder outerFolder, PhylogenyNode node, PhylogenyMould mould, String desc){ //Specify the position of the node. double latitude = getLatitude(node); double longitude = getLongitude(node); double altitude = getAltitude(node); //Create the level 2 folder. Folder middleFolder = outerFolder.createAndAddFolder(); //Create the level 2 placemark. (middle placemark) Placemark middlePlacemark = middleFolder.createAndAddPlacemark(); middlePlacemark.setName(getCondensedName(node.getNodeName())); String description = desc; middlePlacemark.setDescription(description); Point p = middlePlacemark.createAndSetPoint(); p.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); p.addToCoordinates(longitude, latitude, altitude); //Specify a region inside this placemark. Region middleRegion = middleFolder.createAndSetRegion(); //Specify the color of this placemark. Style style = middlePlacemark.createAndAddStyle(); IconStyle iconStyle = style.createAndSetIconStyle(); String color = getColor(node); iconStyle.setColor(color); iconStyle.setColorMode(ColorMode.NORMAL); iconStyle.setScale(0.90); //Specify the icon with this placemark. Icon icon = iconStyle.createAndSetIcon(); icon.setHref(PLACEMARK_ICON_HREF); icon.setRefreshInterval(4); LabelStyle labelStyle = style.createAndSetLabelStyle(); labelStyle.setScale(0.75); LatLonAltBox latlonBox = middleRegion.createAndSetLatLonAltBox(); latlonBox.setEast(longitude + DELTA_L); latlonBox.setWest(longitude - DELTA_L); latlonBox.setNorth(latitude + DELTA_L); latlonBox.setSouth(latitude - DELTA_L); latlonBox.setMinAltitude(altitude); latlonBox.setMaxAltitude(altitude+DELTA_L); latlonBox.setAltitudeMode(AltitudeMode.ABSOLUTE); Lod lod = middleRegion.createAndSetLod(); lod.setMinLodPixels(MIN_LOD_PIXELS_LEVEL_MIDDLE); lod.setMaxLodPixels(MAX_LOD_PIXELS_LEVEL_MIDDLE); lod.setMinFadeExtent(MIN_FADE_EXTENT); lod.setMaxFadeExtent(MAX_FADE_EXTENT); createInternalPlacemark(middleFolder, node, mould, desc); } /** * Created the innermost detailed level. * @param middleFolder * @param node * @param mould */ private static void createInternalPlacemark(Folder middleFolder, PhylogenyNode node, PhylogenyMould mould, String desc){ //Specify the position of the node. double latitude = getLatitude(node); double longitude = getLongitude(node); double altitude = getAltitude(node); //Create the level 2 folder. Folder innerFolder = middleFolder.createAndAddFolder(); //Create the level 2 placemark. (middle placemark) Placemark innerPlacemark = innerFolder.createAndAddPlacemark(); innerPlacemark.setName(node.getNodeName()); String description = desc; innerPlacemark.setDescription(description); Point p = innerPlacemark.createAndSetPoint(); p.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); p.addToCoordinates(longitude, latitude, altitude); //Specify a region inside this placemark. Region innerRegion = innerFolder.createAndSetRegion(); //Create a style map. StyleMap styleMap = innerPlacemark.createAndAddStyleMap(); //Create the style for normal state. Pair pNormal = styleMap.createAndAddPair(); Style normalStyle = pNormal.createAndSetStyle(); pNormal.setKey(StyleState.NORMAL); IconStyle normalIconStyle = normalStyle.createAndSetIconStyle(); String normalColor = getColor(node); normalIconStyle.setColor(normalColor); normalIconStyle.setColorMode(ColorMode.NORMAL); normalIconStyle.setScale(1.00); //Specify the icon with this placemark. Icon normalIcon = normalIconStyle.createAndSetIcon(); normalIcon.setHref(PLACEMARK_ICON_HREF); LabelStyle normalLabelStyle = normalStyle.createAndSetLabelStyle(); normalLabelStyle.setColor(normalColor); normalLabelStyle.setColorMode(ColorMode.NORMAL); normalLabelStyle.setScale(1.0); //Create the style for highlighted state. Pair pHighlight = styleMap.createAndAddPair(); Style highlightStyle = pHighlight.createAndSetStyle(); pHighlight.setKey(StyleState.HIGHLIGHT); IconStyle highlightIconStyle = highlightStyle.createAndSetIconStyle(); String highlightColor = getColor(node); highlightIconStyle.setColor(highlightColor); highlightIconStyle.setColorMode(ColorMode.NORMAL); highlightIconStyle.setScale(2.00); //Specify the icon with this placemark. Icon highlightIcon = highlightIconStyle.createAndSetIcon(); highlightIcon.setHref(PLACEMARK_ICON_HREF); LabelStyle highlightLabelStyle = highlightStyle.createAndSetLabelStyle(); highlightLabelStyle.setColor(highlightColor); highlightLabelStyle.setColorMode(ColorMode.NORMAL); highlightLabelStyle.setScale(1.5); LatLonAltBox latlonBox = innerRegion.createAndSetLatLonAltBox(); latlonBox.setEast(longitude + DELTA_L); latlonBox.setWest(longitude - DELTA_L); latlonBox.setNorth(latitude + DELTA_L); latlonBox.setSouth(latitude - DELTA_L); latlonBox.setMinAltitude(altitude); latlonBox.setMaxAltitude(altitude+DELTA_L); latlonBox.setAltitudeMode(AltitudeMode.ABSOLUTE); Lod lod = innerRegion.createAndSetLod(); lod.setMinLodPixels(MIN_LOD_PIXELS_LEVEL_INTERNAL); lod.setMaxLodPixels(MAX_LOD_PIXELS_LEVEL_INTERNAL); lod.setMinFadeExtent(MIN_FADE_EXTENT); lod.setMaxFadeExtent(MAX_FADE_EXTENT); } /** * Returns true is the node has a valid location else returns false. * @param node * @return */ public static boolean hasValidLocation(PhylogenyNode node){ double latitude = getLatitude(node); double longitude = getLongitude(node); double altitude = getAltitude(node); if(latitude !=UNDEFINED && longitude!=UNDEFINED && altitude!=UNDEFINED){ return true; }else{ return false; } } /** * Returns the latitude of the PhylogenyNode node * @param node * @return */ public static double getLatitude(PhylogenyNode node){ double latitude = 0.0; NodeData nodeData = node.getNodeData(); Distribution dist = nodeData.getDistribution(); if(dist == null){ latitude = UNDEFINED; }else{ latitude = dist.getLatitude().doubleValue(); } return latitude; } /** * Returns the longitude of the PhylogenyNode node * @param node * @return */ public static double getLongitude(PhylogenyNode node){ double longitude = 0.0; NodeData nodeData = node.getNodeData(); Distribution dist = nodeData.getDistribution(); if(dist == null){ longitude = UNDEFINED; }else{ longitude = dist.getLongitude().doubleValue(); } return longitude; } /** * Returns the altitude of the PhylogenyNode node * @param node * @return */ public static double getAltitude(PhylogenyNode node){ double altitude = 0.0; NodeData nodeData = node.getNodeData(); Distribution dist = nodeData.getDistribution(); if(dist == null){ altitude = UNDEFINED; }else{ altitude = dist.getAltitude().doubleValue(); } double elevation = getElevation(node); return (altitude+elevation); } public static double getElevation(PhylogenyNode node){ double elevation = 0.0; elevation = ELEVATION; return elevation; } /** * Gets the color associated with node as hex string. * @param node * @return */ public static String getColor(PhylogenyNode node){ BranchData branchData = node.getBranchData(); BranchColor branchColor = branchData.getBranchColor(); Color c = branchColor.getValue(); String hexColor = rgbToHex(c); return hexColor; } /** * Converts a RGB color to a hex color in kml compliant form. * In kml colors are specified in the format aabbggrr. * @param c * @return */ public static String rgbToHex(Color c){ String color = null; color = decimalToHex(c.getAlpha()) + decimalToHex(c.getBlue()) + decimalToHex(c.getGreen())+ decimalToHex(c.getRed()); return color; } /** * Gets the lighter shade corresponding to the given color. * @param color * @return */ private static String getLighterShade(String color){ String lighterShade = color.substring(2); return "bb"+lighterShade; } /** * Returns the hexadecimal representation of the positive decimal number n.<br> * A two digit representation is provided by this function always. * @param n * @return */ private static String decimalToHex(int n){ assert(n>=0); String invHexRepr = ""; String hexRepr = ""; String[] digits = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; while(n>=16){ int remainder = n%16; invHexRepr = invHexRepr+digits[remainder]; n=n/16; } invHexRepr = invHexRepr+digits[n]; for(int i=invHexRepr.length()-1;i>=0;i--){ hexRepr = hexRepr + invHexRepr.substring(i, i+1); } if(hexRepr.length() < 2){ hexRepr = "0"+hexRepr; } return hexRepr; } /** * Gets the condensed name of a taxonomic unit.<br> * e.g. Homo Sapiens as H.Sapiens * e.g. Homo_Sapiens as H.Sapiens * @param name * @return */ public static String getCondensedName(String name){ String condensedName = null; String nameCopy = name.trim(); condensedName = Character.toString(nameCopy.charAt(0)); condensedName = condensedName+"."; int i = nameCopy.indexOf(' '); if(i==-1){ i = nameCopy.indexOf('_'); } if(i==-1){ condensedName = nameCopy.substring(i+1); }else{ condensedName = condensedName + nameCopy.substring(i+1); } return condensedName; } /** * Creates a placemark for an internal node.<br> * These nodes represent the hypothetical taxonomic units.<br> * @param folder * @param node * @param mould */ public static void createHTUPlacemark(Folder folder, PhylogenyNode node, PhylogenyMould mould){ if(!hasValidLocation(node)){ return; } //Specify the position of the node. double latitude = getLatitude(node); double longitude = getLongitude(node); double altitude = getAltitude(node); Integer id = node.getNodeId(); String name = node.getNodeName()+" HTU-"+id.toString(); //Create the level 1 folder. Folder outerFolder = folder.createAndAddFolder(); outerFolder.withName(name); //Create the level 1 placemark. (outermost placemark) Placemark outerPlacemark = outerFolder.createAndAddPlacemark(); String description = parlour.prepareHTMLContent(node, mould); outerPlacemark.setDescription(description); Point p = outerPlacemark.createAndSetPoint(); p.addToCoordinates(longitude, latitude, altitude); p.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND); //TODO: No regions have been added to Hypothetical Taxa placemarks. // Can be added in future; at present, the HTU's dont have any names. //Specify the color of this placemark. Style style = outerPlacemark.createAndAddStyle(); IconStyle iconStyle = style.createAndSetIconStyle(); String color = HTU_COLOR; iconStyle.setColor(color); iconStyle.setColorMode(ColorMode.NORMAL); iconStyle.setScale(0.50); //Specify the icon with this placemark. Icon icon = iconStyle.createAndSetIcon(); icon.setHref(HTU_ICON_HREF); icon.setRefreshInterval(4); icon.setViewRefreshMode(ViewRefreshMode.NEVER); icon.setViewRefreshTime(4); icon.setViewBoundScale(1); } /** * Creates the 3 edges from the parentNode to the childNode. * @param folder * @param parentNode * @param childNode */ public static void createBranch(Folder folder, PhylogenyNode parentNode, PhylogenyNode childNode){ String childColor = getColor(childNode); LineString line = KmlToolkit.drawNewStyledLine(folder, childColor); createVerticalEdge(line, parentNode, childNode); createCurvedEdge(line, parentNode, childNode); } /** * Creates the vertical edge joining each child node to the curved edge connected to its parent. * @param folder * @param parentNode * @param childNode */ private static void createVerticalEdge(LineString line, PhylogenyNode parentNode, PhylogenyNode childNode){ double parentAlt = getAltitude(parentNode); double childLat = getLatitude(childNode); double childLong = getLongitude(childNode); double childAlt = getAltitude(childNode); double sourceLat = childLat; double sourceLong = childLong; double sourceAlt = childAlt; double destLat = childLat; double destLong = childLong;//TODO: double destAlt = parentAlt;//FRACTION*(parentAlt-childAlt) + childAlt; line.addToCoordinates(sourceLong, sourceLat, sourceAlt); line.addToCoordinates(destLong, destLat, destAlt); } /** * Appends the curved portion of the edge between the child and parent node to line. * @param line * @param parentNode * @param childNode */ private static void createCurvedEdge(LineString line, PhylogenyNode parentNode, PhylogenyNode childNode){ double childLat = getLatitude(childNode); double childLong = getLongitude(childNode); double childAlt = getAltitude(childNode); double parentLat = getLatitude(parentNode); double parentLong = getLongitude(parentNode); double parentAlt = getAltitude(parentNode); double sourceLat = childLat; double sourceLong = childLong;//TODO: double sourceAlt = parentAlt;//FRACTION*(parentAlt-childAlt) + childAlt; double destLat = parentLat; double destLong = parentLong; double destAlt = parentAlt;//FRACTION*(parentAlt-childAlt) + childAlt; Coordinate fromCoord = new Coordinate(sourceLong, sourceLat, sourceAlt); Coordinate toCoord = new Coordinate(destLong, destLat, destAlt); KmlToolkit.drawCurvedLine(line, fromCoord, toCoord); } /** * Creates a polygon on the surface of the earth filled with the specified color. * @param latVector * @param lonVector * @param folder the folder in which the polygon is to be drawn. */ public static void createPolygon(Vector<Double> latVector, Vector<Double> lonVector, Folder folder, PhylogenyNode node){ Placemark polyPlacemark = folder.createAndAddPlacemark(); polyPlacemark.setName(node.getNodeName()); Style style = polyPlacemark.createAndAddStyle(); PolyStyle polyStyle = style.createAndSetPolyStyle(); String color = getLighterShade(getColor(node)); polyStyle.setColor(color); Region region = polyPlacemark.createAndSetRegion(); LatLonAltBox box = region.createAndSetLatLonAltBox(); double minLat = findMinPosition(latVector); double maxLat = findMaxPosition(latVector); double minLon = findMinPosition(lonVector); double maxLon = findMaxPosition(lonVector); box.setNorth(maxLat); box.setSouth(minLat); box.setEast(maxLon); box.setWest(minLon); Lod lod = region.createAndSetLod(); lod.setMaxLodPixels(1024.0); lod.setMinLodPixels(128.0); lod.setMinFadeExtent(64); lod.setMaxFadeExtent(256); Polygon polygon = polyPlacemark.createAndSetPolygon(); Boundary boundary = polygon.createAndSetOuterBoundaryIs(); LinearRing lRing = boundary.createAndSetLinearRing(); lRing.setTessellate(Boolean.TRUE); List<Coordinate> coordList = lRing.createAndSetCoordinates(); int l = latVector.size(); for(int i=0; i<l; i++){ Double lat = latVector.get(i); Double lon = lonVector.get(i); Coordinate coordinate = new Coordinate(lon, lat); coordList.add(coordinate); } Double lat = latVector.get(0); Double lon = lonVector.get(0); Coordinate coordinate = new Coordinate(lon, lat); coordList.add(coordinate); } /** * Finds the minimum position. * @param posVector * @return */ private static double findMinPosition(Vector<Double> posVector) { double minPos = posVector.elementAt(0); for(Double pos:posVector){ if(pos < minPos){ minPos = pos; } } return minPos; } /** * Finds the maximum position. * @param posVector * @return */ private static double findMaxPosition(Vector<Double> posVector) { double maxPos = posVector.elementAt(0); for(Double pos:posVector){ if(pos > maxPos){ maxPos = pos; } } return maxPos; } }