18add1155SRico Sonntag<?php 28add1155SRico Sonntag/** 38add1155SRico Sonntag * webtrees: online genealogy 4*242a7862SGreg 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; 218add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\AbstractGoogle; 2288de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Helper\Country; 238add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; 2488de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; 258add1155SRico Sonntaguse Fisharebest\Webtrees\Tree; 2688de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB; 2788de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause; 288add1155SRico Sonntag 298add1155SRico Sonntag/** 308add1155SRico Sonntag * Create a chart showing where events occurred. 318add1155SRico Sonntag */ 328add1155SRico Sonntagclass ChartDistribution extends AbstractGoogle 338add1155SRico Sonntag{ 348add1155SRico Sonntag /** 358add1155SRico Sonntag * @var Country 368add1155SRico Sonntag */ 378add1155SRico Sonntag private $countryHelper; 388add1155SRico Sonntag 398add1155SRico Sonntag /** 408add1155SRico Sonntag * @var IndividualRepository 418add1155SRico Sonntag */ 428add1155SRico Sonntag private $individualRepository; 438add1155SRico Sonntag 448add1155SRico Sonntag /** 458add1155SRico Sonntag * @var PlaceRepository 468add1155SRico Sonntag */ 478add1155SRico Sonntag private $placeRepository; 488add1155SRico Sonntag 498add1155SRico Sonntag /** 5088de55fdSRico Sonntag * @var string[] 5188de55fdSRico Sonntag */ 5288de55fdSRico Sonntag private $country_to_iso3166; 5388de55fdSRico Sonntag 5488de55fdSRico Sonntag /** 558add1155SRico Sonntag * Constructor. 568add1155SRico Sonntag * 578add1155SRico Sonntag * @param Tree $tree 588add1155SRico Sonntag */ 598add1155SRico Sonntag public function __construct(Tree $tree) 608add1155SRico Sonntag { 6188de55fdSRico Sonntag parent::__construct($tree); 6288de55fdSRico Sonntag 638add1155SRico Sonntag $this->countryHelper = new Country(); 648add1155SRico Sonntag $this->individualRepository = new IndividualRepository($tree); 658add1155SRico Sonntag $this->placeRepository = new PlaceRepository($tree); 6688de55fdSRico Sonntag 6788de55fdSRico Sonntag // Get the country names for each language 6888de55fdSRico Sonntag $this->country_to_iso3166 = $this->getIso3166Countries(); 6988de55fdSRico Sonntag } 7088de55fdSRico Sonntag 7188de55fdSRico Sonntag /** 7288de55fdSRico Sonntag * Returns the country names for each language. 7388de55fdSRico Sonntag * 7488de55fdSRico Sonntag * @return string[] 7588de55fdSRico Sonntag */ 7688de55fdSRico Sonntag private function getIso3166Countries(): array 7788de55fdSRico Sonntag { 7888de55fdSRico Sonntag $countries = $this->countryHelper->getAllCountries(); 7988de55fdSRico Sonntag 8088de55fdSRico Sonntag // Get the country names for each language 8188de55fdSRico Sonntag $this->country_to_iso3166 = []; 8288de55fdSRico Sonntag 8388de55fdSRico Sonntag foreach (I18N::activeLocales() as $locale) { 8488de55fdSRico Sonntag I18N::init($locale->languageTag()); 8588de55fdSRico Sonntag 8688de55fdSRico Sonntag foreach ($this->countryHelper->iso3166() as $three => $two) { 8788de55fdSRico Sonntag $this->country_to_iso3166[$three] = $two; 8888de55fdSRico Sonntag $this->country_to_iso3166[$countries[$three]] = $two; 8988de55fdSRico Sonntag } 9088de55fdSRico Sonntag } 9188de55fdSRico Sonntag 9288de55fdSRico Sonntag return $this->country_to_iso3166; 9388de55fdSRico Sonntag } 9488de55fdSRico Sonntag 9588de55fdSRico Sonntag /** 9688de55fdSRico Sonntag * Returns the data structure required by google geochart. 9788de55fdSRico Sonntag * 9888de55fdSRico Sonntag * @param array $places 9988de55fdSRico Sonntag * 10088de55fdSRico Sonntag * @return array 10188de55fdSRico Sonntag */ 10288de55fdSRico Sonntag private function createChartData(array $places): array 10388de55fdSRico Sonntag { 10488de55fdSRico Sonntag $data = [ 10588de55fdSRico Sonntag [ 10688de55fdSRico Sonntag I18N::translate('Country'), 10788de55fdSRico Sonntag I18N::translate('Total'), 10888de55fdSRico Sonntag ], 10988de55fdSRico Sonntag ]; 11088de55fdSRico Sonntag 11188de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 11288de55fdSRico Sonntag foreach ($places as $country => $count) { 11388de55fdSRico Sonntag $data[] = [ 11488de55fdSRico Sonntag [ 11588de55fdSRico Sonntag 'v' => $country, 11688de55fdSRico Sonntag 'f' => $this->countryHelper->mapTwoLetterToName($country), 11788de55fdSRico Sonntag ], 11888de55fdSRico Sonntag $count 11988de55fdSRico Sonntag ]; 12088de55fdSRico Sonntag } 12188de55fdSRico Sonntag 12288de55fdSRico Sonntag return $data; 12388de55fdSRico Sonntag } 12488de55fdSRico Sonntag 12588de55fdSRico Sonntag /** 12688de55fdSRico Sonntag * Returns the google geochart data for birth fact. 12788de55fdSRico Sonntag * 12888de55fdSRico Sonntag * @return array 12988de55fdSRico Sonntag */ 13088de55fdSRico Sonntag private function getBirthChartData(): array 13188de55fdSRico Sonntag { 13288de55fdSRico Sonntag // Count how many people were born in each country 13388de55fdSRico Sonntag $surn_countries = []; 13488de55fdSRico Sonntag $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); 13588de55fdSRico Sonntag 13688de55fdSRico Sonntag foreach ($b_countries as $country => $count) { 13788de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 13888de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 13988de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 14088de55fdSRico Sonntag 14188de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 14288de55fdSRico Sonntag $surn_countries[$country_code] += $count; 14388de55fdSRico Sonntag } else { 14488de55fdSRico Sonntag $surn_countries[$country_code] = $count; 14588de55fdSRico Sonntag } 14688de55fdSRico Sonntag } 14788de55fdSRico Sonntag } 14888de55fdSRico Sonntag 14988de55fdSRico Sonntag return $this->createChartData($surn_countries); 15088de55fdSRico Sonntag } 15188de55fdSRico Sonntag 15288de55fdSRico Sonntag /** 15388de55fdSRico Sonntag * Returns the google geochart data for death fact. 15488de55fdSRico Sonntag * 15588de55fdSRico Sonntag * @return array 15688de55fdSRico Sonntag */ 15788de55fdSRico Sonntag private function getDeathChartData(): array 15888de55fdSRico Sonntag { 15988de55fdSRico Sonntag // Count how many people were death in each country 16088de55fdSRico Sonntag $surn_countries = []; 16188de55fdSRico Sonntag $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); 16288de55fdSRico Sonntag 16388de55fdSRico Sonntag foreach ($d_countries as $country => $count) { 16488de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 16588de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 16688de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 16788de55fdSRico Sonntag 16888de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 16988de55fdSRico Sonntag $surn_countries[$country_code] += $count; 17088de55fdSRico Sonntag } else { 17188de55fdSRico Sonntag $surn_countries[$country_code] = $count; 17288de55fdSRico Sonntag } 17388de55fdSRico Sonntag } 17488de55fdSRico Sonntag } 17588de55fdSRico Sonntag 17688de55fdSRico Sonntag return $this->createChartData($surn_countries); 17788de55fdSRico Sonntag } 17888de55fdSRico Sonntag 17988de55fdSRico Sonntag /** 18088de55fdSRico Sonntag * Returns the google geochart data for marriages. 18188de55fdSRico Sonntag * 18288de55fdSRico Sonntag * @return array 18388de55fdSRico Sonntag */ 18488de55fdSRico Sonntag private function getMarriageChartData(): array 18588de55fdSRico Sonntag { 18688de55fdSRico Sonntag // Count how many families got marriage in each country 18788de55fdSRico Sonntag $surn_countries = []; 18888de55fdSRico Sonntag $m_countries = $this->placeRepository->statsPlaces('FAM'); 18988de55fdSRico Sonntag 19088de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 19188de55fdSRico Sonntag foreach ($m_countries as $place) { 19288de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 19388de55fdSRico Sonntag if (\array_key_exists($place->country, $this->country_to_iso3166)) { 19488de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 19588de55fdSRico Sonntag 19688de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 19788de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 19888de55fdSRico Sonntag } else { 19988de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 20088de55fdSRico Sonntag } 20188de55fdSRico Sonntag } 20288de55fdSRico Sonntag } 20388de55fdSRico Sonntag 20488de55fdSRico Sonntag return $this->createChartData($surn_countries); 20588de55fdSRico Sonntag } 20688de55fdSRico Sonntag 20788de55fdSRico Sonntag /** 20888de55fdSRico Sonntag * Returns the related database records. 20988de55fdSRico Sonntag * 21088de55fdSRico Sonntag * @param string $surname 21188de55fdSRico Sonntag * 21288de55fdSRico Sonntag * @return \stdClass[] 21388de55fdSRico Sonntag */ 21488de55fdSRico Sonntag private function queryRecords(string $surname): array 21588de55fdSRico Sonntag { 21688de55fdSRico Sonntag $query = DB::table('individuals') 21788de55fdSRico Sonntag ->select(['i_gedcom']) 21888de55fdSRico Sonntag ->join('name', function (JoinClause $join) { 21988de55fdSRico Sonntag $join->on('n_id', '=', 'i_id') 22088de55fdSRico Sonntag ->on('n_file', '=', 'i_file'); 22188de55fdSRico Sonntag }) 22288de55fdSRico Sonntag ->where('n_file', '=', $this->tree->id()) 22388de55fdSRico Sonntag ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); 22488de55fdSRico Sonntag 22588de55fdSRico Sonntag return $query->get()->all(); 22688de55fdSRico Sonntag } 22788de55fdSRico Sonntag 22888de55fdSRico Sonntag /** 22988de55fdSRico Sonntag * Returns the google geochart data for surnames. 23088de55fdSRico Sonntag * 23188de55fdSRico Sonntag * @param string $surname The surname used to create the chart 23288de55fdSRico Sonntag * 23388de55fdSRico Sonntag * @return array 23488de55fdSRico Sonntag */ 23588de55fdSRico Sonntag private function getSurnameChartData(string $surname): array 23688de55fdSRico Sonntag { 23788de55fdSRico Sonntag if ($surname === '') { 23888de55fdSRico Sonntag $surname = $this->individualRepository->getCommonSurname(); 23988de55fdSRico Sonntag } 24088de55fdSRico Sonntag 24188de55fdSRico Sonntag // Count how many people are events in each country 24288de55fdSRico Sonntag $surn_countries = []; 24388de55fdSRico Sonntag $records = $this->queryRecords($surname); 24488de55fdSRico Sonntag 24588de55fdSRico Sonntag foreach ($records as $row) { 24688de55fdSRico Sonntag /** @var string[][] $matches */ 24788de55fdSRico Sonntag if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { 24888de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, 24988de55fdSRico Sonntag // but google uses 2 letter codes. 25088de55fdSRico Sonntag foreach ($matches[1] as $country) { 25188de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 25288de55fdSRico Sonntag if (\array_key_exists($country, $this->country_to_iso3166)) { 25388de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 25488de55fdSRico Sonntag 25588de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 25688de55fdSRico Sonntag $surn_countries[$country_code]++; 25788de55fdSRico Sonntag } else { 25888de55fdSRico Sonntag $surn_countries[$country_code] = 1; 25988de55fdSRico Sonntag } 26088de55fdSRico Sonntag } 26188de55fdSRico Sonntag } 26288de55fdSRico Sonntag } 26388de55fdSRico Sonntag } 26488de55fdSRico Sonntag 26588de55fdSRico Sonntag return $this->createChartData($surn_countries); 26688de55fdSRico Sonntag } 26788de55fdSRico Sonntag 26888de55fdSRico Sonntag /** 26988de55fdSRico Sonntag * Returns the google geochart data for individuals. 27088de55fdSRico Sonntag * 27188de55fdSRico Sonntag * @return array 27288de55fdSRico Sonntag */ 27388de55fdSRico Sonntag private function getIndivdualChartData(): array 27488de55fdSRico Sonntag { 27588de55fdSRico Sonntag // Count how many people have events in each country 27688de55fdSRico Sonntag $surn_countries = []; 27788de55fdSRico Sonntag $a_countries = $this->placeRepository->statsPlaces('INDI'); 27888de55fdSRico Sonntag 27988de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 28088de55fdSRico Sonntag foreach ($a_countries as $place) { 28188de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 28288de55fdSRico Sonntag if (\array_key_exists($place->country, $this->country_to_iso3166)) { 28388de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 28488de55fdSRico Sonntag 28588de55fdSRico Sonntag if (\array_key_exists($country_code, $surn_countries)) { 28688de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 28788de55fdSRico Sonntag } else { 28888de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 28988de55fdSRico Sonntag } 29088de55fdSRico Sonntag } 29188de55fdSRico Sonntag } 29288de55fdSRico Sonntag 29388de55fdSRico Sonntag return $this->createChartData($surn_countries); 2948add1155SRico Sonntag } 2958add1155SRico Sonntag 2968add1155SRico Sonntag /** 2978add1155SRico Sonntag * Create a chart showing where events occurred. 2988add1155SRico Sonntag * 29988de55fdSRico Sonntag * @param string $chart_shows The type of chart map to show 30088de55fdSRico Sonntag * @param string $chart_type The type of chart to show 30188de55fdSRico Sonntag * @param string $surname The surname for surname based distribution chart 3028add1155SRico Sonntag * 3038add1155SRico Sonntag * @return string 3048add1155SRico Sonntag */ 3058add1155SRico Sonntag public function chartDistribution( 3068add1155SRico Sonntag string $chart_shows = 'world', 3078add1155SRico Sonntag string $chart_type = '', 3088add1155SRico Sonntag string $surname = '' 3098add1155SRico Sonntag ): string { 3108add1155SRico Sonntag I18N::init(WT_LOCALE); 3118add1155SRico Sonntag 3128add1155SRico Sonntag switch ($chart_type) { 3138add1155SRico Sonntag case 'surname_distribution_chart': 3148add1155SRico Sonntag $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; 31588de55fdSRico Sonntag $data = $this->getSurnameChartData($surname); 3168add1155SRico Sonntag break; 3178add1155SRico Sonntag 3188add1155SRico Sonntag case 'birth_distribution_chart': 3198add1155SRico Sonntag $chart_title = I18N::translate('Birth by country'); 32088de55fdSRico Sonntag $data = $this->getBirthChartData(); 3218add1155SRico Sonntag break; 3228add1155SRico Sonntag 3238add1155SRico Sonntag case 'death_distribution_chart': 3248add1155SRico Sonntag $chart_title = I18N::translate('Death by country'); 32588de55fdSRico Sonntag $data = $this->getDeathChartData(); 3268add1155SRico Sonntag break; 3278add1155SRico Sonntag 3288add1155SRico Sonntag case 'marriage_distribution_chart': 3298add1155SRico Sonntag $chart_title = I18N::translate('Marriage by country'); 33088de55fdSRico Sonntag $data = $this->getMarriageChartData(); 3318add1155SRico Sonntag break; 3328add1155SRico Sonntag 3338add1155SRico Sonntag case 'indi_distribution_chart': 3348add1155SRico Sonntag default: 3358add1155SRico Sonntag $chart_title = I18N::translate('Individual distribution chart'); 33688de55fdSRico Sonntag $data = $this->getIndivdualChartData(); 3378add1155SRico Sonntag break; 3388add1155SRico Sonntag } 3398add1155SRico Sonntag 34088de55fdSRico Sonntag $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); 34188de55fdSRico Sonntag $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); 3428add1155SRico Sonntag 3438add1155SRico Sonntag return view( 34488de55fdSRico Sonntag 'statistics/other/charts/geo', 3458add1155SRico Sonntag [ 3468add1155SRico Sonntag 'chart_title' => $chart_title, 3478add1155SRico Sonntag 'chart_color2' => $chart_color2, 3488add1155SRico Sonntag 'chart_color3' => $chart_color3, 34988de55fdSRico Sonntag 'region' => $chart_shows, 35088de55fdSRico Sonntag 'data' => $data, 3518add1155SRico Sonntag ] 3528add1155SRico Sonntag ); 3538add1155SRico Sonntag } 3548add1155SRico Sonntag} 355