18add1155SRico Sonntag<?php 23976b470SGreg Roach 38add1155SRico Sonntag/** 48add1155SRico Sonntag * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team 68add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify 78add1155SRico Sonntag * it under the terms of the GNU General Public License as published by 88add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or 98add1155SRico Sonntag * (at your option) any later version. 108add1155SRico Sonntag * This program is distributed in the hope that it will be useful, 118add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of 128add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138add1155SRico Sonntag * GNU General Public License for more details. 148add1155SRico Sonntag * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 168add1155SRico Sonntag */ 17fcfa147eSGreg Roach 188add1155SRico Sonntagdeclare(strict_types=1); 198add1155SRico Sonntag 208add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Google; 218add1155SRico Sonntag 228add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 2393ccd686SRico Sonntaguse Fisharebest\Webtrees\Module\ModuleThemeInterface; 248add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; 2588de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; 2693ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService; 278add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 2888de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB; 29a69f5655SGreg Roachuse Illuminate\Database\Query\Expression; 3088de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause; 316ccdf4f0SGreg Roachuse stdClass; 328add1155SRico Sonntag 3390a2f718SGreg Roachuse function app; 3471378461SGreg Roachuse function array_key_exists; 3571378461SGreg Roach 368add1155SRico Sonntag/** 3793ccd686SRico Sonntag * A chart showing the distribution of different events on a map. 388add1155SRico Sonntag */ 3993ccd686SRico Sonntagclass ChartDistribution 408add1155SRico Sonntag{ 418add1155SRico Sonntag /** 4293ccd686SRico Sonntag * @var Tree 438add1155SRico Sonntag */ 4493ccd686SRico Sonntag private $tree; 4593ccd686SRico Sonntag 4693ccd686SRico Sonntag /** 4793ccd686SRico Sonntag * @var ModuleThemeInterface 4893ccd686SRico Sonntag */ 4993ccd686SRico Sonntag private $theme; 5093ccd686SRico Sonntag 5193ccd686SRico Sonntag /** 5293ccd686SRico Sonntag * @var CountryService 5393ccd686SRico Sonntag */ 5493ccd686SRico Sonntag private $country_service; 558add1155SRico Sonntag 568add1155SRico Sonntag /** 578add1155SRico Sonntag * @var IndividualRepository 588add1155SRico Sonntag */ 598add1155SRico Sonntag private $individualRepository; 608add1155SRico Sonntag 618add1155SRico Sonntag /** 628add1155SRico Sonntag * @var PlaceRepository 638add1155SRico Sonntag */ 648add1155SRico Sonntag private $placeRepository; 658add1155SRico Sonntag 668add1155SRico Sonntag /** 6788de55fdSRico Sonntag * @var string[] 6888de55fdSRico Sonntag */ 6988de55fdSRico Sonntag private $country_to_iso3166; 7088de55fdSRico Sonntag 7188de55fdSRico Sonntag /** 728add1155SRico Sonntag * Constructor. 738add1155SRico Sonntag * 748add1155SRico Sonntag * @param Tree $tree 758add1155SRico Sonntag */ 768add1155SRico Sonntag public function __construct(Tree $tree) 778add1155SRico Sonntag { 7893ccd686SRico Sonntag $this->tree = $tree; 79cab242e7SGreg Roach $this->theme = app(ModuleThemeInterface::class); 8093ccd686SRico Sonntag $this->country_service = new CountryService(); 818add1155SRico Sonntag $this->individualRepository = new IndividualRepository($tree); 828add1155SRico Sonntag $this->placeRepository = new PlaceRepository($tree); 8388de55fdSRico Sonntag 8488de55fdSRico Sonntag // Get the country names for each language 8588de55fdSRico Sonntag $this->country_to_iso3166 = $this->getIso3166Countries(); 8688de55fdSRico Sonntag } 8788de55fdSRico Sonntag 8888de55fdSRico Sonntag /** 8988de55fdSRico Sonntag * Returns the country names for each language. 9088de55fdSRico Sonntag * 9124f2a3afSGreg Roach * @return array<string> 9288de55fdSRico Sonntag */ 9388de55fdSRico Sonntag private function getIso3166Countries(): array 9488de55fdSRico Sonntag { 9588de55fdSRico Sonntag // Get the country names for each language 9693ccd686SRico Sonntag $country_to_iso3166 = []; 9788de55fdSRico Sonntag 9856eca4e8SGreg Roach $current_language = I18N::languageTag(); 9956eca4e8SGreg Roach 10088de55fdSRico Sonntag foreach (I18N::activeLocales() as $locale) { 10188de55fdSRico Sonntag I18N::init($locale->languageTag()); 10288de55fdSRico Sonntag 10356eca4e8SGreg Roach $countries = $this->country_service->getAllCountries(); 10456eca4e8SGreg Roach 10593ccd686SRico Sonntag foreach ($this->country_service->iso3166() as $three => $two) { 10693ccd686SRico Sonntag $country_to_iso3166[$three] = $two; 10793ccd686SRico Sonntag $country_to_iso3166[$countries[$three]] = $two; 10888de55fdSRico Sonntag } 10988de55fdSRico Sonntag } 11088de55fdSRico Sonntag 11156eca4e8SGreg Roach I18N::init($current_language); 11256eca4e8SGreg Roach 11393ccd686SRico Sonntag return $country_to_iso3166; 11488de55fdSRico Sonntag } 11588de55fdSRico Sonntag 11688de55fdSRico Sonntag /** 11788de55fdSRico Sonntag * Returns the data structure required by google geochart. 11888de55fdSRico Sonntag * 119*ac701fbdSGreg Roach * @param array<int|string,int> $places 12088de55fdSRico Sonntag * 12188de55fdSRico Sonntag * @return array 12288de55fdSRico Sonntag */ 12388de55fdSRico Sonntag private function createChartData(array $places): array 12488de55fdSRico Sonntag { 12588de55fdSRico Sonntag $data = [ 12688de55fdSRico Sonntag [ 12788de55fdSRico Sonntag I18N::translate('Country'), 12888de55fdSRico Sonntag I18N::translate('Total'), 12988de55fdSRico Sonntag ], 13088de55fdSRico Sonntag ]; 13188de55fdSRico Sonntag 132*ac701fbdSGreg Roach // webtrees uses 3-letter country codes and localised country names, but google uses 2 letter codes. 13388de55fdSRico Sonntag foreach ($places as $country => $count) { 13488de55fdSRico Sonntag $data[] = [ 13588de55fdSRico Sonntag [ 13688de55fdSRico Sonntag 'v' => $country, 13793ccd686SRico Sonntag 'f' => $this->country_service->mapTwoLetterToName($country), 13888de55fdSRico Sonntag ], 13988de55fdSRico Sonntag $count 14088de55fdSRico Sonntag ]; 14188de55fdSRico Sonntag } 14288de55fdSRico Sonntag 14388de55fdSRico Sonntag return $data; 14488de55fdSRico Sonntag } 14588de55fdSRico Sonntag 14688de55fdSRico Sonntag /** 14788de55fdSRico Sonntag * Returns the google geochart data for birth fact. 14888de55fdSRico Sonntag * 14988de55fdSRico Sonntag * @return array 15088de55fdSRico Sonntag */ 15188de55fdSRico Sonntag private function getBirthChartData(): array 15288de55fdSRico Sonntag { 15388de55fdSRico Sonntag // Count how many people were born in each country 15488de55fdSRico Sonntag $surn_countries = []; 15588de55fdSRico Sonntag $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); 15688de55fdSRico Sonntag 15788de55fdSRico Sonntag foreach ($b_countries as $country => $count) { 15888de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 1596ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 16088de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 16188de55fdSRico Sonntag 1626ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 16388de55fdSRico Sonntag $surn_countries[$country_code] += $count; 16488de55fdSRico Sonntag } else { 16588de55fdSRico Sonntag $surn_countries[$country_code] = $count; 16688de55fdSRico Sonntag } 16788de55fdSRico Sonntag } 16888de55fdSRico Sonntag } 16988de55fdSRico Sonntag 17088de55fdSRico Sonntag return $this->createChartData($surn_countries); 17188de55fdSRico Sonntag } 17288de55fdSRico Sonntag 17388de55fdSRico Sonntag /** 17488de55fdSRico Sonntag * Returns the google geochart data for death fact. 17588de55fdSRico Sonntag * 17688de55fdSRico Sonntag * @return array 17788de55fdSRico Sonntag */ 17888de55fdSRico Sonntag private function getDeathChartData(): array 17988de55fdSRico Sonntag { 18088de55fdSRico Sonntag // Count how many people were death in each country 18188de55fdSRico Sonntag $surn_countries = []; 18288de55fdSRico Sonntag $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); 18388de55fdSRico Sonntag 18488de55fdSRico Sonntag foreach ($d_countries as $country => $count) { 18588de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 1866ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 18788de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 18888de55fdSRico Sonntag 1896ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 19088de55fdSRico Sonntag $surn_countries[$country_code] += $count; 19188de55fdSRico Sonntag } else { 19288de55fdSRico Sonntag $surn_countries[$country_code] = $count; 19388de55fdSRico Sonntag } 19488de55fdSRico Sonntag } 19588de55fdSRico Sonntag } 19688de55fdSRico Sonntag 19788de55fdSRico Sonntag return $this->createChartData($surn_countries); 19888de55fdSRico Sonntag } 19988de55fdSRico Sonntag 20088de55fdSRico Sonntag /** 20188de55fdSRico Sonntag * Returns the google geochart data for marriages. 20288de55fdSRico Sonntag * 20388de55fdSRico Sonntag * @return array 20488de55fdSRico Sonntag */ 20588de55fdSRico Sonntag private function getMarriageChartData(): array 20688de55fdSRico Sonntag { 20788de55fdSRico Sonntag // Count how many families got marriage in each country 20888de55fdSRico Sonntag $surn_countries = []; 20988de55fdSRico Sonntag $m_countries = $this->placeRepository->statsPlaces('FAM'); 21088de55fdSRico Sonntag 21188de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 21288de55fdSRico Sonntag foreach ($m_countries as $place) { 21388de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 2146ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 21588de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 21688de55fdSRico Sonntag 2176ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 21888de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 21988de55fdSRico Sonntag } else { 22088de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 22188de55fdSRico Sonntag } 22288de55fdSRico Sonntag } 22388de55fdSRico Sonntag } 22488de55fdSRico Sonntag 22588de55fdSRico Sonntag return $this->createChartData($surn_countries); 22688de55fdSRico Sonntag } 22788de55fdSRico Sonntag 22888de55fdSRico Sonntag /** 22988de55fdSRico Sonntag * Returns the related database records. 23088de55fdSRico Sonntag * 23188de55fdSRico Sonntag * @param string $surname 23288de55fdSRico Sonntag * 233870ec5a3SGreg Roach * @return array<stdClass> 23488de55fdSRico Sonntag */ 23588de55fdSRico Sonntag private function queryRecords(string $surname): array 23688de55fdSRico Sonntag { 23788de55fdSRico Sonntag $query = DB::table('individuals') 23888de55fdSRico Sonntag ->select(['i_gedcom']) 2390b5fd0a6SGreg Roach ->join('name', static function (JoinClause $join): void { 24088de55fdSRico Sonntag $join->on('n_id', '=', 'i_id') 24188de55fdSRico Sonntag ->on('n_file', '=', 'i_file'); 24288de55fdSRico Sonntag }) 24388de55fdSRico Sonntag ->where('n_file', '=', $this->tree->id()) 244a69f5655SGreg Roach ->where(new Expression('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); 24588de55fdSRico Sonntag 24688de55fdSRico Sonntag return $query->get()->all(); 24788de55fdSRico Sonntag } 24888de55fdSRico Sonntag 24988de55fdSRico Sonntag /** 25088de55fdSRico Sonntag * Returns the google geochart data for surnames. 25188de55fdSRico Sonntag * 25288de55fdSRico Sonntag * @param string $surname The surname used to create the chart 25388de55fdSRico Sonntag * 25488de55fdSRico Sonntag * @return array 25588de55fdSRico Sonntag */ 25688de55fdSRico Sonntag private function getSurnameChartData(string $surname): array 25788de55fdSRico Sonntag { 25888de55fdSRico Sonntag if ($surname === '') { 25988de55fdSRico Sonntag $surname = $this->individualRepository->getCommonSurname(); 26088de55fdSRico Sonntag } 26188de55fdSRico Sonntag 26288de55fdSRico Sonntag // Count how many people are events in each country 26388de55fdSRico Sonntag $surn_countries = []; 26488de55fdSRico Sonntag $records = $this->queryRecords($surname); 26588de55fdSRico Sonntag 26688de55fdSRico Sonntag foreach ($records as $row) { 26788de55fdSRico Sonntag /** @var string[][] $matches */ 26888de55fdSRico Sonntag if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { 26988de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, 27088de55fdSRico Sonntag // but google uses 2 letter codes. 27188de55fdSRico Sonntag foreach ($matches[1] as $country) { 27288de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 2736ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 27488de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 27588de55fdSRico Sonntag 2766ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 27788de55fdSRico Sonntag $surn_countries[$country_code]++; 27888de55fdSRico Sonntag } else { 27988de55fdSRico Sonntag $surn_countries[$country_code] = 1; 28088de55fdSRico Sonntag } 28188de55fdSRico Sonntag } 28288de55fdSRico Sonntag } 28388de55fdSRico Sonntag } 28488de55fdSRico Sonntag } 28588de55fdSRico Sonntag 28688de55fdSRico Sonntag return $this->createChartData($surn_countries); 28788de55fdSRico Sonntag } 28888de55fdSRico Sonntag 28988de55fdSRico Sonntag /** 29088de55fdSRico Sonntag * Returns the google geochart data for individuals. 29188de55fdSRico Sonntag * 29288de55fdSRico Sonntag * @return array 29388de55fdSRico Sonntag */ 29488de55fdSRico Sonntag private function getIndivdualChartData(): array 29588de55fdSRico Sonntag { 29688de55fdSRico Sonntag // Count how many people have events in each country 29788de55fdSRico Sonntag $surn_countries = []; 29888de55fdSRico Sonntag $a_countries = $this->placeRepository->statsPlaces('INDI'); 29988de55fdSRico Sonntag 30088de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 30188de55fdSRico Sonntag foreach ($a_countries as $place) { 30288de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 3036ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 30488de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 30588de55fdSRico Sonntag 3066ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 30788de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 30888de55fdSRico Sonntag } else { 30988de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 31088de55fdSRico Sonntag } 31188de55fdSRico Sonntag } 31288de55fdSRico Sonntag } 31388de55fdSRico Sonntag 31488de55fdSRico Sonntag return $this->createChartData($surn_countries); 3158add1155SRico Sonntag } 3168add1155SRico Sonntag 3178add1155SRico Sonntag /** 3188add1155SRico Sonntag * Create a chart showing where events occurred. 3198add1155SRico Sonntag * 32088de55fdSRico Sonntag * @param string $chart_shows The type of chart map to show 32188de55fdSRico Sonntag * @param string $chart_type The type of chart to show 32288de55fdSRico Sonntag * @param string $surname The surname for surname based distribution chart 3238add1155SRico Sonntag * 3248add1155SRico Sonntag * @return string 3258add1155SRico Sonntag */ 3268add1155SRico Sonntag public function chartDistribution( 3278add1155SRico Sonntag string $chart_shows = 'world', 3288add1155SRico Sonntag string $chart_type = '', 3298add1155SRico Sonntag string $surname = '' 3308add1155SRico Sonntag ): string { 3318add1155SRico Sonntag switch ($chart_type) { 3328add1155SRico Sonntag case 'surname_distribution_chart': 3338add1155SRico Sonntag $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; 33488de55fdSRico Sonntag $data = $this->getSurnameChartData($surname); 3358add1155SRico Sonntag break; 3368add1155SRico Sonntag 3378add1155SRico Sonntag case 'birth_distribution_chart': 3388add1155SRico Sonntag $chart_title = I18N::translate('Birth by country'); 33988de55fdSRico Sonntag $data = $this->getBirthChartData(); 3408add1155SRico Sonntag break; 3418add1155SRico Sonntag 3428add1155SRico Sonntag case 'death_distribution_chart': 3438add1155SRico Sonntag $chart_title = I18N::translate('Death by country'); 34488de55fdSRico Sonntag $data = $this->getDeathChartData(); 3458add1155SRico Sonntag break; 3468add1155SRico Sonntag 3478add1155SRico Sonntag case 'marriage_distribution_chart': 3488add1155SRico Sonntag $chart_title = I18N::translate('Marriage by country'); 34988de55fdSRico Sonntag $data = $this->getMarriageChartData(); 3508add1155SRico Sonntag break; 3518add1155SRico Sonntag 3528add1155SRico Sonntag case 'indi_distribution_chart': 3538add1155SRico Sonntag default: 3548add1155SRico Sonntag $chart_title = I18N::translate('Individual distribution chart'); 35588de55fdSRico Sonntag $data = $this->getIndivdualChartData(); 3568add1155SRico Sonntag break; 3578add1155SRico Sonntag } 3588add1155SRico Sonntag 35988de55fdSRico Sonntag $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); 36088de55fdSRico Sonntag $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); 3618add1155SRico Sonntag 36290a2f718SGreg Roach return view('statistics/other/charts/geo', [ 3638add1155SRico Sonntag 'chart_title' => $chart_title, 3648add1155SRico Sonntag 'chart_color2' => $chart_color2, 3658add1155SRico Sonntag 'chart_color3' => $chart_color3, 36688de55fdSRico Sonntag 'region' => $chart_shows, 36788de55fdSRico Sonntag 'data' => $data, 36865cf5706SGreg Roach 'language' => I18N::languageTag(), 36990a2f718SGreg Roach ]); 3708add1155SRico Sonntag } 3718add1155SRico Sonntag} 372