18add1155SRico Sonntag<?php 23976b470SGreg Roach 38add1155SRico Sonntag/** 48add1155SRico Sonntag * webtrees: online genealogy 5242a7862SGreg Roach * Copyright (C) 2019 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 158add1155SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>. 168add1155SRico Sonntag */ 178add1155SRico Sonntagdeclare(strict_types=1); 188add1155SRico Sonntag 198add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Google; 208add1155SRico Sonntag 218add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 2293ccd686SRico Sonntaguse Fisharebest\Webtrees\Module\ModuleThemeInterface; 238add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; 2488de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; 2593ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService; 268add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 2788de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB; 28a69f5655SGreg Roachuse Illuminate\Database\Query\Expression; 2988de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause; 306ccdf4f0SGreg Roachuse stdClass; 318add1155SRico Sonntag 32*71378461SGreg Roachuse function array_key_exists; 33*71378461SGreg Roach 348add1155SRico Sonntag/** 3593ccd686SRico Sonntag * A chart showing the distribution of different events on a map. 368add1155SRico Sonntag */ 3793ccd686SRico Sonntagclass ChartDistribution 388add1155SRico Sonntag{ 398add1155SRico Sonntag /** 4093ccd686SRico Sonntag * @var Tree 418add1155SRico Sonntag */ 4293ccd686SRico Sonntag private $tree; 4393ccd686SRico Sonntag 4493ccd686SRico Sonntag /** 4593ccd686SRico Sonntag * @var ModuleThemeInterface 4693ccd686SRico Sonntag */ 4793ccd686SRico Sonntag private $theme; 4893ccd686SRico Sonntag 4993ccd686SRico Sonntag /** 5093ccd686SRico Sonntag * @var CountryService 5193ccd686SRico Sonntag */ 5293ccd686SRico Sonntag private $country_service; 538add1155SRico Sonntag 548add1155SRico Sonntag /** 558add1155SRico Sonntag * @var IndividualRepository 568add1155SRico Sonntag */ 578add1155SRico Sonntag private $individualRepository; 588add1155SRico Sonntag 598add1155SRico Sonntag /** 608add1155SRico Sonntag * @var PlaceRepository 618add1155SRico Sonntag */ 628add1155SRico Sonntag private $placeRepository; 638add1155SRico Sonntag 648add1155SRico Sonntag /** 6588de55fdSRico Sonntag * @var string[] 6688de55fdSRico Sonntag */ 6788de55fdSRico Sonntag private $country_to_iso3166; 6888de55fdSRico Sonntag 6988de55fdSRico Sonntag /** 708add1155SRico Sonntag * Constructor. 718add1155SRico Sonntag * 728add1155SRico Sonntag * @param Tree $tree 738add1155SRico Sonntag */ 748add1155SRico Sonntag public function __construct(Tree $tree) 758add1155SRico Sonntag { 7693ccd686SRico Sonntag $this->tree = $tree; 77cab242e7SGreg Roach $this->theme = app(ModuleThemeInterface::class); 7893ccd686SRico Sonntag $this->country_service = new CountryService(); 798add1155SRico Sonntag $this->individualRepository = new IndividualRepository($tree); 808add1155SRico Sonntag $this->placeRepository = new PlaceRepository($tree); 8188de55fdSRico Sonntag 8288de55fdSRico Sonntag // Get the country names for each language 8388de55fdSRico Sonntag $this->country_to_iso3166 = $this->getIso3166Countries(); 8488de55fdSRico Sonntag } 8588de55fdSRico Sonntag 8688de55fdSRico Sonntag /** 8788de55fdSRico Sonntag * Returns the country names for each language. 8888de55fdSRico Sonntag * 8988de55fdSRico Sonntag * @return string[] 9088de55fdSRico Sonntag */ 9188de55fdSRico Sonntag private function getIso3166Countries(): array 9288de55fdSRico Sonntag { 9393ccd686SRico Sonntag $countries = $this->country_service->getAllCountries(); 9488de55fdSRico Sonntag 9588de55fdSRico Sonntag // Get the country names for each language 9693ccd686SRico Sonntag $country_to_iso3166 = []; 9788de55fdSRico Sonntag 9888de55fdSRico Sonntag foreach (I18N::activeLocales() as $locale) { 9988de55fdSRico Sonntag I18N::init($locale->languageTag()); 10088de55fdSRico Sonntag 10193ccd686SRico Sonntag foreach ($this->country_service->iso3166() as $three => $two) { 10293ccd686SRico Sonntag $country_to_iso3166[$three] = $two; 10393ccd686SRico Sonntag $country_to_iso3166[$countries[$three]] = $two; 10488de55fdSRico Sonntag } 10588de55fdSRico Sonntag } 10688de55fdSRico Sonntag 10793ccd686SRico Sonntag return $country_to_iso3166; 10888de55fdSRico Sonntag } 10988de55fdSRico Sonntag 11088de55fdSRico Sonntag /** 11188de55fdSRico Sonntag * Returns the data structure required by google geochart. 11288de55fdSRico Sonntag * 11388de55fdSRico Sonntag * @param array $places 11488de55fdSRico Sonntag * 11588de55fdSRico Sonntag * @return array 11688de55fdSRico Sonntag */ 11788de55fdSRico Sonntag private function createChartData(array $places): array 11888de55fdSRico Sonntag { 11988de55fdSRico Sonntag $data = [ 12088de55fdSRico Sonntag [ 12188de55fdSRico Sonntag I18N::translate('Country'), 12288de55fdSRico Sonntag I18N::translate('Total'), 12388de55fdSRico Sonntag ], 12488de55fdSRico Sonntag ]; 12588de55fdSRico Sonntag 12688de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 12788de55fdSRico Sonntag foreach ($places as $country => $count) { 12888de55fdSRico Sonntag $data[] = [ 12988de55fdSRico Sonntag [ 13088de55fdSRico Sonntag 'v' => $country, 13193ccd686SRico Sonntag 'f' => $this->country_service->mapTwoLetterToName($country), 13288de55fdSRico Sonntag ], 13388de55fdSRico Sonntag $count 13488de55fdSRico Sonntag ]; 13588de55fdSRico Sonntag } 13688de55fdSRico Sonntag 13788de55fdSRico Sonntag return $data; 13888de55fdSRico Sonntag } 13988de55fdSRico Sonntag 14088de55fdSRico Sonntag /** 14188de55fdSRico Sonntag * Returns the google geochart data for birth fact. 14288de55fdSRico Sonntag * 14388de55fdSRico Sonntag * @return array 14488de55fdSRico Sonntag */ 14588de55fdSRico Sonntag private function getBirthChartData(): array 14688de55fdSRico Sonntag { 14788de55fdSRico Sonntag // Count how many people were born in each country 14888de55fdSRico Sonntag $surn_countries = []; 14988de55fdSRico Sonntag $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); 15088de55fdSRico Sonntag 15188de55fdSRico Sonntag foreach ($b_countries as $country => $count) { 15288de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 1536ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 15488de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 15588de55fdSRico Sonntag 1566ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 15788de55fdSRico Sonntag $surn_countries[$country_code] += $count; 15888de55fdSRico Sonntag } else { 15988de55fdSRico Sonntag $surn_countries[$country_code] = $count; 16088de55fdSRico Sonntag } 16188de55fdSRico Sonntag } 16288de55fdSRico Sonntag } 16388de55fdSRico Sonntag 16488de55fdSRico Sonntag return $this->createChartData($surn_countries); 16588de55fdSRico Sonntag } 16688de55fdSRico Sonntag 16788de55fdSRico Sonntag /** 16888de55fdSRico Sonntag * Returns the google geochart data for death fact. 16988de55fdSRico Sonntag * 17088de55fdSRico Sonntag * @return array 17188de55fdSRico Sonntag */ 17288de55fdSRico Sonntag private function getDeathChartData(): array 17388de55fdSRico Sonntag { 17488de55fdSRico Sonntag // Count how many people were death in each country 17588de55fdSRico Sonntag $surn_countries = []; 17688de55fdSRico Sonntag $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); 17788de55fdSRico Sonntag 17888de55fdSRico Sonntag foreach ($d_countries as $country => $count) { 17988de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 1806ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 18188de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 18288de55fdSRico Sonntag 1836ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 18488de55fdSRico Sonntag $surn_countries[$country_code] += $count; 18588de55fdSRico Sonntag } else { 18688de55fdSRico Sonntag $surn_countries[$country_code] = $count; 18788de55fdSRico Sonntag } 18888de55fdSRico Sonntag } 18988de55fdSRico Sonntag } 19088de55fdSRico Sonntag 19188de55fdSRico Sonntag return $this->createChartData($surn_countries); 19288de55fdSRico Sonntag } 19388de55fdSRico Sonntag 19488de55fdSRico Sonntag /** 19588de55fdSRico Sonntag * Returns the google geochart data for marriages. 19688de55fdSRico Sonntag * 19788de55fdSRico Sonntag * @return array 19888de55fdSRico Sonntag */ 19988de55fdSRico Sonntag private function getMarriageChartData(): array 20088de55fdSRico Sonntag { 20188de55fdSRico Sonntag // Count how many families got marriage in each country 20288de55fdSRico Sonntag $surn_countries = []; 20388de55fdSRico Sonntag $m_countries = $this->placeRepository->statsPlaces('FAM'); 20488de55fdSRico Sonntag 20588de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 20688de55fdSRico Sonntag foreach ($m_countries as $place) { 20788de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 2086ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 20988de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 21088de55fdSRico Sonntag 2116ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 21288de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 21388de55fdSRico Sonntag } else { 21488de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 21588de55fdSRico Sonntag } 21688de55fdSRico Sonntag } 21788de55fdSRico Sonntag } 21888de55fdSRico Sonntag 21988de55fdSRico Sonntag return $this->createChartData($surn_countries); 22088de55fdSRico Sonntag } 22188de55fdSRico Sonntag 22288de55fdSRico Sonntag /** 22388de55fdSRico Sonntag * Returns the related database records. 22488de55fdSRico Sonntag * 22588de55fdSRico Sonntag * @param string $surname 22688de55fdSRico Sonntag * 2276ccdf4f0SGreg Roach * @return stdClass[] 22888de55fdSRico Sonntag */ 22988de55fdSRico Sonntag private function queryRecords(string $surname): array 23088de55fdSRico Sonntag { 23188de55fdSRico Sonntag $query = DB::table('individuals') 23288de55fdSRico Sonntag ->select(['i_gedcom']) 2330b5fd0a6SGreg Roach ->join('name', static function (JoinClause $join): void { 23488de55fdSRico Sonntag $join->on('n_id', '=', 'i_id') 23588de55fdSRico Sonntag ->on('n_file', '=', 'i_file'); 23688de55fdSRico Sonntag }) 23788de55fdSRico Sonntag ->where('n_file', '=', $this->tree->id()) 238a69f5655SGreg Roach ->where(new Expression('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); 23988de55fdSRico Sonntag 24088de55fdSRico Sonntag return $query->get()->all(); 24188de55fdSRico Sonntag } 24288de55fdSRico Sonntag 24388de55fdSRico Sonntag /** 24488de55fdSRico Sonntag * Returns the google geochart data for surnames. 24588de55fdSRico Sonntag * 24688de55fdSRico Sonntag * @param string $surname The surname used to create the chart 24788de55fdSRico Sonntag * 24888de55fdSRico Sonntag * @return array 24988de55fdSRico Sonntag */ 25088de55fdSRico Sonntag private function getSurnameChartData(string $surname): array 25188de55fdSRico Sonntag { 25288de55fdSRico Sonntag if ($surname === '') { 25388de55fdSRico Sonntag $surname = $this->individualRepository->getCommonSurname(); 25488de55fdSRico Sonntag } 25588de55fdSRico Sonntag 25688de55fdSRico Sonntag // Count how many people are events in each country 25788de55fdSRico Sonntag $surn_countries = []; 25888de55fdSRico Sonntag $records = $this->queryRecords($surname); 25988de55fdSRico Sonntag 26088de55fdSRico Sonntag foreach ($records as $row) { 26188de55fdSRico Sonntag /** @var string[][] $matches */ 26288de55fdSRico Sonntag if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { 26388de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, 26488de55fdSRico Sonntag // but google uses 2 letter codes. 26588de55fdSRico Sonntag foreach ($matches[1] as $country) { 26688de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 2676ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 26888de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 26988de55fdSRico Sonntag 2706ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 27188de55fdSRico Sonntag $surn_countries[$country_code]++; 27288de55fdSRico Sonntag } else { 27388de55fdSRico Sonntag $surn_countries[$country_code] = 1; 27488de55fdSRico Sonntag } 27588de55fdSRico Sonntag } 27688de55fdSRico Sonntag } 27788de55fdSRico Sonntag } 27888de55fdSRico Sonntag } 27988de55fdSRico Sonntag 28088de55fdSRico Sonntag return $this->createChartData($surn_countries); 28188de55fdSRico Sonntag } 28288de55fdSRico Sonntag 28388de55fdSRico Sonntag /** 28488de55fdSRico Sonntag * Returns the google geochart data for individuals. 28588de55fdSRico Sonntag * 28688de55fdSRico Sonntag * @return array 28788de55fdSRico Sonntag */ 28888de55fdSRico Sonntag private function getIndivdualChartData(): array 28988de55fdSRico Sonntag { 29088de55fdSRico Sonntag // Count how many people have events in each country 29188de55fdSRico Sonntag $surn_countries = []; 29288de55fdSRico Sonntag $a_countries = $this->placeRepository->statsPlaces('INDI'); 29388de55fdSRico Sonntag 29488de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 29588de55fdSRico Sonntag foreach ($a_countries as $place) { 29688de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 2976ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 29888de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 29988de55fdSRico Sonntag 3006ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 30188de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 30288de55fdSRico Sonntag } else { 30388de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 30488de55fdSRico Sonntag } 30588de55fdSRico Sonntag } 30688de55fdSRico Sonntag } 30788de55fdSRico Sonntag 30888de55fdSRico Sonntag return $this->createChartData($surn_countries); 3098add1155SRico Sonntag } 3108add1155SRico Sonntag 3118add1155SRico Sonntag /** 3128add1155SRico Sonntag * Create a chart showing where events occurred. 3138add1155SRico Sonntag * 31488de55fdSRico Sonntag * @param string $chart_shows The type of chart map to show 31588de55fdSRico Sonntag * @param string $chart_type The type of chart to show 31688de55fdSRico Sonntag * @param string $surname The surname for surname based distribution chart 3178add1155SRico Sonntag * 3188add1155SRico Sonntag * @return string 3198add1155SRico Sonntag */ 3208add1155SRico Sonntag public function chartDistribution( 3218add1155SRico Sonntag string $chart_shows = 'world', 3228add1155SRico Sonntag string $chart_type = '', 3238add1155SRico Sonntag string $surname = '' 3248add1155SRico Sonntag ): string { 3258add1155SRico Sonntag I18N::init(WT_LOCALE); 3268add1155SRico Sonntag 3278add1155SRico Sonntag switch ($chart_type) { 3288add1155SRico Sonntag case 'surname_distribution_chart': 3298add1155SRico Sonntag $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; 33088de55fdSRico Sonntag $data = $this->getSurnameChartData($surname); 3318add1155SRico Sonntag break; 3328add1155SRico Sonntag 3338add1155SRico Sonntag case 'birth_distribution_chart': 3348add1155SRico Sonntag $chart_title = I18N::translate('Birth by country'); 33588de55fdSRico Sonntag $data = $this->getBirthChartData(); 3368add1155SRico Sonntag break; 3378add1155SRico Sonntag 3388add1155SRico Sonntag case 'death_distribution_chart': 3398add1155SRico Sonntag $chart_title = I18N::translate('Death by country'); 34088de55fdSRico Sonntag $data = $this->getDeathChartData(); 3418add1155SRico Sonntag break; 3428add1155SRico Sonntag 3438add1155SRico Sonntag case 'marriage_distribution_chart': 3448add1155SRico Sonntag $chart_title = I18N::translate('Marriage by country'); 34588de55fdSRico Sonntag $data = $this->getMarriageChartData(); 3468add1155SRico Sonntag break; 3478add1155SRico Sonntag 3488add1155SRico Sonntag case 'indi_distribution_chart': 3498add1155SRico Sonntag default: 3508add1155SRico Sonntag $chart_title = I18N::translate('Individual distribution chart'); 35188de55fdSRico Sonntag $data = $this->getIndivdualChartData(); 3528add1155SRico Sonntag break; 3538add1155SRico Sonntag } 3548add1155SRico Sonntag 35588de55fdSRico Sonntag $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); 35688de55fdSRico Sonntag $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); 3578add1155SRico Sonntag 3588add1155SRico Sonntag return view( 35988de55fdSRico Sonntag 'statistics/other/charts/geo', 3608add1155SRico Sonntag [ 3618add1155SRico Sonntag 'chart_title' => $chart_title, 3628add1155SRico Sonntag 'chart_color2' => $chart_color2, 3638add1155SRico Sonntag 'chart_color3' => $chart_color3, 36488de55fdSRico Sonntag 'region' => $chart_shows, 36588de55fdSRico Sonntag 'data' => $data, 3668add1155SRico Sonntag ] 3678add1155SRico Sonntag ); 3688add1155SRico Sonntag } 3698add1155SRico Sonntag} 370