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 20*6ccdf4f0SGreg Roachuse function array_key_exists; 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; 2888de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause; 29*6ccdf4f0SGreg Roachuse stdClass; 308add1155SRico Sonntag 318add1155SRico Sonntag/** 3293ccd686SRico Sonntag * A chart showing the distribution of different events on a map. 338add1155SRico Sonntag */ 3493ccd686SRico Sonntagclass ChartDistribution 358add1155SRico Sonntag{ 368add1155SRico Sonntag /** 3793ccd686SRico Sonntag * @var Tree 388add1155SRico Sonntag */ 3993ccd686SRico Sonntag private $tree; 4093ccd686SRico Sonntag 4193ccd686SRico Sonntag /** 4293ccd686SRico Sonntag * @var ModuleThemeInterface 4393ccd686SRico Sonntag */ 4493ccd686SRico Sonntag private $theme; 4593ccd686SRico Sonntag 4693ccd686SRico Sonntag /** 4793ccd686SRico Sonntag * @var CountryService 4893ccd686SRico Sonntag */ 4993ccd686SRico Sonntag private $country_service; 508add1155SRico Sonntag 518add1155SRico Sonntag /** 528add1155SRico Sonntag * @var IndividualRepository 538add1155SRico Sonntag */ 548add1155SRico Sonntag private $individualRepository; 558add1155SRico Sonntag 568add1155SRico Sonntag /** 578add1155SRico Sonntag * @var PlaceRepository 588add1155SRico Sonntag */ 598add1155SRico Sonntag private $placeRepository; 608add1155SRico Sonntag 618add1155SRico Sonntag /** 6288de55fdSRico Sonntag * @var string[] 6388de55fdSRico Sonntag */ 6488de55fdSRico Sonntag private $country_to_iso3166; 6588de55fdSRico Sonntag 6688de55fdSRico Sonntag /** 678add1155SRico Sonntag * Constructor. 688add1155SRico Sonntag * 698add1155SRico Sonntag * @param Tree $tree 708add1155SRico Sonntag */ 718add1155SRico Sonntag public function __construct(Tree $tree) 728add1155SRico Sonntag { 7393ccd686SRico Sonntag $this->tree = $tree; 74cab242e7SGreg Roach $this->theme = app(ModuleThemeInterface::class); 7593ccd686SRico Sonntag $this->country_service = new CountryService(); 768add1155SRico Sonntag $this->individualRepository = new IndividualRepository($tree); 778add1155SRico Sonntag $this->placeRepository = new PlaceRepository($tree); 7888de55fdSRico Sonntag 7988de55fdSRico Sonntag // Get the country names for each language 8088de55fdSRico Sonntag $this->country_to_iso3166 = $this->getIso3166Countries(); 8188de55fdSRico Sonntag } 8288de55fdSRico Sonntag 8388de55fdSRico Sonntag /** 8488de55fdSRico Sonntag * Returns the country names for each language. 8588de55fdSRico Sonntag * 8688de55fdSRico Sonntag * @return string[] 8788de55fdSRico Sonntag */ 8888de55fdSRico Sonntag private function getIso3166Countries(): array 8988de55fdSRico Sonntag { 9093ccd686SRico Sonntag $countries = $this->country_service->getAllCountries(); 9188de55fdSRico Sonntag 9288de55fdSRico Sonntag // Get the country names for each language 9393ccd686SRico Sonntag $country_to_iso3166 = []; 9488de55fdSRico Sonntag 9588de55fdSRico Sonntag foreach (I18N::activeLocales() as $locale) { 9688de55fdSRico Sonntag I18N::init($locale->languageTag()); 9788de55fdSRico Sonntag 9893ccd686SRico Sonntag foreach ($this->country_service->iso3166() as $three => $two) { 9993ccd686SRico Sonntag $country_to_iso3166[$three] = $two; 10093ccd686SRico Sonntag $country_to_iso3166[$countries[$three]] = $two; 10188de55fdSRico Sonntag } 10288de55fdSRico Sonntag } 10388de55fdSRico Sonntag 10493ccd686SRico Sonntag return $country_to_iso3166; 10588de55fdSRico Sonntag } 10688de55fdSRico Sonntag 10788de55fdSRico Sonntag /** 10888de55fdSRico Sonntag * Returns the data structure required by google geochart. 10988de55fdSRico Sonntag * 11088de55fdSRico Sonntag * @param array $places 11188de55fdSRico Sonntag * 11288de55fdSRico Sonntag * @return array 11388de55fdSRico Sonntag */ 11488de55fdSRico Sonntag private function createChartData(array $places): array 11588de55fdSRico Sonntag { 11688de55fdSRico Sonntag $data = [ 11788de55fdSRico Sonntag [ 11888de55fdSRico Sonntag I18N::translate('Country'), 11988de55fdSRico Sonntag I18N::translate('Total'), 12088de55fdSRico Sonntag ], 12188de55fdSRico Sonntag ]; 12288de55fdSRico Sonntag 12388de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 12488de55fdSRico Sonntag foreach ($places as $country => $count) { 12588de55fdSRico Sonntag $data[] = [ 12688de55fdSRico Sonntag [ 12788de55fdSRico Sonntag 'v' => $country, 12893ccd686SRico Sonntag 'f' => $this->country_service->mapTwoLetterToName($country), 12988de55fdSRico Sonntag ], 13088de55fdSRico Sonntag $count 13188de55fdSRico Sonntag ]; 13288de55fdSRico Sonntag } 13388de55fdSRico Sonntag 13488de55fdSRico Sonntag return $data; 13588de55fdSRico Sonntag } 13688de55fdSRico Sonntag 13788de55fdSRico Sonntag /** 13888de55fdSRico Sonntag * Returns the google geochart data for birth fact. 13988de55fdSRico Sonntag * 14088de55fdSRico Sonntag * @return array 14188de55fdSRico Sonntag */ 14288de55fdSRico Sonntag private function getBirthChartData(): array 14388de55fdSRico Sonntag { 14488de55fdSRico Sonntag // Count how many people were born in each country 14588de55fdSRico Sonntag $surn_countries = []; 14688de55fdSRico Sonntag $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); 14788de55fdSRico Sonntag 14888de55fdSRico Sonntag foreach ($b_countries as $country => $count) { 14988de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 150*6ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 15188de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 15288de55fdSRico Sonntag 153*6ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 15488de55fdSRico Sonntag $surn_countries[$country_code] += $count; 15588de55fdSRico Sonntag } else { 15688de55fdSRico Sonntag $surn_countries[$country_code] = $count; 15788de55fdSRico Sonntag } 15888de55fdSRico Sonntag } 15988de55fdSRico Sonntag } 16088de55fdSRico Sonntag 16188de55fdSRico Sonntag return $this->createChartData($surn_countries); 16288de55fdSRico Sonntag } 16388de55fdSRico Sonntag 16488de55fdSRico Sonntag /** 16588de55fdSRico Sonntag * Returns the google geochart data for death fact. 16688de55fdSRico Sonntag * 16788de55fdSRico Sonntag * @return array 16888de55fdSRico Sonntag */ 16988de55fdSRico Sonntag private function getDeathChartData(): array 17088de55fdSRico Sonntag { 17188de55fdSRico Sonntag // Count how many people were death in each country 17288de55fdSRico Sonntag $surn_countries = []; 17388de55fdSRico Sonntag $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); 17488de55fdSRico Sonntag 17588de55fdSRico Sonntag foreach ($d_countries as $country => $count) { 17688de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 177*6ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 17888de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 17988de55fdSRico Sonntag 180*6ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 18188de55fdSRico Sonntag $surn_countries[$country_code] += $count; 18288de55fdSRico Sonntag } else { 18388de55fdSRico Sonntag $surn_countries[$country_code] = $count; 18488de55fdSRico Sonntag } 18588de55fdSRico Sonntag } 18688de55fdSRico Sonntag } 18788de55fdSRico Sonntag 18888de55fdSRico Sonntag return $this->createChartData($surn_countries); 18988de55fdSRico Sonntag } 19088de55fdSRico Sonntag 19188de55fdSRico Sonntag /** 19288de55fdSRico Sonntag * Returns the google geochart data for marriages. 19388de55fdSRico Sonntag * 19488de55fdSRico Sonntag * @return array 19588de55fdSRico Sonntag */ 19688de55fdSRico Sonntag private function getMarriageChartData(): array 19788de55fdSRico Sonntag { 19888de55fdSRico Sonntag // Count how many families got marriage in each country 19988de55fdSRico Sonntag $surn_countries = []; 20088de55fdSRico Sonntag $m_countries = $this->placeRepository->statsPlaces('FAM'); 20188de55fdSRico Sonntag 20288de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 20388de55fdSRico Sonntag foreach ($m_countries as $place) { 20488de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 205*6ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 20688de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 20788de55fdSRico Sonntag 208*6ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 20988de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 21088de55fdSRico Sonntag } else { 21188de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 21288de55fdSRico Sonntag } 21388de55fdSRico Sonntag } 21488de55fdSRico Sonntag } 21588de55fdSRico Sonntag 21688de55fdSRico Sonntag return $this->createChartData($surn_countries); 21788de55fdSRico Sonntag } 21888de55fdSRico Sonntag 21988de55fdSRico Sonntag /** 22088de55fdSRico Sonntag * Returns the related database records. 22188de55fdSRico Sonntag * 22288de55fdSRico Sonntag * @param string $surname 22388de55fdSRico Sonntag * 224*6ccdf4f0SGreg Roach * @return stdClass[] 22588de55fdSRico Sonntag */ 22688de55fdSRico Sonntag private function queryRecords(string $surname): array 22788de55fdSRico Sonntag { 22888de55fdSRico Sonntag $query = DB::table('individuals') 22988de55fdSRico Sonntag ->select(['i_gedcom']) 2300b5fd0a6SGreg Roach ->join('name', static function (JoinClause $join): void { 23188de55fdSRico Sonntag $join->on('n_id', '=', 'i_id') 23288de55fdSRico Sonntag ->on('n_file', '=', 'i_file'); 23388de55fdSRico Sonntag }) 23488de55fdSRico Sonntag ->where('n_file', '=', $this->tree->id()) 23588de55fdSRico Sonntag ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); 23688de55fdSRico Sonntag 23788de55fdSRico Sonntag return $query->get()->all(); 23888de55fdSRico Sonntag } 23988de55fdSRico Sonntag 24088de55fdSRico Sonntag /** 24188de55fdSRico Sonntag * Returns the google geochart data for surnames. 24288de55fdSRico Sonntag * 24388de55fdSRico Sonntag * @param string $surname The surname used to create the chart 24488de55fdSRico Sonntag * 24588de55fdSRico Sonntag * @return array 24688de55fdSRico Sonntag */ 24788de55fdSRico Sonntag private function getSurnameChartData(string $surname): array 24888de55fdSRico Sonntag { 24988de55fdSRico Sonntag if ($surname === '') { 25088de55fdSRico Sonntag $surname = $this->individualRepository->getCommonSurname(); 25188de55fdSRico Sonntag } 25288de55fdSRico Sonntag 25388de55fdSRico Sonntag // Count how many people are events in each country 25488de55fdSRico Sonntag $surn_countries = []; 25588de55fdSRico Sonntag $records = $this->queryRecords($surname); 25688de55fdSRico Sonntag 25788de55fdSRico Sonntag foreach ($records as $row) { 25888de55fdSRico Sonntag /** @var string[][] $matches */ 25988de55fdSRico Sonntag if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { 26088de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, 26188de55fdSRico Sonntag // but google uses 2 letter codes. 26288de55fdSRico Sonntag foreach ($matches[1] as $country) { 26388de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 264*6ccdf4f0SGreg Roach if (array_key_exists($country, $this->country_to_iso3166)) { 26588de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$country]; 26688de55fdSRico Sonntag 267*6ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 26888de55fdSRico Sonntag $surn_countries[$country_code]++; 26988de55fdSRico Sonntag } else { 27088de55fdSRico Sonntag $surn_countries[$country_code] = 1; 27188de55fdSRico Sonntag } 27288de55fdSRico Sonntag } 27388de55fdSRico Sonntag } 27488de55fdSRico Sonntag } 27588de55fdSRico Sonntag } 27688de55fdSRico Sonntag 27788de55fdSRico Sonntag return $this->createChartData($surn_countries); 27888de55fdSRico Sonntag } 27988de55fdSRico Sonntag 28088de55fdSRico Sonntag /** 28188de55fdSRico Sonntag * Returns the google geochart data for individuals. 28288de55fdSRico Sonntag * 28388de55fdSRico Sonntag * @return array 28488de55fdSRico Sonntag */ 28588de55fdSRico Sonntag private function getIndivdualChartData(): array 28688de55fdSRico Sonntag { 28788de55fdSRico Sonntag // Count how many people have events in each country 28888de55fdSRico Sonntag $surn_countries = []; 28988de55fdSRico Sonntag $a_countries = $this->placeRepository->statsPlaces('INDI'); 29088de55fdSRico Sonntag 29188de55fdSRico Sonntag // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. 29288de55fdSRico Sonntag foreach ($a_countries as $place) { 29388de55fdSRico Sonntag // Consolidate places (Germany, DEU => DE) 294*6ccdf4f0SGreg Roach if (array_key_exists($place->country, $this->country_to_iso3166)) { 29588de55fdSRico Sonntag $country_code = $this->country_to_iso3166[$place->country]; 29688de55fdSRico Sonntag 297*6ccdf4f0SGreg Roach if (array_key_exists($country_code, $surn_countries)) { 29888de55fdSRico Sonntag $surn_countries[$country_code] += $place->tot; 29988de55fdSRico Sonntag } else { 30088de55fdSRico Sonntag $surn_countries[$country_code] = $place->tot; 30188de55fdSRico Sonntag } 30288de55fdSRico Sonntag } 30388de55fdSRico Sonntag } 30488de55fdSRico Sonntag 30588de55fdSRico Sonntag return $this->createChartData($surn_countries); 3068add1155SRico Sonntag } 3078add1155SRico Sonntag 3088add1155SRico Sonntag /** 3098add1155SRico Sonntag * Create a chart showing where events occurred. 3108add1155SRico Sonntag * 31188de55fdSRico Sonntag * @param string $chart_shows The type of chart map to show 31288de55fdSRico Sonntag * @param string $chart_type The type of chart to show 31388de55fdSRico Sonntag * @param string $surname The surname for surname based distribution chart 3148add1155SRico Sonntag * 3158add1155SRico Sonntag * @return string 3168add1155SRico Sonntag */ 3178add1155SRico Sonntag public function chartDistribution( 3188add1155SRico Sonntag string $chart_shows = 'world', 3198add1155SRico Sonntag string $chart_type = '', 3208add1155SRico Sonntag string $surname = '' 3218add1155SRico Sonntag ): string { 3228add1155SRico Sonntag I18N::init(WT_LOCALE); 3238add1155SRico Sonntag 3248add1155SRico Sonntag switch ($chart_type) { 3258add1155SRico Sonntag case 'surname_distribution_chart': 3268add1155SRico Sonntag $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; 32788de55fdSRico Sonntag $data = $this->getSurnameChartData($surname); 3288add1155SRico Sonntag break; 3298add1155SRico Sonntag 3308add1155SRico Sonntag case 'birth_distribution_chart': 3318add1155SRico Sonntag $chart_title = I18N::translate('Birth by country'); 33288de55fdSRico Sonntag $data = $this->getBirthChartData(); 3338add1155SRico Sonntag break; 3348add1155SRico Sonntag 3358add1155SRico Sonntag case 'death_distribution_chart': 3368add1155SRico Sonntag $chart_title = I18N::translate('Death by country'); 33788de55fdSRico Sonntag $data = $this->getDeathChartData(); 3388add1155SRico Sonntag break; 3398add1155SRico Sonntag 3408add1155SRico Sonntag case 'marriage_distribution_chart': 3418add1155SRico Sonntag $chart_title = I18N::translate('Marriage by country'); 34288de55fdSRico Sonntag $data = $this->getMarriageChartData(); 3438add1155SRico Sonntag break; 3448add1155SRico Sonntag 3458add1155SRico Sonntag case 'indi_distribution_chart': 3468add1155SRico Sonntag default: 3478add1155SRico Sonntag $chart_title = I18N::translate('Individual distribution chart'); 34888de55fdSRico Sonntag $data = $this->getIndivdualChartData(); 3498add1155SRico Sonntag break; 3508add1155SRico Sonntag } 3518add1155SRico Sonntag 35288de55fdSRico Sonntag $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); 35388de55fdSRico Sonntag $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); 3548add1155SRico Sonntag 3558add1155SRico Sonntag return view( 35688de55fdSRico Sonntag 'statistics/other/charts/geo', 3578add1155SRico Sonntag [ 3588add1155SRico Sonntag 'chart_title' => $chart_title, 3598add1155SRico Sonntag 'chart_color2' => $chart_color2, 3608add1155SRico Sonntag 'chart_color3' => $chart_color3, 36188de55fdSRico Sonntag 'region' => $chart_shows, 36288de55fdSRico Sonntag 'data' => $data, 3638add1155SRico Sonntag ] 3648add1155SRico Sonntag ); 3658add1155SRico Sonntag } 3668add1155SRico Sonntag} 367