/* * 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.reader; import java.awt.Color; import static java.lang.System.out; import java.io.File; import java.math.BigDecimal; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import nescent.phylogeoref.processor.utility.ComputeUtility; import org.forester.phylogeny.Phylogeny; 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; import org.forester.phylogeny.data.Taxonomy; /** * Takes a raw, crude phylogenetic tree and metadata from a secondary file * and converts it into a cooked, full-fledged phylogenetic tree. * * @author apurv */ public class PhylogenyKitchen { private final static Logger LOGGER = Logger.getLogger("nescent"); private File metaFile; private Phylogeny phy; private int numCol; // number of columns in the input text file. private int numMaxCol; //maximum number of columns in var args. private char delim; //Delimiter character private int cladeDiv; // clade classifier. // The values in this column will be used in classification in clades. private UniversalMetadataReader in; //Reader for the metadata file. private Map<String,PhylogenyMould> map; private Map<String,BranchColor> cladeColorMap; private int index[]; private String[] properties = null; private String[] propertyNames = null; private boolean[] attendanceInMould = null; private Random r; private Random g; private Random b; private final static int NaN = -1; private final static String[] PROPERTIES = {"label", "latitude", "longitude", "id", "sname", //Scientific Name "cname", //Common Name }; public PhylogenyKitchen(File metaFile, Phylogeny phy, Map<String,PhylogenyMould> map, char delim, int cladeDiv) { this.metaFile = metaFile; this.phy = phy; this.map = map; this.delim = delim; this.cladeDiv = cladeDiv-1; //Subtracted 1 because 1st column means 0th index in an array. numCol = 0; if(this.cladeDiv !=NaN){ cladeColorMap = new HashMap<String, BranchColor>(); } r = new Random(); g = new Random(); b = new Random(); in = new UniversalMetadataReader(this.metaFile, this.delim); } /** * * @param args * @see NeXMLReader readPhylogeny() */ public void setRecipe(Integer...args){ int l = PROPERTIES.length; numMaxCol = l; index = new int[l]; Arrays.fill(index,0, l,NaN); for(int i=0;i<args.length;i++){ if(args[i]!=0){ numCol++; index[i]=args[i]-1; } } } /** * Cooks the raw data and prepare a cooked Phylogeny with all the metadata. */ public void cookDish(){ //Store the names of the properties which occur in the first row. propertyNames = in.readLine(); setAttendanceInMould(); displayRecipe(propertyNames); while ((properties = in.readLine()) != null) { String label = get("label"); PhylogenyMould mould = map.get(label); if(mould!=null){ PhylogenyNode node = mould.getAssociatedNode(); mixIngredients(node); fillMould(mould); } else { //Ignore any node whose details are given in the text file but not in the phylogeny. LOGGER.log(Level.CONFIG, "Ignored node named "+label+" in the metaFile"); } } } /** * Sets the values of the array attendanceInMould[].<br> * attendaceInMould[i] = true denotes that value in 'i'th column will be stored in the external mould * attached with the phylogeny node. */ private void setAttendanceInMould(){ int l = propertyNames.length; attendanceInMould = new boolean[l]; Arrays.fill(attendanceInMould,0, l,true); for(int i=0;i<index.length;i++){ if(index[i]>=0){ attendanceInMould[index[i]] = false; } } } /** * displays the recipe. * @param headerLine */ private void displayRecipe(String[] headerLine){ //LOGGER.log(Level.FINEST, "The recipe you specified is as follows"); out.println("The recipe you specified is as follows"); for(int i=0;i<numMaxCol;i++){ if( index[i] != NaN){ String part1 = String.format("%18s", headerLine[index[i]]); String part2 = String.format("%18s", PROPERTIES[i]); //LOGGER.log(Level.FINEST, part1 + " maps to: "+part2); out.println(part1 + " maps to: "+part2); } } } /** * Fills the extraneous values in the mould. * @param mould */ private void fillMould(PhylogenyMould mould){ for(int i=0;i<attendanceInMould.length;i++){ if(attendanceInMould[i]){ mould.storeValue(propertyNames[i], properties[i]); } } } private String getCladeValue(){ String cladeValue = null; if(cladeDiv == NaN) cladeValue = null; else cladeValue = properties[cladeDiv]; return cladeValue; } /** * Generates and returns a random color. * @return */ private Color getRandomColor(){ float red = r.nextFloat(); float green = g.nextFloat(); float blue = b.nextFloat(); Color c = new Color(red, green, blue); return c; } /** * Gets the value of the property as a String. Null if the property is not present. * @param propName * @return "" if the index for that property was not specified or was zero. */ private String get(String propName){ int colNo = index(propName); String value = ""; if(colNo != NaN){ value = properties[colNo]; } return value; } /** * * @param property * @return the column number of property. */ private int index(String property){ int ordinal = NaN; for(int i=0; i<PROPERTIES.length;i++){ if(property.equalsIgnoreCase(PROPERTIES[i])){ // index[i] is the column number of the ith property, i.e. PROPERTY[i]. ordinal = index[i]; } } return ordinal; } /** * Put your logic for patching values on the node here.<br><br> * * Set all the values for node.<br> * To get the value of any property use get(property name as specified in the string PROPERTIES) * @param node */ private void mixIngredients(PhylogenyNode node)throws NullPointerException{ PhylogenyMould mould = map.get(node.getNodeName()); setNodeData(node.getNodeData(), mould); // If the user has specified a colums to be taken as clade, color this node. setBranchData(node.getBranchData()); //Set the clade value if it is not null. if(getCladeValue()!=null){ String name = node.getNodeName(); PhylogenyMould phyMould = map.get(name); phyMould.setClade(getCladeValue()); } } /** * Sets node data for this node. * * @param nodeData */ private void setNodeData(NodeData nodeData, PhylogenyMould mould){ Taxonomy taxo = new Taxonomy(); nodeData.setTaxonomy(taxo); taxo.setScientificName(get("sname")); taxo.setCommonName(get("cname")); Double lat = Double.parseDouble(get("latitude")); Double lon = Double.parseDouble(get("longitude")); mould.addLatitude(lat); mould.addLongitude(lon); int n = mould.getNumObservations(); if(n == 0){ Distribution dist = new Distribution(get("label")); nodeData.setDistribution(dist); dist.setLatitude(new BigDecimal(lat)); dist.setLongitude(new BigDecimal(lon)); dist.setAltitude(BigDecimal.ZERO); n++; mould.setNumObservations(n); }else{ Distribution dist = nodeData.getDistribution(); Double curLat = dist.getLatitude().doubleValue(); double sigmaLat = curLat*n + lat; n++; Double meanLat = sigmaLat/n; double meanLon = ComputeUtility.findMeanPosition(mould.getLonVector()); dist.setLatitude(new BigDecimal(meanLat)); dist.setLongitude(new BigDecimal(meanLon)); dist.setAltitude(BigDecimal.ZERO); mould.setNumObservations(n); } } /** * Sets the branch data for this node. * @param nodeData */ private void setBranchData(BranchData branchData){ String clade = getCladeValue(); //If no clade has been specfied then assign the same color to all nodes. if(cladeColorMap == null){ Color constantColor = Color.RED; BranchColor bc = new BranchColor(constantColor); branchData.setBranchColor(bc); return; } if(cladeColorMap.containsKey(clade)){ BranchColor bc = cladeColorMap.get(clade); branchData.setBranchColor(bc); } else{ Color newRandomColor = getRandomColor(); BranchColor bc = new BranchColor(newRandomColor); branchData.setBranchColor(bc); cladeColorMap.put(clade, bc); } } }