/*
* Copyright (C) 2010 Kathryn Iverson <kd.iverson at gmail.com>
*
* 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;
import java.math.BigDecimal;
import java.util.ListIterator;
import java.util.ArrayList;
import java.util.List;
import org.forester.phylogeny.Phylogeny;
import org.forester.phylogeny.PhylogenyNode;
import org.forester.phylogeny.data.Distribution;
import org.forester.phylogeny.data.NodeData;
import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
import org.forester.phylogeny.PhylogenyMethods;
/**
*This class handles the calculations of the trees.
*
* @author Kathryn Iverson <kd.iverson at gmail.com>
*/
public class Calc3DTree {
/**
* This function assigns coordinates to the external nodes (leafs) from the coordfile
*
* @param my_phy
* @param coordList
*/
private void assignExtenalNodeDistribution (Phylogeny my_phy, ArrayList coordList) {
if (coordList.get(0) instanceof Triple) {
for(ListIterator<Triple> li = coordList.listIterator(); li.hasNext();) {
Triple item = li.next();
String species = (String) item.getFirst();
BigDecimal lat = (BigDecimal) item.getSecond();
BigDecimal lng = (BigDecimal) item.getThird();
PhylogenyNode node = my_phy.getNode(species);//ext_it.next(); look into fuzzy matching
NodeData data = node.getNodeData();
node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
dist.setLatitude(lat); //from geo coord file
dist.setLongitude(lng); //from geo coord file
dist.setAltitude(BigDecimal.ZERO);//always 0
}
}
else if (coordList.get(0) instanceof Quad){
for(ListIterator<Quad> li = coordList.listIterator(); li.hasNext();) {
Quad item = li.next();
String species = (String) item.getFirst();
BigDecimal lat = (BigDecimal) item.getSecond();
BigDecimal lng = (BigDecimal) item.getThird();
//Should decide what to do with this
String metadata = (String) item.getFourth();
PhylogenyNode node = my_phy.getNode(species);//ext_it.next();
NodeData data = node.getNodeData();
node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
dist.setLatitude(lat); //from geo coord file
dist.setLongitude(lng); //from geo coord file
dist.setAltitude(BigDecimal.ZERO);//always 0
dist.setDescription(metadata);
}
}
else{
System.out.println("Something wrong with coordList: didn't find type Triple or Quad");
}
}
/**
* This implements the Janies et al. 2007 height algorithm
* @param node
*/
private void assignNodeAltitude(PhylogenyNode node) {
NodeData data = node.getNodeData();
//Don't need this if alt has already been assigned
//node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
if (node.isInternal()) {
//calc altitude for leafs, nodeAltitude = a + ((n-1)*b)
//BigDecimal [] alt = new BigDecimal [countNodes(my_phy)];
double n = PhylogenyMethods.calculateDistanceToRoot(node);
//double n = node.getNumberOfParents();
int a = 198000; //from Janies et al. 2007
int b = 66000; //from Janies et al. 2007
double theAlt = a + ((n-1)*b);
//alt = BigDecimal.valueOf(theAlt);
BigDecimal alt = BigDecimal.valueOf(theAlt);//new BigDecimal(theAlt);
dist.setAltitude(alt);
System.out.println("number of patents: ");
System.out.println(n);
}
else if (node.isExternal() ) dist.setAltitude(BigDecimal.ZERO);
//else if (node.isRoot()) dist.setAltitude(BigDecimal.valueOf(1000000));
}
/**
* linear stretch algorithm from GeoPhyloBuilder 1.1
* converts a non-ultrametric tree to an ultrametric tree
* @param node
* @param maxLtree
*/
private void assignNodeAltitude(PhylogenyNode node, int maxLtree){
NodeData data = node.getNodeData();
Distribution dist = data.getDistribution();
if (node.isInternal()) {
//right now all we can do is ignore branch lengths
//short nLtip = PhylogenyMethods.calculateMaxBranchesToLeaf(node);
//double nLtip = (double) nLt;
int nLtip = node.getNumberOfDescendants();
double nLroot = node.getNumberOfParents();
//double nLroot = PhylogenyMethods.calculateDistanceToRoot(node);
short zMult = 1;
System.out.println(nLtip);
System.out.println(nLroot);
System.out.println((nLtip/(nLroot/nLtip)*maxLtree)*zMult);
double nodeZ = (nLtip/(nLroot/nLtip)*maxLtree)*zMult;
//double test = nodeZ;
//BigDecimal t = new BigDecimal("123");
System.out.println(nodeZ);
BigDecimal alt = BigDecimal.valueOf(nodeZ*100000);
//BigDecimal big = new BigDecimal(1000000000);
dist.setAltitude(alt);
}
else dist.setAltitude(BigDecimal.ZERO);
}
/**
* Algorithm: Each external node, "leaf" is assigned a lat/long from the coordlist
* all subsequent nodes are then placed in the middle of the child nodes at altitude alt.
* TODO: still need to add xmeridian check
* This is probably not a necessary function, I think the regular assignNodeCoords will do
* the same thing as this function.
*
* @param my_phy
* @param coordList
*/
public void assignBinaryNodes (Phylogeny my_phy, ArrayList coordList) {
assignExtenalNodeDistribution(my_phy, coordList);
for( PhylogenyNodeIterator ext_it = my_phy.iteratorPreorder(); ext_it.hasNext();) {
PhylogenyNode node = ext_it.next();
NodeData data = node.getNodeData();
if ( !node.isExternal() ) {
node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
PhylogenyNode firstChild = node.getFirstChildNode();
NodeData fcn = firstChild.getNodeData();
Distribution fcd = fcn.getDistribution();
BigDecimal firstChildLat = fcd.getLatitude();
BigDecimal firstChildLong = fcd.getLongitude();
PhylogenyNode lastChild = node.getLastChildNode();
NodeData lcn = lastChild.getNodeData();
Distribution lcd = lcn.getDistribution();
BigDecimal lastChildLat = lcd.getLatitude();
BigDecimal lastChildLong = lcd.getLongitude();
BigDecimal two = new BigDecimal("2");
//Assigns the node a lat/long pair that is in the midpoint of the two children
dist.setLatitude(firstChildLat.add(lastChildLat).divide(two));
dist.setLongitude(firstChildLong.add(lastChildLong).divide(two));
}
//assignNodeAltitude(node, maxLtree);
//uncoment the following line for the Janis et al tree height calculation
assignNodeAltitude(node);
}
}
/**
* This just calls assignBinaryNodes -- it was mostly used for testing and
* can probably be removed
*
* @param my_phy
* @param coordList
*/
public void lazyAssignNodeCoords (Phylogeny my_phy, ArrayList coordList) {
assignBinaryNodes(my_phy, coordList);
}
/**
* This doesn't quite work yet -- don't use it unless you fix it
* The best way I can think to do this is create a polygon in KML that
* is bound by the points and put the leaf of the tree in the center of the
* polygon.
*
* @param my_phy
* @param coordList
*/
public void assignMultipleObservations (Phylogeny my_phy, ArrayList coordList) {
//similar to other assignNode functions but altitude needs to change; leaves should be at
//some nonzero altitude, then assignment can continue as normal.
//
for( PhylogenyNodeIterator ext_it = my_phy.iteratorPostorder(); ext_it.hasNext();) {
PhylogenyNode node = ext_it.next();
NodeData data = node.getNodeData();
Distribution dist = data.getDistribution();
//coordlist is an array of triples (or quads) like this: [(species,lat,long,metadata), (species2, lat, long, metadata)]
//iterate through coordList [(species,lat,long,metadata), (species2, lat, long, metadata)]
ArrayList childCoordsLat = new ArrayList();
ArrayList childCoordsLong = new ArrayList();
BigDecimal latSum = BigDecimal.ZERO;
BigDecimal longSum = BigDecimal.ZERO;
int c = 1;
for (int i=0; i < node.getNumberOfDescendants(); i++){
//do mean calcs
PhylogenyNode childNode = node.getChildNode(i);
NodeData childData = childNode.getNodeData();
Distribution childDist = childData.getDistribution();
BigDecimal childLat = childDist.getLatitude();
BigDecimal childLong = childDist.getLongitude();
childCoordsLat.add(childLat);
childCoordsLong.add(childLong);
latSum = latSum.add(childLat);
longSum = longSum.add(childLong);
c++;
}
BigDecimal count = new BigDecimal(c);
BigDecimal meanLat = latSum.divide(count,BigDecimal.ROUND_CEILING);
BigDecimal meanLong = longSum.divide(count,BigDecimal.ROUND_CEILING);
dist.setLatitude(meanLat);
dist.setLongitude(meanLong);
int maxLtree = PhylogenyMethods.calculateMaxDepth(my_phy);
System.out.println(maxLtree);
if (node.isExternal() ) dist.setAltitude(new BigDecimal("198000"));
else assignNodeAltitude(node, maxLtree);
assignNodeAltitude(node, maxLtree);
//uncoment the following line for the Janis et al tree height calculation
//assignNodeAltitude(node);
}
}
/**
* This function assigned coordinates to the nodes of the tree.
*
* @param my_phy
* @param coordList
*/
public void assignNodeCoords(Phylogeny my_phy, ArrayList coordList) {
if (my_phy.isCompletelyBinary()) {
assignBinaryNodes(my_phy, coordList);
}
assignExtenalNodeDistribution(my_phy, coordList);
//IMPLEMNTS MEAN POSITION ALGORITHM
//Basic idea: get the mean of the lat and long of all the decendents of each node
//then assigns the mean to the node
for( PhylogenyNodeIterator ext_it = my_phy.iteratorPostorder(); ext_it.hasNext();) {
PhylogenyNode node = ext_it.next();
NodeData data = node.getNodeData();
//coordlist is an array of triples (or quads) like this: [(species,lat,long,metadata), (species2, lat, long, metadata)]
//iterate through coordList [(species,lat,long,metadata), (species2, lat, long, metadata)]
ArrayList childCoordsLat = new ArrayList();
ArrayList childCoordsLong = new ArrayList();
BigDecimal latSum = BigDecimal.ZERO;
BigDecimal longSum = BigDecimal.ZERO;
int c = 0;
//this assumes we already assigned external nodes
if ( !node.isExternal() ) {
node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
for (int i=0; i < node.getNumberOfDescendants(); i++){
//do mean calcs
PhylogenyNode childNode = node.getChildNode(i);
NodeData childData = childNode.getNodeData();
Distribution childDist = childData.getDistribution();
BigDecimal childLat = childDist.getLatitude();
BigDecimal childLong = childDist.getLongitude();
childCoordsLat.add(childLat);
childCoordsLong.add(childLong);
latSum = latSum.add(childLat);
longSum = longSum.add(childLong);
c++;
}
BigDecimal count = new BigDecimal(c);
//BigDecimal latAbs = latSum.abs();
//BigDecimal longAbs = longSum.abs();
BigDecimal meanLat = latSum.divide(count,BigDecimal.ROUND_CEILING);
BigDecimal meanLong = longSum.divide(count,BigDecimal.ROUND_CEILING);
dist.setLatitude(meanLat);
dist.setLongitude(meanLong);
}
int maxLtree = PhylogenyMethods.calculateMaxDepth(my_phy);
System.out.println(maxLtree);
assignNodeAltitude(node, maxLtree);
//assignNodeAltitude(node);
}
}
/**
* This function has a feature that checks if any nodes have children that cross the 180 meridian.
* If so, it will do a special calculation to determine the midpoint. It is safe to use this
* function even if your tree doesn't cross the 180 meridian.
* @param my_phy
* @param coordList
*/
public void assignMeridianCoords (Phylogeny my_phy, ArrayList coordList) {
if (my_phy.isCompletelyBinary()) {
assignBinaryNodes(my_phy, coordList);
}
assignExtenalNodeDistribution(my_phy, coordList);
//IMPLEMNTS MEAN POSITION ALGORITHM
//Basic idea: get the mean of the lat and long of all the decendents of each node
//then assigns the mean to the node
for( PhylogenyNodeIterator ext_it = my_phy.iteratorPostorder(); ext_it.hasNext();) {
PhylogenyNode node = ext_it.next();
NodeData data = node.getNodeData();
//coordlist is an array of triples (or quads) like this: [(species,lat,long,metadata), (species2, lat, long, metadata)]
//iterate through coordList [(species,lat,long,metadata), (species2, lat, long, metadata)]
ArrayList childCoordsLat = new ArrayList();
ArrayList childCoordsLong = new ArrayList();
BigDecimal latSum = BigDecimal.ZERO;
BigDecimal longSum = BigDecimal.ZERO;
int c = 0;
//this assumes we already assigned external nodes
if ( !node.isExternal() ) {
node.getNodeData().setDistribution(new Distribution(""));
Distribution dist = data.getDistribution();
boolean xmeridian = false;
int neg = 0;
for (int i=0; i < node.getNumberOfDescendants(); i++){
//do mean calcs
PhylogenyNode childNode = node.getChildNode(i);
NodeData childData = childNode.getNodeData();
Distribution childDist = childData.getDistribution();
BigDecimal childLat = childDist.getLatitude();
BigDecimal childLong = childDist.getLongitude();
List<PhylogenyNode> decendents = node.getDescendants();
//int neg = 0;
for (PhylogenyNode descendent : decendents) {
NodeData descendentData = descendent.getNodeData();
Distribution descendentDist = descendentData.getDistribution();
if (descendentDist.getLongitude().floatValue() < 0 && descendentDist.getLongitude().floatValue() < -90) {
neg++;
System.out.println("crossing the meridian...");
System.out.println(neg);
}
}
if (neg != decendents.size() && neg != 0) xmeridian = true;
childCoordsLat.add(childLat);
childCoordsLong.add(childLong);
latSum = latSum.add(childLat);
longSum = longSum.add(childLong);
c++;
}
BigDecimal count = new BigDecimal(c);
BigDecimal meanLat = latSum.divide(count,BigDecimal.ROUND_CEILING);
BigDecimal meanLong = longSum.divide(count,BigDecimal.ROUND_CEILING);
if (xmeridian == true) {
//if meanLong is negative
int midPtSign;
if (meanLong.compareTo(BigDecimal.ZERO) == -1) {
//if midPtSign is 1 then the midpoint value should be positive
midPtSign = 1;
meanLong = meanLong.abs();
}
else midPtSign = -1;
BigDecimal oneEighty = new BigDecimal("180");
//meanLat = (oneEighty.subtract(meanLat)).multiply(BigDecimal.ONE.negate());
meanLong = oneEighty.subtract(meanLong);
//switch the sign if the origional meanLong was negative
if (midPtSign == -1) meanLong = meanLong.negate();
}
dist.setLatitude(meanLat);
dist.setLongitude(meanLong);
}
int maxLtree = PhylogenyMethods.calculateMaxDepth(my_phy);
System.out.println(maxLtree);
assignNodeAltitude(node, maxLtree);
//assignNodeAltitude(node);
}
}
}
// private int countNodes(Phylogeny my_phy) {
// int c = 0;
// for( PhylogenyNodeIterator ext_it = my_phy.iteratorPostorder(); ext_it.hasNext();) {
// c++;
// }
// return c;
// }
//This is SUPER ugly but here's how it works:
//we need to get the distance between the two child nodes
//so this is an inplementation of the distance formula.
//BigDecimal doesn't have square root or powers so it has
//to be done the old fashioned way.
// double distance = Math.sqrt( ( firstChildLat.subtract(lastChildLat)
// ).multiply(firstChildLat.subtract(lastChildLat) ).add(
// ( firstChildLong.subtract(lastChildLong)
// ).multiply(firstChildLong.subtract(lastChildLong)) ).doubleValue() );
//calc lat long
//BigDecimal [] latLong = new BigDecimal [countNodes(my_phy)];
//function that determins the lat and long of each node
//assigns lat and long to the leafs from the coord file
//external nodes are leafs
//Triple speciesCoord;
//ListIterator li = coordList.listIterator();
//MAY NOT NEED TO ITERATE THROGUH NODES. CAN JUST CALL THE NODE BY SPECIES NAME!!!
//for( PhylogenyNodeIterator ext_it = my_phy.iteratorExternalForward(); ext_it.hasNext();) {
// PhylogenyNode node = my_phy.getNode(null);//ext_it.next();
// NodeData data = node.getNodeData();
// Distribution dist = data.getDistribution();
// Taxonomy tax = data.getTaxonomy();
// String name = tax.getScientificName();
// if (i+1 < node.getNumberOfDescendants()) {
// PhylogenyNode nextChild = node.getChildNode(i+1);
// NodeData nextChildData = nextChild.getNodeData();
// Distribution nextChildDist = nextChildData.getDistribution();
//
// if (childLong.floatValue() < 0 && nextChildDist.getLongitude().floatValue() > 0) {
// xmeridian = true;
// System.out.println("crossing the xmeridian...");
// }
// }
// private BigDecimal [] calcNodeLatLong (Phylogeny my_phy) {
// //calc lat long latLong[lat,long,lat,long...]
// BigDecimal [] latLong = new BigDecimal [countNodes(my_phy)];
// //function that determins the lat and long of each node
//
// return latLong;
// }
// private void assignLatLong(Phylogeny my_phy, PhylogenyNode node) {
//
// BigDecimal lat = BigDecimal.TEN;
// BigDecimal lng = BigDecimal.TEN;
//
// //linear strech algorithm from GeoPhyloBuilder 1.1
// //converts a non-ultrametric tree to an ultrametric tree
// //right now all we can do is ignore branch lengths
// short nLtip = PhylogenyMethods.calculateMaxBranchesToLeaf(node);
// double nLroot = PhylogenyMethods.calculateDistanceToRoot(node);
// int maxLtree = PhylogenyMethods.calculateMaxDepth(my_phy);
// short zMult = 1;
//
// double nodeZ = (nLtip/(nLroot/nLtip)*maxLtree)*zMult;
//
// NodeData data = node.getNodeData();
// node.getNodeData().setDistribution(new Distribution(""));
// Distribution dist = data.getDistribution();
// dist.setLatitude(lat);
// dist.setLongitude(lng);
//
// }