1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Services; 21 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\Individual; 24use Illuminate\Support\Collection; 25 26/** 27 * Find ancestors, descendants, cousins, etc for drawing charts. 28 */ 29class ChartService 30{ 31 /** 32 * Find the ancestors of an individual, indexed by their Sosa-Stradonitz number. 33 * 34 * @param Individual $individual Start with this individual 35 * @param int $generations Fetch this number of generations 36 * 37 * @return Collection<int,Individual> 38 */ 39 public function sosaStradonitzAncestors(Individual $individual, int $generations): Collection 40 { 41 $ancestors = [1 => $individual]; 42 43 $queue = [1]; 44 45 $max = 2 ** ($generations - 1); 46 47 while ($queue !== []) { 48 $sosa_stradonitz_number = array_shift($queue); 49 50 if ($sosa_stradonitz_number >= $max) { 51 break; 52 } 53 54 $family = $ancestors[$sosa_stradonitz_number]->childFamilies()->first(); 55 56 if ($family instanceof Family) { 57 if ($family->husband() instanceof Individual) { 58 $ancestors[$sosa_stradonitz_number * 2] = $family->husband(); 59 $queue[] = $sosa_stradonitz_number * 2; 60 } 61 62 if ($family->wife() instanceof Individual) { 63 $ancestors[$sosa_stradonitz_number * 2 + 1] = $family->wife(); 64 $queue[] = $sosa_stradonitz_number * 2 + 1; 65 } 66 } 67 } 68 69 return new Collection($ancestors); 70 } 71 72 /** 73 * Find the descendants of an individual. 74 * 75 * @param Individual $individual Start with this individual 76 * @param int $generations Fetch this number of generations 77 * 78 * @return Collection<int,Individual> 79 */ 80 public function descendants(Individual $individual, int $generations): Collection 81 { 82 $descendants = new Collection([$individual->xref() => $individual]); 83 84 if ($generations > 0) { 85 foreach ($individual->spouseFamilies() as $family) { 86 foreach ($family->children() as $child) { 87 if (!$descendants->has($child->xref())) { 88 $descendants = $descendants->merge($this->descendants($child, $generations - 1)); 89 } 90 } 91 } 92 } 93 94 return $descendants->values(); 95 } 96 97 /** 98 * Find the descendants of an individual. 99 * 100 * @param Individual $individual Start with this individual 101 * @param int $generations Fetch this number of generations 102 * 103 * @return Collection<int,Family> 104 */ 105 public function descendantFamilies(Individual $individual, int $generations): Collection 106 { 107 $descendants = $individual->spouseFamilies(); 108 109 if ($generations > 0) { 110 foreach ($descendants as $family) { 111 foreach ($family->children() as $child) { 112 $descendants = $descendants->merge($this->descendantFamilies($child, $generations - 1)); 113 } 114 } 115 } 116 117 return $descendants; 118 } 119} 120