xref: /webtrees/app/Statistics/Google/ChartDistribution.php (revision 71378461661e7642e52abe7d41c9cfffb3e5369b)
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