/*
* 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.utility;
import java.math.BigDecimal;
import static java.lang.System.out;
import java.net.URI;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.forester.phylogeny.PhylogenyNode;
import org.forester.phylogeny.data.BranchData;
import org.forester.phylogeny.data.Distribution;
import org.forester.phylogeny.data.Identifier;
import org.forester.phylogeny.data.NodeData;
import org.forester.phylogeny.data.PropertiesMap;
import org.forester.phylogeny.data.Property;
import org.forester.phylogeny.data.Property.AppliesTo;
import org.nexml.model.Annotation;
import org.nexml.model.Edge;
import org.nexml.model.FloatEdge;
import org.nexml.model.Network;
import org.nexml.model.Node;
import org.nexml.model.OTU;
/**
* Provides the necessary utility functions while transforming a parsed NeXML document
* into a Phylogeny tree.<br>
* It is important to note that there are two nodes and edges used in this class.<br>
* (1) org.forester.phylogeny.PhylogenyNode<br>
* (2) org.nexml.model.Node<br><br>
*
* A successful transformation will grab all the data from a Node and put it into the
* PhylogenyNode.<br><br>
*
* Similarly there are two edges.<br>
* (1) org.nexml.model.Edge<br>
* (2) org.forester.phylogeny.Edge<br>
*
* @author apurv
*/
public class PhyloUtility {
/**
* Finds and returns the root node of the tree.
* @param network
* @return the root node
*/
public static Node getRootNode(Network<FloatEdge> network){
Node rootNode=null;
for(Node node:network.getNodes()){
if(node.isRoot()){
rootNode=node;
}
}
return rootNode;
}
/**
* Transforms Node to a PhylogenyNode.<br>
* Does not attach the metadata.<br><br>
*
* @param fromNode the node from which data is to be extracted.
* @param toNode the node to which data has to be copied.
* @return a PhylogenyNode analogous to node.
*/
public static PhylogenyNode toPhylogenyNode(Node node){
PhylogenyNode phyNode = new PhylogenyNode();
phyNode.setName(node.getLabel());
int id = getNumberFromMetaId(node.getId());
//TODO: This can't be uncommented because setNodeId() is automatically managed.
//phyNode.setNodeId(id);
return phyNode;
}
/**
* Attaches latitude and longitudes to each node.<br>
* The latitude/longitude metadata can be attached to the node in two ways.<br>
* 1) Either in the OTU which is referenced by this node.<br>
* 2) Or directly within this node.<br><br>
*
* Caveat: Should be called only for external nodes.<br>
* @see http://rs.tdwg.org/dwc/2009-07-06/terms/simple/index.htm <br><br>
*
* @param node the node in nexml document
* @param phyNode the phylogeny tree node
*/
public static void attachLocationMetadata(Node node, PhylogenyNode phyNode){
//TODO: Check this
URI namespaceURI = URI.create("http://rs.tdwg.org/dwc/terms/");
//Case 1: Location metadata is stored in an OTU.
OTU otu = node.getOTU();
if(otu!=null){
Set<Object> latitudes = otu.getAnnotationValues("dwc:decimalLatitude");
Set<Object> longitudes = otu.getAnnotationValues("dwc:decimalLongitude");
if(!latitudes.isEmpty() && !longitudes.isEmpty()){
Double lat = (Double)latitudes.iterator().next();
Double lon = (Double)longitudes.iterator().next();
Distribution dist = new Distribution(phyNode.getNodeName());
phyNode.getNodeData().setDistribution(dist);
phyNode.getNodeData().getDistribution().setLatitude(new BigDecimal(lat));
phyNode.getNodeData().getDistribution().setLongitude(new BigDecimal(lon));
phyNode.getNodeData().getDistribution().setAltitude(BigDecimal.ZERO);
}
}
//Case 2: Location metadata is attached to the node itself.
Set<Object> latitudes = node.getAnnotationValues("dwc:decimalLatitude");
Set<Object> longitudes = node.getAnnotationValues("dwc:decimalLongitude");
if(!latitudes.isEmpty() && !longitudes.isEmpty()){
Double lat = (Double)latitudes.iterator().next();
Double lon = (Double)longitudes.iterator().next();
Distribution dist = new Distribution(phyNode.getNodeName());
phyNode.getNodeData().setDistribution(dist);
phyNode.getNodeData().getDistribution().setLatitude(new BigDecimal(lat));
phyNode.getNodeData().getDistribution().setLongitude(new BigDecimal(lon));
phyNode.getNodeData().getDistribution().setAltitude(BigDecimal.ZERO);
}
//throw new LocationNotFoundException(node.getId(),node.getLabel());
}
/**
* Maps the various metadata properties to a phylogeny node.<br>
* This function may require a revision because a perfect mapping between
* NeXML and Phylogeny object doesn't exist. Since the forester library
* was made keeping the PhyloXML format in mind.<br><br>
*
* TODO: This URI needs to be checked,however it's not used because of the leniency of the api.
*
* @param node
* @param phyNode
*/
public static void attachOtherMetadata(Node node, PhylogenyNode phyNode){
//TODO: This needs to be checked. Works well now so not to worry.
URI namespaceURI = URI.create("http://purl.org/PHYLO/TREEBASE/PHYLOWS/study/TB2:");
OTU otu= node.getOTU();
NodeData nodeData = phyNode.getNodeData();
PropertiesMap pMap = new PropertiesMap();
nodeData.setProperties(pMap);
if(otu!=null){
Set<Annotation> set = null;
String pName = null; //Property name
pName = "tb:identifier.taxon";
set = otu.getAnnotations(pName);
for(Annotation a:set){
String value = a.getValue().toString();
String unit = null; //This is not applicable.
String datatype = a.getXsdType().toString();
String id_ref = a.getId().toString();
Property p = new Property( pName, value, unit, datatype, AppliesTo.NODE, id_ref);
pMap.addProperty(p);
//TODO: Currently the properties map can only contain unique properties.
break;
}
pName = "tb:identifier.taxonVariant";
set = otu.getAnnotations(pName);
for(Annotation a:set){
String value = a.getValue().toString();
String unit = null; //This is not applicable.
String datatype = a.getXsdType().toString();
String id_ref = a.getId().toString();
Property p = new Property( pName, value, unit, datatype, AppliesTo.NODE, id_ref);
pMap.addProperty(p);
//TODO: Currently the properties map can only contain unique properties.
break;
}
pName = "skos:prefLabel";
set = otu.getAnnotations(pName);
for(Annotation a:set){
String value = a.getValue().toString();
String unit = null; //This is not applicable.
String datatype = a.getXsdType().toString();
String id_ref = a.getId();
Property p = new Property( pName, value, unit, datatype, AppliesTo.NODE, id_ref);
pMap.addProperty(p);
//TODO: Currently the properties map can only contain unique properties.
break;
}
pName = "skos:closeMatch";
set = otu.getAnnotations(pName);
for(Annotation a:set){
String value = a.getValue().toString();
String unit = null; //This is not applicable.
String datatype = a.getXsdType().toString();
String id_ref = a.getId();
Property p = new Property( pName, value, unit, datatype, AppliesTo.NODE, id_ref);
pMap.addProperty(p);
//TODO: Currently the properties map can only contain unique properties.
break;
}
pName = "rdfs:isDefinedBy";
set = otu.getAnnotations(pName);
for(Annotation a:set){
String value = a.getValue().toString();
String unit = null; //This is not applicable.
String datatype = a.getXsdType().toString();
String id_ref = a.getId();
Property p = new Property( pName, value, unit, datatype, AppliesTo.NODE, id_ref);
pMap.addProperty(p);
//TODO: Currently the properties map can only contain unique properties.
break;
}
}
}
/**
* Sets the distance of PhylogenyNode node from its parent.
* @param edge
* @param node
*/
public static void setDistanceToParent(Edge edge, PhylogenyNode node){
Number length = edge.getLength();
if(length!=null){
node.setDistanceToParent(length.doubleValue());
}
else{
node.setDistanceToParent(0.0d);
}
}
/**
* Returns the number part of a meta id<br>
* For example if a node has id = "node234"<br>
* It returns 234
*
* @param metaId
* @return the number id of this meta id.
*/
private static int getNumberFromMetaId(String metaId){
int n;
Pattern numberRegex = Pattern.compile("(\\D*)(\\d*)");
Matcher numberMatcher = numberRegex.matcher(metaId);
String number = "-1";
if(numberMatcher.find()){
number = numberMatcher.group(2);
}
n = Integer.parseInt(number);
return n;
}
}