18add1155SRico Sonntag<?php 28add1155SRico Sonntag/** 38add1155SRico Sonntag * webtrees: online genealogy 4242a7862SGreg Roach * Copyright (C) 2019 webtrees development team 58add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify 68add1155SRico Sonntag * it under the terms of the GNU General Public License as published by 78add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or 88add1155SRico Sonntag * (at your option) any later version. 98add1155SRico Sonntag * This program is distributed in the hope that it will be useful, 108add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of 118add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 128add1155SRico Sonntag * GNU General Public License for more details. 138add1155SRico Sonntag * You should have received a copy of the GNU General Public License 148add1155SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>. 158add1155SRico Sonntag */ 168add1155SRico Sonntagdeclare(strict_types=1); 178add1155SRico Sonntag 188add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Google; 198add1155SRico Sonntag 208add1155SRico Sonntaguse Fisharebest\Webtrees\I18N; 2193ccd686SRico Sonntaguse Fisharebest\Webtrees\Module\ModuleThemeInterface; 228add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; 2388de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; 2493ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService; 258add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 2688de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB; 2788de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause; 288add1155SRico Sonntag 298add1155SRico Sonntag/** 3093ccd686SRico Sonntag * A chart showing the distribution of different events on a map. 318add1155SRico Sonntag */ 3293ccd686SRico Sonntagclass ChartDistribution 338add1155SRico Sonntag{ 348add1155SRico Sonntag /** 3593ccd686SRico Sonntag * @var Tree 368add1155SRico Sonntag */ 3793ccd686SRico Sonntag private $tree; 3893ccd686SRico Sonntag 3993ccd686SRico Sonntag /** 4093ccd686SRico Sonntag * @var ModuleThemeInterface 4193ccd686SRico Sonntag */ 4293ccd686SRico Sonntag private $theme; 4393ccd686SRico Sonntag 4493ccd686SRico Sonntag /** 4593ccd686SRico Sonntag * @var CountryService 4693ccd686SRico Sonntag */ 4793ccd686SRico Sonntag private $country_service; 488add1155SRico Sonntag 498add1155SRico Sonntag /** 508add1155SRico Sonntag * @var IndividualRepository 518add1155SRico Sonntag */ 528add1155SRico Sonntag private $individualRepository; 538add1155SRico Sonntag 548add1155SRico Sonntag /** 558add1155SRico Sonntag * @var PlaceRepository 568add1155SRico Sonntag */ 578add1155SRico Sonntag private $placeRepository; 588add1155SRico Sonntag 598add1155SRico Sonntag /** 6088de55fdSRico Sonntag * @var string[] 6188de55fdSRico Sonntag */ 6288de55fdSRico Sonntag private $country_to_iso3166; 6388de55fdSRico Sonntag 6488de55fdSRico Sonntag /** 658add1155SRico Sonntag * Constructor. 668add1155SRico Sonntag * 678add1155SRico Sonntag * @param Tree $tree 688add1155SRico Sonntag */ 698add1155SRico Sonntag public function __construct(Tree $tree) 708add1155SRico Sonntag { 7193ccd686SRico Sonntag $this->tree = $tree; 7293ccd686SRico Sonntag $this->theme = app()->make(ModuleThemeInterface::class); 7393ccd686SRico Sonntag $this->country_service = new CountryService(); 748add1155SRico Sonntag $this->individualRepository = new IndividualRepository($tree); 758add1155SRico Sonntag $this->placeRepository = new PlaceRepository($tree); 7688de55fdSRico Sonntag 7788de55fdSRico Sonntag // Get the country names for each language 7888de55fdSRico Sonntag $this->country_to_iso3166 = $this->getIso3166Countries(); 7988de55fdSRico Sonntag } 8088de55fdSRico Sonntag 8188de55fdSRico Sonntag /** 8288de55fdSRico Sonntag * Returns the country names for each language. 8388de55fdSRico Sonntag * 8488de55fdSRico Sonntag * @return string[] 8588de55fdSRico Sonntag */ 8688de55fdSRico Sonntag private function getIso3166Countries(): array 8788de55fdSRico Sonntag { 8893ccd686SRico Sonntag $countries = $this->country_service->getAllCountries(); 8988de55fdSRico Sonntag 9088de55fdSRico Sonntag // Get the country names for each language 9193ccd686SRico Sonntag $country_to_iso3166 = []; 9288de55fdSRico Sonntag 9388de55fdSRico Sonntag foreach (I18N::activeLocales() as $locale) { 9488de55fdSRico Sonntag I18N::init($locale->languageTag()); 9588de55fdSRico Sonntag 9693ccd686SRico Sonntag foreach ($this->country_service->iso3166() as $three => $two) { 9793ccd686SRico Sonntag $country_to_iso3166[$three] = $two; 9893ccd686SRico Sonntag $country_to_iso3166[$countries[$three]] = $two; 9988de55fdSRico Sonntag } 10088de55fdSRico Sonntag } 10188de55fdSRico Sonntag 10293ccd686SRico Sonntag return $country_to_iso3166; 10388de55fdSRico Sonntag } 10488de55fdSRico Sonntag 10588de55fdSRico Sonntag /** 10688de55fdSRico Sonntag * Returns the data structure required by google geochart. 10788de55fdSRico Sonntag * 10888de55fdSRico Sonntag * @param array $places 10988de55fdSRico Sonntag * 11088de55fdSRico Sonntag * @return array 11188de55fdSRico Sonntag */ 11288de55fdSRico Sonntag private function createChartData(array $places): array 11388de55fdSRico Sonntag { 11488de55fdSRico Sonntag $data = [ 11588de55fdSRico Sonntag [ 11688de55fdSRico Sonntag I18N::translate('Country'), 11788de55fdSRico Sonntag I18N::translate('Total'), 11888de55fdSRico Sonntag ], 11988de55fdSRico Sonntag ]; 12088de55fdSRico Sonntag 12188de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 12288de55fdSRico Sonntag foreach ($places as $country => $count) { 12388de55fdSRico Sonntag $data[] = [ 12488de55fdSRico Sonntag [ 12588de55fdSRico Sonntag 'v' => $country, 12693ccd686SRico Sonntag 'f' => $this->country_service->mapTwoLetterToName($country), 12788de55fdSRico Sonntag ], 12888de55fdSRico Sonntag $count 12988de55fdSRico Sonntag ]; 13088de55fdSRico Sonntag } 13188de55fdSRico Sonntag 13288de55fdSRico Sonntag return $data; 13388de55fdSRico Sonntag } 13488de55fdSRico Sonntag 13588de55fdSRico Sonntag /** 13688de55fdSRico Sonntag * Returns the google geochart data for birth fact. 13788de55fdSRico Sonntag * 13888de55fdSRico Sonntag * @return array 13988de55fdSRico Sonntag */ 14088de55fdSRico Sonntag private function getBirthChartData(): array 14188de55fdSRico Sonntag { 14288de55fdSRico Sonntag // Count how many people were born in each country 14388de55fdSRico Sonntag $surn_countries = []; 14488de55fdSRico Sonntag $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); 14588de55fdSRico Sonntag 14688de55fdSRico Sonntag foreach ($b_countries as $country => $count) { 14788de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 14888de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 14988de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 15088de55fdSRico Sonntag 15188de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 15288de55fdSRico Sonntag $surn_countries[$country_code] += $count; 15388de55fdSRico Sonntag } else { 15488de55fdSRico Sonntag $surn_countries[$country_code] = $count; 15588de55fdSRico Sonntag } 15688de55fdSRico Sonntag } 15788de55fdSRico Sonntag } 15888de55fdSRico Sonntag 15988de55fdSRico Sonntag return $this->createChartData($surn_countries); 16088de55fdSRico Sonntag } 16188de55fdSRico Sonntag 16288de55fdSRico Sonntag /** 16388de55fdSRico Sonntag * Returns the google geochart data for death fact. 16488de55fdSRico Sonntag * 16588de55fdSRico Sonntag * @return array 16688de55fdSRico Sonntag */ 16788de55fdSRico Sonntag private function getDeathChartData(): array 16888de55fdSRico Sonntag { 16988de55fdSRico Sonntag // Count how many people were death in each country 17088de55fdSRico Sonntag $surn_countries = []; 17188de55fdSRico Sonntag $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); 17288de55fdSRico Sonntag 17388de55fdSRico Sonntag foreach ($d_countries as $country => $count) { 17488de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 17588de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 17688de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 17788de55fdSRico Sonntag 17888de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 17988de55fdSRico Sonntag $surn_countries[$country_code] += $count; 18088de55fdSRico Sonntag } else { 18188de55fdSRico Sonntag $surn_countries[$country_code] = $count; 18288de55fdSRico Sonntag } 18388de55fdSRico Sonntag } 18488de55fdSRico Sonntag } 18588de55fdSRico Sonntag 18688de55fdSRico Sonntag return $this->createChartData($surn_countries); 18788de55fdSRico Sonntag } 18888de55fdSRico Sonntag 18988de55fdSRico Sonntag /** 19088de55fdSRico Sonntag * Returns the google geochart data for marriages. 19188de55fdSRico Sonntag * 19288de55fdSRico Sonntag * @return array 19388de55fdSRico Sonntag */ 19488de55fdSRico Sonntag private function getMarriageChartData(): array 19588de55fdSRico Sonntag { 19688de55fdSRico Sonntag // Count how many families got marriage in each country 19788de55fdSRico Sonntag $surn_countries = []; 19888de55fdSRico Sonntag $m_countries = $this->placeRepository->statsPlaces('FAM'); 19988de55fdSRico Sonntag 20088de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 20188de55fdSRico Sonntag foreach ($m_countries as $place) { 20288de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 20388de55fdSRico Sonntag if (\array_key_exists($place->country, $this->country_to_iso3166)) { 20488de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 20588de55fdSRico Sonntag 20688de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 20788de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 20888de55fdSRico Sonntag } else { 20988de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 21088de55fdSRico Sonntag } 21188de55fdSRico Sonntag } 21288de55fdSRico Sonntag } 21388de55fdSRico Sonntag 21488de55fdSRico Sonntag return $this->createChartData($surn_countries); 21588de55fdSRico Sonntag } 21688de55fdSRico Sonntag 21788de55fdSRico Sonntag /** 21888de55fdSRico Sonntag * Returns the related database records. 21988de55fdSRico Sonntag * 22088de55fdSRico Sonntag * @param string $surname 22188de55fdSRico Sonntag * 22288de55fdSRico Sonntag * @return \stdClass[] 22388de55fdSRico Sonntag */ 22488de55fdSRico Sonntag private function queryRecords(string $surname): array 22588de55fdSRico Sonntag { 22688de55fdSRico Sonntag $query = DB::table('individuals') 22788de55fdSRico Sonntag ->select(['i_gedcom']) 228*2b7831a1SGreg Roach ->join('name', function (JoinClause $join): void { 22988de55fdSRico Sonntag $join->on('n_id', '=', 'i_id') 23088de55fdSRico Sonntag ->on('n_file', '=', 'i_file'); 23188de55fdSRico Sonntag }) 23288de55fdSRico Sonntag ->where('n_file', '=', $this->tree->id()) 23388de55fdSRico Sonntag ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); 23488de55fdSRico Sonntag 23588de55fdSRico Sonntag return $query->get()->all(); 23688de55fdSRico Sonntag } 23788de55fdSRico Sonntag 23888de55fdSRico Sonntag /** 23988de55fdSRico Sonntag * Returns the google geochart data for surnames. 24088de55fdSRico Sonntag * 24188de55fdSRico Sonntag * @param string $surname The surname used to create the chart 24288de55fdSRico Sonntag * 24388de55fdSRico Sonntag * @return array 24488de55fdSRico Sonntag */ 24588de55fdSRico Sonntag private function getSurnameChartData(string $surname): array 24688de55fdSRico Sonntag { 24788de55fdSRico Sonntag if ($surname === '') { 24888de55fdSRico Sonntag $surname = $this->individualRepository->getCommonSurname(); 24988de55fdSRico Sonntag } 25088de55fdSRico Sonntag 25188de55fdSRico Sonntag // Count how many people are events in each country 25288de55fdSRico Sonntag $surn_countries = []; 25388de55fdSRico Sonntag $records = $this->queryRecords($surname); 25488de55fdSRico Sonntag 25588de55fdSRico Sonntag foreach ($records as $row) { 25688de55fdSRico Sonntag /** @var string[][] $matches */ 25788de55fdSRico Sonntag if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { 25888de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, 25988de55fdSRico Sonntag // but google uses 2 letter codes. 26088de55fdSRico Sonntag foreach ($matches[1] as $country) { 26188de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 26288de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 26388de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 26488de55fdSRico Sonntag 26588de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 26688de55fdSRico Sonntag $surn_countries[$country_code]++; 26788de55fdSRico Sonntag } else { 26888de55fdSRico Sonntag $surn_countries[$country_code] = 1; 26988de55fdSRico Sonntag } 27088de55fdSRico Sonntag } 27188de55fdSRico Sonntag } 27288de55fdSRico Sonntag } 27388de55fdSRico Sonntag } 27488de55fdSRico Sonntag 27588de55fdSRico Sonntag return $this->createChartData($surn_countries); 27688de55fdSRico Sonntag } 27788de55fdSRico Sonntag 27888de55fdSRico Sonntag /** 27988de55fdSRico Sonntag * Returns the google geochart data for individuals. 28088de55fdSRico Sonntag * 28188de55fdSRico Sonntag * @return array 28288de55fdSRico Sonntag */ 28388de55fdSRico Sonntag private function getIndivdualChartData(): array 28488de55fdSRico Sonntag { 28588de55fdSRico Sonntag // Count how many people have events in each country 28688de55fdSRico Sonntag $surn_countries = []; 28788de55fdSRico Sonntag $a_countries = $this->placeRepository->statsPlaces('INDI'); 28888de55fdSRico Sonntag 28988de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 29088de55fdSRico Sonntag foreach ($a_countries as $place) { 29188de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 29288de55fdSRico Sonntag if (\array_key_exists($place->country, $this->country_to_iso3166)) { 29388de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 29488de55fdSRico Sonntag 29588de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 29688de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 29788de55fdSRico Sonntag } else { 29888de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 29988de55fdSRico Sonntag } 30088de55fdSRico Sonntag } 30188de55fdSRico Sonntag } 30288de55fdSRico Sonntag 30388de55fdSRico Sonntag return $this->createChartData($surn_countries); 3048add1155SRico Sonntag } 3058add1155SRico Sonntag 3068add1155SRico Sonntag /** 3078add1155SRico Sonntag * Create a chart showing where events occurred. 3088add1155SRico Sonntag * 30988de55fdSRico Sonntag * @param string $chart_shows The type of chart map to show 31088de55fdSRico Sonntag * @param string $chart_type The type of chart to show 31188de55fdSRico Sonntag * @param string $surname The surname for surname based distribution chart 3128add1155SRico Sonntag * 3138add1155SRico Sonntag * @return string 3148add1155SRico Sonntag */ 3158add1155SRico Sonntag public function chartDistribution( 3168add1155SRico Sonntag string $chart_shows = 'world', 3178add1155SRico Sonntag string $chart_type = '', 3188add1155SRico Sonntag string $surname = '' 3198add1155SRico Sonntag ): string { 3208add1155SRico Sonntag I18N::init(WT_LOCALE); 3218add1155SRico Sonntag 3228add1155SRico Sonntag switch ($chart_type) { 3238add1155SRico Sonntag case 'surname_distribution_chart': 3248add1155SRico Sonntag $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; 32588de55fdSRico Sonntag $data = $this->getSurnameChartData($surname); 3268add1155SRico Sonntag break; 3278add1155SRico Sonntag 3288add1155SRico Sonntag case 'birth_distribution_chart': 3298add1155SRico Sonntag $chart_title = I18N::translate('Birth by country'); 33088de55fdSRico Sonntag $data = $this->getBirthChartData(); 3318add1155SRico Sonntag break; 3328add1155SRico Sonntag 3338add1155SRico Sonntag case 'death_distribution_chart': 3348add1155SRico Sonntag $chart_title = I18N::translate('Death by country'); 33588de55fdSRico Sonntag $data = $this->getDeathChartData(); 3368add1155SRico Sonntag break; 3378add1155SRico Sonntag 3388add1155SRico Sonntag case 'marriage_distribution_chart': 3398add1155SRico Sonntag $chart_title = I18N::translate('Marriage by country'); 34088de55fdSRico Sonntag $data = $this->getMarriageChartData(); 3418add1155SRico Sonntag break; 3428add1155SRico Sonntag 3438add1155SRico Sonntag case 'indi_distribution_chart': 3448add1155SRico Sonntag default: 3458add1155SRico Sonntag $chart_title = I18N::translate('Individual distribution chart'); 34688de55fdSRico Sonntag $data = $this->getIndivdualChartData(); 3478add1155SRico Sonntag break; 3488add1155SRico Sonntag } 3498add1155SRico Sonntag 35088de55fdSRico Sonntag $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); 35188de55fdSRico Sonntag $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); 3528add1155SRico Sonntag 3538add1155SRico Sonntag return view( 35488de55fdSRico Sonntag 'statistics/other/charts/geo', 3558add1155SRico Sonntag [ 3568add1155SRico Sonntag 'chart_title' => $chart_title, 3578add1155SRico Sonntag 'chart_color2' => $chart_color2, 3588add1155SRico Sonntag 'chart_color3' => $chart_color3, 35988de55fdSRico Sonntag 'region' => $chart_shows, 36088de55fdSRico Sonntag 'data' => $data, 3618add1155SRico Sonntag ] 3628add1155SRico Sonntag ); 3638add1155SRico Sonntag } 3648add1155SRico Sonntag} 365