/* * DiscreteTreeToKML.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST 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 * of the License, or (at your option) any later version. * * BEAST 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. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.app.phylogeography.tools; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import dr.app.util.Arguments; import jebl.evolution.io.ImportException; import jebl.evolution.io.NexusImporter; import jebl.evolution.io.TreeImporter; import jebl.evolution.trees.RootedTree; /** * @author Philippe Lemey * @author Andrew Rambaut * @author Marc A. Suchard */ public class DiscreteTreeToKML { public static final String HELP = "help"; public static final String ANNOTATION = "annotation"; public static final String COORDINATES = "coordinates"; public static final String TIMESCALER = "timescaler"; public static final String MRSD = "mrsd"; public static final String BWC = "bwc"; public static final String BWM = "bwm"; public static final String DIVIDER = "divider"; public static final String USP = "usp"; public static final String BW = "bw"; public static final String BCUSE = "bcuse"; public static final String BCOLOR = "bcolor"; public static final String BSTARTCOLOR = "bstartcolor"; public static final String BENDCOLOR = "bendcolor"; public static final String ARCHBRANCH = "archbranch"; public static final String ARCHHEIGHT = "archheight"; public static final String ALTITUDE = "altitude"; public static final String TEMP = "temp"; public static final String CIRCLESEGMENTS = "circlesegments"; public static final String RADIUS = "radius"; public static final String AUTORADIUS = "autoradius"; public static final String CIRCLEOP = "circleop"; public static final String COORDSFORTAXA = "coordsfortaxa"; public static final String SLICES = "slices"; public static final String SLICEBW = "slicebw"; public static final String SLICEMIDPOINT = "slicemidpoint"; public static final String[] use = new String[] {"heights","posteriors"}; public static final String[] arch = new String[] {"distance","time"}; public static final String[] falseTrue = new String[] {"false","true"}; private static final PrintStream progressStream = System.out; private static final String commandName = "discrete_tree_to_kml"; public static void printUsage(Arguments arguments) { arguments.printUsage(commandName, "[<inputTree-file-name>] [<output-file-name>]"); //TODO: set this right progressStream.println(); progressStream.println(" Example: " + commandName + " -coordinates coordinates.txt input.tre"); progressStream.println(); } private static int[] countLinesAndTokens(String coordinatesFileString){ int lineCounter = 0; int tokenCounter = 0; int[] container = new int[2]; try{ BufferedReader reader1 = new BufferedReader(new FileReader(coordinatesFileString)); String current1 = reader1.readLine(); while (current1 != null && !reader1.equals("")) { lineCounter++; if (lineCounter == 1) { StringTokenizer tokens = new StringTokenizer(current1); while (tokens.hasMoreTokens()) { tokenCounter++; tokens.nextToken(); } } current1 = reader1.readLine(); } } catch (IOException e) { System.err.println("Error reading " + coordinatesFileString); System.exit(1); } container[0] = lineCounter; container[1] = tokenCounter; return container; } private static void readLocationsCoordinates(String coordinatesFileString, String[][] locationsAndCoordinates){ try { BufferedReader reader2 = new BufferedReader(new FileReader(coordinatesFileString)); String current2 = reader2.readLine(); int counter2 = 0; while (current2 != null && !reader2.equals("")) { StringTokenizer tokens2 = new StringTokenizer(current2); for (int i = 0; i < locationsAndCoordinates[0].length; i++) { locationsAndCoordinates[counter2][i] = tokens2.nextToken(); progressStream.print(locationsAndCoordinates[counter2][i]+"\t"); } progressStream.print("\r"); counter2 ++; current2 = reader2.readLine(); } } catch (IOException e) { e.printStackTrace(); return; } } public static double[] parseVariableLengthDoubleArray(String inString) throws Arguments.ArgumentException { List<Double> returnList = new ArrayList<Double>(); StringTokenizer st = new StringTokenizer(inString,","); while(st.hasMoreTokens()) { try { returnList.add(Double.parseDouble(st.nextToken())); } catch (NumberFormatException e) { throw new Arguments.ArgumentException(); } } if (returnList.size()>0) { double[] doubleArray = new double[returnList.size()]; for(int i=0; i<doubleArray.length; i++) doubleArray[i] = returnList.get(i); return doubleArray; } return null; } private static RootedTree readTree(String inString) throws Arguments.ArgumentException { RootedTree tree; try { TreeImporter importer = new NexusImporter(new FileReader(inString)); tree = (RootedTree)importer.importNextTree(); } catch (ImportException e) { e.printStackTrace(); return null; } catch (IOException e) { e.printStackTrace(); return null; } return tree; } public static void main(String[] args) throws Arguments.ArgumentException { String inputFileName = null; String outputFileName = null; RootedTree tree = null; String[][] locations = null; String stateAnnotation = "state"; //in case trees are scaled in other time units double timeScaler = 1; double branchWidthConstant = 2.0; // the width of branches will be stateProbability*branchWidthMultiplier+branchWidthConstant double branchWidthMultiplier = 5.0; double divider = 100; // this is to chop up the branches of the surface tree in 'divider' segments boolean useStateProbability = true; // use state probabilities for branch width double branchWidth = 10.0; // branch width if posterior probabilities are not used boolean usePosterior = false; // use posterior probabilities to color branch boolean useHeights = true; // use heights (time) to color branches String startBranchColor = "FF00FF"; //red: 0000FF green: 00FF00 magenta: FF00FF white: FFFFFF yellow: 00FFFF cyan: FFFF00 String endBranchColor = "FFFF00"; String branchColor = "ffffff"; // branch color if color range based on rates is not used boolean arcBranches = true; // branches are arcs with heights proportional to the distance between locations boolean arcTimeHeight = false; // the height of the arcs is proportional to the time the branch spans, by default arch-heights are proportional to the distance between locations double altitudeFactor = 100; // this is the factor with which to multiply the time of the branch to get the altitude for that branch in the surface Tree boolean temporary = false; double mostRecentDate = 2010; // required to convert heights to calendar dates //circles int numberOfIntervals = 100; //boolean autoRadius = false; double radius = 0; String circleOpacity = "8F"; //extra coordinates for some taxa boolean coordinatesForTaxa = false; String[][] taxaCoordinates = null; //tree slices for google maps, requires treeHeight in exporterString: e.g., exporterString.writeTreeToKML(0.025); //TODO: have this outputed to a different file (what happens now)? boolean makeTreeSlices = false; double[] sliceTimes = null; double treeSliceBranchWidth = 3; boolean showBranchAtMidPoint = false; // shows complete branch for slice if time is more recent than the branch's midpoint Arguments arguments = new Arguments( new Arguments.Option[]{ new Arguments.StringOption(COORDINATES, "coordinate file", "specifies a tab-delimited file with coordinates for the locations"), new Arguments.StringOption(ANNOTATION, "location state annotation string", "specifies the string used for location state annotation [default=state]"), new Arguments.RealOption(TIMESCALER,"specifies the scaling factor by which to rescale time [default=1]"), new Arguments.RealOption(MRSD,"specifies the most recent sampling data in fractional years to rescale time [default=2010]"), new Arguments.RealOption(BWC,"specifies the branch width constant [default=2]"), new Arguments.RealOption(BWM,"specifies the branch width multiplier [default=5]"), new Arguments.StringOption(USP, falseTrue, false, "use state probabilities for branch width [default = true]"), new Arguments.StringOption(BCUSE, use, false, "use heights or posterior probabilities for branch colors [default = heights]"), new Arguments.RealOption(BW,"specifies the branch width if posterior probabilities are not used [default=10]"), new Arguments.IntegerOption(DIVIDER,"specifies in how many segments at branch should be chopped up [default=50]"), new Arguments.StringOption(BSTARTCOLOR, "branch start color", "specifies a starting color for the branches [default=FF00FF]"), new Arguments.StringOption(BENDCOLOR, "branch end color", "specifies an end color for the branches [default=FFFF00]"), new Arguments.StringOption(BCOLOR, "branch color", "specifies a branch color if color range based on rates is not used [default=ffffff]"), new Arguments.StringOption(ARCHBRANCH, falseTrue, false, "use arcs for the branches [default = true], by default arc-heights are proportional to the distance between locations"), new Arguments.StringOption(ARCHHEIGHT, arch, false, "use time or distance for arch heights [default = no arcs]"), new Arguments.RealOption(ALTITUDE,"specifies the altitudefactor for the branches [default=1000]"), new Arguments.StringOption(TEMP, falseTrue, false, "display branches only temporary [default=false"), new Arguments.IntegerOption(CIRCLESEGMENTS,"specifies the number of segments to construct circles [default=100]"), new Arguments.IntegerOption(RADIUS,"specifies the radiusfactor for the circles [default='autoradius']"), new Arguments.StringOption(CIRCLEOP, "circle opacity", "sets the opacity of the circles [default=8F]"), new Arguments.Option(HELP, "option to print this message"), new Arguments.StringOption(COORDSFORTAXA, "file with taxa coords", "specifies a file with additional coordinates for particular taxa"), new Arguments.RealOption(SLICEBW,"specifies the branch width for tree slices [default=3]"), new Arguments.StringOption(SLICES,"time","specifies a slice time-list [default=none]"), new Arguments.StringOption(SLICEMIDPOINT, falseTrue, false, "shows complete branch for sliced tree if time is more recent than the branch's midpoint [default=false"), }); try { arguments.parseArguments(args); } catch (Arguments.ArgumentException ae) { progressStream.println(ae); printUsage(arguments); System.exit(1); } if (args.length == 0 || arguments.hasOption(HELP)) { printUsage(arguments); System.exit(0); } try { String coordinatesFileString = arguments.getStringOption(COORDINATES); //System.out.println(coordinatesFileString); if (coordinatesFileString != null) { // count lines in locations file and tokens per line int counts[] = countLinesAndTokens(coordinatesFileString); //System.out.println(counts[0]+"\t"+counts[1]); //read in locations locations = new String[counts[0]][counts[1]]; readLocationsCoordinates(coordinatesFileString,locations); } else { progressStream.println("no coordinates for taxa??"); System.exit(1); } if (arguments.hasOption(MRSD)) { mostRecentDate = arguments.getRealOption(MRSD); } if (arguments.hasOption(TIMESCALER)) { timeScaler = arguments.getRealOption(TIMESCALER); } if (arguments.hasOption(DIVIDER)) { divider = arguments.getRealOption(DIVIDER); } if (arguments.hasOption(ALTITUDE)) { altitudeFactor = arguments.getRealOption(ALTITUDE); } String stateAnnotationString = arguments.getStringOption(ANNOTATION); if (stateAnnotationString != null){ stateAnnotation = stateAnnotationString; } String useStateProbString = arguments.getStringOption(USP); if (useStateProbString != null && useStateProbString.compareToIgnoreCase("posteriors") != 0) useStateProbability = false; String useColorString = arguments.getStringOption(BCUSE); if (useColorString != null && useColorString.compareToIgnoreCase("posteriors") == 0) { usePosterior = true; useHeights = false; } if (useColorString != null && useColorString.compareToIgnoreCase("heights") == 0) { useHeights = true; usePosterior = false; } if (arguments.hasOption(BWC)) { branchWidthConstant = arguments.getRealOption(BWC); } if (arguments.hasOption(BWM)) { branchWidthMultiplier = arguments.getRealOption(BWM); } if (arguments.hasOption(BW)) { branchWidth = arguments.getRealOption(BW); } String color1String = arguments.getStringOption(BSTARTCOLOR); if (color1String != null) { startBranchColor = color1String; } String color2String = arguments.getStringOption(BENDCOLOR); if (color2String != null) { endBranchColor = color2String; } String colorString = arguments.getStringOption(BCOLOR); if (colorString != null) { branchColor = colorString; } String archString = arguments.getStringOption(ARCHBRANCH); if (archString != null && archString.compareToIgnoreCase("false") == 0) arcBranches = false; String archHeightString = arguments.getStringOption(ARCHHEIGHT); if (archHeightString != null && archHeightString.compareToIgnoreCase("time") == 0) { arcTimeHeight = true; } String tempString = arguments.getStringOption(TEMP); if (tempString != null && tempString.compareToIgnoreCase("true") == 0) temporary = true; if (arguments.hasOption(CIRCLESEGMENTS)) { numberOfIntervals = arguments.getIntegerOption(CIRCLESEGMENTS); } if (arguments.hasOption(RADIUS)) { radius = arguments.getIntegerOption(RADIUS); } String circleOpacityString = arguments.getStringOption(CIRCLEOP); if (circleOpacityString != null) { circleOpacity = circleOpacityString; } //read in extra taxon locations String taxaCoordinatesFileString = arguments.getStringOption(COORDSFORTAXA); if (taxaCoordinatesFileString != null) { coordinatesForTaxa = true; progressStream.println("\radditional taxa locations:"); // count lines in locations file and tokens per line int[] counts = countLinesAndTokens(taxaCoordinatesFileString); //read in locations taxaCoordinates = new String[counts[0]][counts[1]]; readLocationsCoordinates(taxaCoordinatesFileString,taxaCoordinates); } String sliceString = arguments.getStringOption(SLICES); if (sliceString != null) { makeTreeSlices = true; sliceTimes = parseVariableLengthDoubleArray(sliceString); } if (arguments.hasOption(SLICEBW)) { treeSliceBranchWidth = arguments.getRealOption(SLICEBW); } String midpointString = arguments.getStringOption(SLICEMIDPOINT); if (midpointString != null && midpointString.compareToIgnoreCase("true") == 0) showBranchAtMidPoint = true; } catch (Arguments.ArgumentException e) { progressStream.println(e); printUsage(arguments); System.exit(-1); } final String[] args2 = arguments.getLeftoverArguments(); outputFileName = args2[0]+".kml"; switch (args2.length) { case 0: printUsage(arguments); System.exit(1); case 2: outputFileName = args2[1]; // fall to case 1: inputFileName = args2[0]; tree = readTree(inputFileName); break; default: { System.err.println("Unknown option: " + args2[2]); System.err.println(); printUsage(arguments); System.exit(1); } } DiscreteKMLString exporterString = new DiscreteKMLString(tree, stateAnnotation, locations, inputFileName, mostRecentDate, timeScaler, divider, branchWidthConstant, branchWidthMultiplier, useStateProbability, branchWidth, startBranchColor, endBranchColor, branchColor, useHeights, usePosterior, arcBranches, arcTimeHeight, altitudeFactor, temporary, numberOfIntervals, radius, circleOpacity, coordinatesForTaxa, taxaCoordinates, makeTreeSlices); try { BufferedWriter out1 = new BufferedWriter(new FileWriter(outputFileName)); StringBuffer buffer = new StringBuffer(); if (makeTreeSlices) { for (int i = 0; i < sliceTimes.length; i++) exporterString.writeTreeToKML(sliceTimes[i], treeSliceBranchWidth, showBranchAtMidPoint); } else { exporterString.writeTreeToKML(); exporterString.writeLineagesToCircles(); } exporterString.writeLocationsKML(); exporterString.compileBuffer(buffer); out1.write(buffer.toString()); out1.close(); } catch (IOException e) { e.printStackTrace(); return; } } }