xref: /webtrees/app/Statistics/Google/ChartDistribution.php (revision cab242e7d7a773b0a6dab130048696e26fd6612c)
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;
72*cab242e7SGreg Roach        $this->theme                = app(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'])
2282b7831a1SGreg 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