@@ -814,7 +814,195 @@ def otts2identifiers():
814814 headers = colname_map ,
815815 ids = ret )
816816
817+ def ascend_ancestors ():
818+ """
819+ Used to return details of all the ancestors of a particular node, in particular
820+ - number of million years of each ancestor
821+ - the total number of species that are descendants on each side of the bifurcation
822+ - the name of the period/era/eon
823+ - a name (if it exists) for the ancestor
824+
825+ NB: finding the name for the other descendant group is a lot more tricky!
826+
827+ """
828+ session .forget (response )
829+ lang = request .vars .lang or request .env .http_accept_language or 'en'
830+ response .headers ["Access-Control-Allow-Origin" ] = '*'
831+
832+ if "." not in request .env .path_info .split ('/' )[2 ]:
833+ request .extension = "json"
834+ response .view = request .controller + "/" + request .function + "." + request .extension
835+
836+ if request .vars .key is None :
837+ redirect (URL ('error' , vars = dict (
838+ code = 400 ,
839+ text = "Please use an API key (use 0 for the public API key)"
840+ )))
817841
842+ try :
843+ ott = int (request .vars .ott )
844+ except :
845+ redirect (URL ('error' , vars = dict (code = 400 , text = "Invalid OTT" )))
846+
847+ include_otts = request .vars .include_otts
848+ include_fake_nodes = request .vars .include_fake_nodes
849+ rows = db (db .ordered_leaves .ott == ott ).select (
850+ db .ordered_leaves .id ,
851+ db .ordered_leaves .parent ,
852+ db .ordered_leaves .real_parent ,
853+ db .ordered_leaves .name ,
854+ db .ordered_leaves .extinction_date ,
855+ )
856+ if rows :
857+ row = rows .first ()
858+ date = row .extinction_date or 0
859+ prev_n_leaves = n_leaves = 1
860+ prev_leaf_lft = row .id
861+ else :
862+ rows = db (db .ordered_nodes .ott == ott ).select (
863+ db .ordered_nodes .parent ,
864+ db .ordered_nodes .real_parent ,
865+ db .ordered_nodes .name ,
866+ db .ordered_nodes .age ,
867+ db .ordered_nodes .leaf_lft ,
868+ db .ordered_nodes .leaf_rgt ,
869+ )
870+ if not rows :
871+ redirect (URL ('error' , vars = dict (code = 400 , text = "No matching OTT found" )))
872+ row = rows .first ()
873+ date = None if row .age is None else round (row .age , 1 )
874+ prev_n_leaves = n_leaves = row .leaf_rgt - row .leaf_lft + 1
875+ prev_leaf_lft = row .leaf_lft
876+
877+ step = 0
878+ ancestors = []
879+ data_by_sib_node_id = {}
880+ data_by_sib_leaf_id = {}
881+ ret = dict (
882+ step = step ,
883+ date_MYA = date ,
884+ n_spp = n_leaves ,
885+ name = row .name ,
886+ )
887+ if include_otts :
888+ ret ["input_ott" ] = ott
889+
890+ data_by_ott = {ott : ret }
891+ data_by_name = {}
892+ prev_was_fake_node = row .real_parent < 0
893+
894+ while row .parent >= 0 :
895+ rows = db (db .ordered_nodes .id == row .parent ).select (
896+ db .ordered_nodes .id ,
897+ db .ordered_nodes .parent ,
898+ db .ordered_nodes .real_parent ,
899+ db .ordered_nodes .name ,
900+ db .ordered_nodes .ott ,
901+ db .ordered_nodes .age ,
902+ db .ordered_nodes .leaf_lft ,
903+ db .ordered_nodes .leaf_rgt ,
904+ )
905+ if not rows :
906+ break
907+ row = rows .first ()
908+ n_leaves = row .leaf_rgt - row .leaf_lft + 1
909+ if row .real_parent < 0 :
910+ # This is a fake node, inserted to break polytomies
911+ prev_was_fake_node = True
912+ prev_leaf_lft = row .leaf_lft
913+ if not include_fake_nodes :
914+ continue
915+
916+ step += 1
917+ row_data = dict (
918+ step = step ,
919+ date_MYA = None if row .age is None else round (row .age , 1 ),
920+ n_spp = n_leaves ,
921+ tracked_branch_n_spp = prev_n_leaves ,
922+ )
923+ sib_n_leaves = n_leaves - prev_n_leaves # may include multiple sibs in a polytomy
924+ row_data ["name" ] = row .name if row .name and not row .name .endswith ("_" ) else None
925+ row_data ["vernacular" ] = None
926+ if include_otts :
927+ row_data ["ott" ] = row .ott
928+ row_data ["sib_branches" ]= {"n_spp" : sib_n_leaves , "single_sib" : True }
929+
930+ # If either the previous node on the tracked branch or on the sib branch
931+ # is a "fake node" it means that the current node is a polytomy
932+ if prev_was_fake_node and not include_fake_nodes :
933+ row_data ["sib_branches" ]["single_sib" ] = False
934+ else :
935+ # Could be a valid node
936+ if prev_leaf_lft == row .leaf_lft :
937+ # the tracked branch is the left-hand branch from this node
938+ if sib_n_leaves > 1 :
939+ data_by_sib_node_id [row .id + prev_n_leaves ] = row_data
940+ else :
941+ data_by_sib_leaf_id [row .leaf_rgt ] = row_data
942+ else :
943+ # the tracked branch is the right-hand branch from this node
944+ if sib_n_leaves > 1 :
945+ data_by_sib_node_id [row .id + 1 ] = row_data
946+ else :
947+ data_by_sib_leaf_id [row .leaf_lft ] = row_data
948+
949+ prev_n_leaves = n_leaves
950+ ancestors .append (row_data )
951+ if row .ott is not None :
952+ data_by_ott [row .ott ] = row_data
953+ elif row .name is not None :
954+ data_by_name [row .name ] = row_data
955+ prev_was_fake_node = False
956+ prev_leaf_lft = row .leaf_lft
957+
958+ # Get data on the siblings
959+ for sib_data_dict , db_table in zip (
960+ (data_by_sib_node_id , data_by_sib_leaf_id ),
961+ (db .ordered_nodes , db .ordered_leaves )
962+ ):
963+ if len (sib_data_dict ) > 0 :
964+ rows = db (db_table .id .belongs (sib_data_dict .keys ())).select (
965+ db_table .id , db_table .real_parent , db_table .ott , db_table .name )
966+ if rows :
967+ for row in rows :
968+ parent_data = sib_data_dict [row .id ]
969+ if row .real_parent >= 0 or include_fake_nodes :
970+ # The sib node should be included
971+ # if name ends in _ it's not scientific, but a OZ nickname
972+ if row .name and not row .name .endswith ("_" ):
973+ parent_data ["sib_branches" ]["name" ] = row .name
974+ if row .ott is not None :
975+ data_by_ott [row .ott ] = parent_data ["sib_branches" ]
976+ elif row .name is not None :
977+ data_by_name [row .name ] = parent_data ["sib_branches" ]
978+ else :
979+ parent_data ["sib_branches" ]["single_sib" ] = False
980+
981+ for ott , vernacular in OZfunc .get_common_names (
982+ data_by_ott .keys (),
983+ return_nulls = False ,
984+ prefer_short_name = False ,
985+ include_unpreferred = False ,
986+ return_all = False ,
987+ lang = lang ,
988+ ).items ():
989+ data_by_ott [ott ]["vernacular" ] = vernacular
990+
991+ for name , vernacular in OZfunc .get_common_names (
992+ data_by_name .keys (),
993+ OTT = False ,
994+ return_nulls = False ,
995+ prefer_short_name = False ,
996+ include_unpreferred = False ,
997+ return_all = False ,
998+ lang = lang ,
999+ ).items ():
1000+ data_by_name [name ]["vernacular" ] = vernacular
1001+ ret ["ancestors" ] = ancestors
1002+
1003+ return ret
1004+
1005+
8181006#PRIVATE FUNCTIONS
8191007
8201008
0 commit comments