xref: /webtrees/app/Statistics/Google/ChartDistribution.php (revision 870ec5a3f6f8023085b468a88a94b244578a1408)
18add1155SRico Sonntag<?php
23976b470SGreg Roach
38add1155SRico Sonntag/**
48add1155SRico Sonntag * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 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
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
168add1155SRico Sonntag */
17fcfa147eSGreg Roach
188add1155SRico Sonntagdeclare(strict_types=1);
198add1155SRico Sonntag
208add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Google;
218add1155SRico Sonntag
228add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
2393ccd686SRico Sonntaguse Fisharebest\Webtrees\Module\ModuleThemeInterface;
248add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository;
2588de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository;
2693ccd686SRico Sonntaguse Fisharebest\Webtrees\Statistics\Service\CountryService;
278add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
2888de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB;
29a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
3088de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause;
316ccdf4f0SGreg Roachuse stdClass;
328add1155SRico Sonntag
3390a2f718SGreg Roachuse function app;
3471378461SGreg Roachuse function array_key_exists;
3571378461SGreg Roach
368add1155SRico Sonntag/**
3793ccd686SRico Sonntag * A chart showing the distribution of different events on a map.
388add1155SRico Sonntag */
3993ccd686SRico Sonntagclass ChartDistribution
408add1155SRico Sonntag{
418add1155SRico Sonntag    /**
4293ccd686SRico Sonntag     * @var Tree
438add1155SRico Sonntag     */
4493ccd686SRico Sonntag    private $tree;
4593ccd686SRico Sonntag
4693ccd686SRico Sonntag    /**
4793ccd686SRico Sonntag     * @var ModuleThemeInterface
4893ccd686SRico Sonntag     */
4993ccd686SRico Sonntag    private $theme;
5093ccd686SRico Sonntag
5193ccd686SRico Sonntag    /**
5293ccd686SRico Sonntag     * @var CountryService
5393ccd686SRico Sonntag     */
5493ccd686SRico Sonntag    private $country_service;
558add1155SRico Sonntag
568add1155SRico Sonntag    /**
578add1155SRico Sonntag     * @var IndividualRepository
588add1155SRico Sonntag     */
598add1155SRico Sonntag    private $individualRepository;
608add1155SRico Sonntag
618add1155SRico Sonntag    /**
628add1155SRico Sonntag     * @var PlaceRepository
638add1155SRico Sonntag     */
648add1155SRico Sonntag    private $placeRepository;
658add1155SRico Sonntag
668add1155SRico Sonntag    /**
6788de55fdSRico Sonntag     * @var string[]
6888de55fdSRico Sonntag     */
6988de55fdSRico Sonntag    private $country_to_iso3166;
7088de55fdSRico Sonntag
7188de55fdSRico Sonntag    /**
728add1155SRico Sonntag     * Constructor.
738add1155SRico Sonntag     *
748add1155SRico Sonntag     * @param Tree $tree
758add1155SRico Sonntag     */
768add1155SRico Sonntag    public function __construct(Tree $tree)
778add1155SRico Sonntag    {
7893ccd686SRico Sonntag        $this->tree                 = $tree;
79cab242e7SGreg Roach        $this->theme                = app(ModuleThemeInterface::class);
8093ccd686SRico Sonntag        $this->country_service      = new CountryService();
818add1155SRico Sonntag        $this->individualRepository = new IndividualRepository($tree);
828add1155SRico Sonntag        $this->placeRepository      = new PlaceRepository($tree);
8388de55fdSRico Sonntag
8488de55fdSRico Sonntag        // Get the country names for each language
8588de55fdSRico Sonntag        $this->country_to_iso3166 = $this->getIso3166Countries();
8688de55fdSRico Sonntag    }
8788de55fdSRico Sonntag
8888de55fdSRico Sonntag    /**
8988de55fdSRico Sonntag     * Returns the country names for each language.
9088de55fdSRico Sonntag     *
9124f2a3afSGreg Roach     * @return array<string>
9288de55fdSRico Sonntag     */
9388de55fdSRico Sonntag    private function getIso3166Countries(): array
9488de55fdSRico Sonntag    {
9588de55fdSRico Sonntag        // Get the country names for each language
9693ccd686SRico Sonntag        $country_to_iso3166 = [];
9788de55fdSRico Sonntag
9856eca4e8SGreg Roach        $current_language = I18N::languageTag();
9956eca4e8SGreg Roach
10088de55fdSRico Sonntag        foreach (I18N::activeLocales() as $locale) {
10188de55fdSRico Sonntag            I18N::init($locale->languageTag());
10288de55fdSRico Sonntag
10356eca4e8SGreg Roach            $countries = $this->country_service->getAllCountries();
10456eca4e8SGreg Roach
10593ccd686SRico Sonntag            foreach ($this->country_service->iso3166() as $three => $two) {
10693ccd686SRico Sonntag                $country_to_iso3166[$three]             = $two;
10793ccd686SRico Sonntag                $country_to_iso3166[$countries[$three]] = $two;
10888de55fdSRico Sonntag            }
10988de55fdSRico Sonntag        }
11088de55fdSRico Sonntag
11156eca4e8SGreg Roach        I18N::init($current_language);
11256eca4e8SGreg Roach
11393ccd686SRico Sonntag        return $country_to_iso3166;
11488de55fdSRico Sonntag    }
11588de55fdSRico Sonntag
11688de55fdSRico Sonntag    /**
11788de55fdSRico Sonntag     * Returns the data structure required by google geochart.
11888de55fdSRico Sonntag     *
11988de55fdSRico Sonntag     * @param array $places
12088de55fdSRico Sonntag     *
12188de55fdSRico Sonntag     * @return array
12288de55fdSRico Sonntag     */
12388de55fdSRico Sonntag    private function createChartData(array $places): array
12488de55fdSRico Sonntag    {
12588de55fdSRico Sonntag        $data = [
12688de55fdSRico Sonntag            [
12788de55fdSRico Sonntag                I18N::translate('Country'),
12888de55fdSRico Sonntag                I18N::translate('Total'),
12988de55fdSRico Sonntag            ],
13088de55fdSRico Sonntag        ];
13188de55fdSRico Sonntag
13288de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
13388de55fdSRico Sonntag        foreach ($places as $country => $count) {
13488de55fdSRico Sonntag            $data[] = [
13588de55fdSRico Sonntag                [
13688de55fdSRico Sonntag                    'v' => $country,
13793ccd686SRico Sonntag                    'f' => $this->country_service->mapTwoLetterToName($country),
13888de55fdSRico Sonntag                ],
13988de55fdSRico Sonntag                $count
14088de55fdSRico Sonntag            ];
14188de55fdSRico Sonntag        }
14288de55fdSRico Sonntag
14388de55fdSRico Sonntag        return $data;
14488de55fdSRico Sonntag    }
14588de55fdSRico Sonntag
14688de55fdSRico Sonntag    /**
14788de55fdSRico Sonntag     * Returns the google geochart data for birth fact.
14888de55fdSRico Sonntag     *
14988de55fdSRico Sonntag     * @return array
15088de55fdSRico Sonntag     */
15188de55fdSRico Sonntag    private function getBirthChartData(): array
15288de55fdSRico Sonntag    {
15388de55fdSRico Sonntag        // Count how many people were born in each country
15488de55fdSRico Sonntag        $surn_countries = [];
15588de55fdSRico Sonntag        $b_countries    = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true);
15688de55fdSRico Sonntag
15788de55fdSRico Sonntag        foreach ($b_countries as $country => $count) {
15888de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
1596ccdf4f0SGreg Roach            if (array_key_exists($country, $this->country_to_iso3166)) {
16088de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$country];
16188de55fdSRico Sonntag
1626ccdf4f0SGreg Roach                if (array_key_exists($country_code, $surn_countries)) {
16388de55fdSRico Sonntag                    $surn_countries[$country_code] += $count;
16488de55fdSRico Sonntag                } else {
16588de55fdSRico Sonntag                    $surn_countries[$country_code] = $count;
16688de55fdSRico Sonntag                }
16788de55fdSRico Sonntag            }
16888de55fdSRico Sonntag        }
16988de55fdSRico Sonntag
17088de55fdSRico Sonntag        return $this->createChartData($surn_countries);
17188de55fdSRico Sonntag    }
17288de55fdSRico Sonntag
17388de55fdSRico Sonntag    /**
17488de55fdSRico Sonntag     * Returns the google geochart data for death fact.
17588de55fdSRico Sonntag     *
17688de55fdSRico Sonntag     * @return array
17788de55fdSRico Sonntag     */
17888de55fdSRico Sonntag    private function getDeathChartData(): array
17988de55fdSRico Sonntag    {
18088de55fdSRico Sonntag        // Count how many people were death in each country
18188de55fdSRico Sonntag        $surn_countries = [];
18288de55fdSRico Sonntag        $d_countries    = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true);
18388de55fdSRico Sonntag
18488de55fdSRico Sonntag        foreach ($d_countries as $country => $count) {
18588de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
1866ccdf4f0SGreg Roach            if (array_key_exists($country, $this->country_to_iso3166)) {
18788de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$country];
18888de55fdSRico Sonntag
1896ccdf4f0SGreg Roach                if (array_key_exists($country_code, $surn_countries)) {
19088de55fdSRico Sonntag                    $surn_countries[$country_code] += $count;
19188de55fdSRico Sonntag                } else {
19288de55fdSRico Sonntag                    $surn_countries[$country_code] = $count;
19388de55fdSRico Sonntag                }
19488de55fdSRico Sonntag            }
19588de55fdSRico Sonntag        }
19688de55fdSRico Sonntag
19788de55fdSRico Sonntag        return $this->createChartData($surn_countries);
19888de55fdSRico Sonntag    }
19988de55fdSRico Sonntag
20088de55fdSRico Sonntag    /**
20188de55fdSRico Sonntag     * Returns the google geochart data for marriages.
20288de55fdSRico Sonntag     *
20388de55fdSRico Sonntag     * @return array
20488de55fdSRico Sonntag     */
20588de55fdSRico Sonntag    private function getMarriageChartData(): array
20688de55fdSRico Sonntag    {
20788de55fdSRico Sonntag        // Count how many families got marriage in each country
20888de55fdSRico Sonntag        $surn_countries = [];
20988de55fdSRico Sonntag        $m_countries    = $this->placeRepository->statsPlaces('FAM');
21088de55fdSRico Sonntag
21188de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
21288de55fdSRico Sonntag        foreach ($m_countries as $place) {
21388de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
2146ccdf4f0SGreg Roach            if (array_key_exists($place->country, $this->country_to_iso3166)) {
21588de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$place->country];
21688de55fdSRico Sonntag
2176ccdf4f0SGreg Roach                if (array_key_exists($country_code, $surn_countries)) {
21888de55fdSRico Sonntag                    $surn_countries[$country_code] += $place->tot;
21988de55fdSRico Sonntag                } else {
22088de55fdSRico Sonntag                    $surn_countries[$country_code] = $place->tot;
22188de55fdSRico Sonntag                }
22288de55fdSRico Sonntag            }
22388de55fdSRico Sonntag        }
22488de55fdSRico Sonntag
22588de55fdSRico Sonntag        return $this->createChartData($surn_countries);
22688de55fdSRico Sonntag    }
22788de55fdSRico Sonntag
22888de55fdSRico Sonntag    /**
22988de55fdSRico Sonntag     * Returns the related database records.
23088de55fdSRico Sonntag     *
23188de55fdSRico Sonntag     * @param string $surname
23288de55fdSRico Sonntag     *
233*870ec5a3SGreg Roach     * @return array<stdClass>
23488de55fdSRico Sonntag     */
23588de55fdSRico Sonntag    private function queryRecords(string $surname): array
23688de55fdSRico Sonntag    {
23788de55fdSRico Sonntag        $query = DB::table('individuals')
23888de55fdSRico Sonntag            ->select(['i_gedcom'])
2390b5fd0a6SGreg Roach            ->join('name', static function (JoinClause $join): void {
24088de55fdSRico Sonntag                $join->on('n_id', '=', 'i_id')
24188de55fdSRico Sonntag                    ->on('n_file', '=', 'i_file');
24288de55fdSRico Sonntag            })
24388de55fdSRico Sonntag            ->where('n_file', '=', $this->tree->id())
244a69f5655SGreg Roach            ->where(new Expression('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname);
24588de55fdSRico Sonntag
24688de55fdSRico Sonntag        return $query->get()->all();
24788de55fdSRico Sonntag    }
24888de55fdSRico Sonntag
24988de55fdSRico Sonntag    /**
25088de55fdSRico Sonntag     * Returns the google geochart data for surnames.
25188de55fdSRico Sonntag     *
25288de55fdSRico Sonntag     * @param string $surname The surname used to create the chart
25388de55fdSRico Sonntag     *
25488de55fdSRico Sonntag     * @return array
25588de55fdSRico Sonntag     */
25688de55fdSRico Sonntag    private function getSurnameChartData(string $surname): array
25788de55fdSRico Sonntag    {
25888de55fdSRico Sonntag        if ($surname === '') {
25988de55fdSRico Sonntag            $surname = $this->individualRepository->getCommonSurname();
26088de55fdSRico Sonntag        }
26188de55fdSRico Sonntag
26288de55fdSRico Sonntag        // Count how many people are events in each country
26388de55fdSRico Sonntag        $surn_countries = [];
26488de55fdSRico Sonntag        $records        = $this->queryRecords($surname);
26588de55fdSRico Sonntag
26688de55fdSRico Sonntag        foreach ($records as $row) {
26788de55fdSRico Sonntag            /** @var string[][] $matches */
26888de55fdSRico Sonntag            if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) {
26988de55fdSRico Sonntag                // webtrees uses 3 letter country codes and localised country names,
27088de55fdSRico Sonntag                // but google uses 2 letter codes.
27188de55fdSRico Sonntag                foreach ($matches[1] as $country) {
27288de55fdSRico Sonntag                    // Consolidate places (Germany, DEU => DE)
2736ccdf4f0SGreg Roach                    if (array_key_exists($country, $this->country_to_iso3166)) {
27488de55fdSRico Sonntag                        $country_code = $this->country_to_iso3166[$country];
27588de55fdSRico Sonntag
2766ccdf4f0SGreg Roach                        if (array_key_exists($country_code, $surn_countries)) {
27788de55fdSRico Sonntag                            $surn_countries[$country_code]++;
27888de55fdSRico Sonntag                        } else {
27988de55fdSRico Sonntag                            $surn_countries[$country_code] = 1;
28088de55fdSRico Sonntag                        }
28188de55fdSRico Sonntag                    }
28288de55fdSRico Sonntag                }
28388de55fdSRico Sonntag            }
28488de55fdSRico Sonntag        }
28588de55fdSRico Sonntag
28688de55fdSRico Sonntag        return $this->createChartData($surn_countries);
28788de55fdSRico Sonntag    }
28888de55fdSRico Sonntag
28988de55fdSRico Sonntag    /**
29088de55fdSRico Sonntag     * Returns the google geochart data for individuals.
29188de55fdSRico Sonntag     *
29288de55fdSRico Sonntag     * @return array
29388de55fdSRico Sonntag     */
29488de55fdSRico Sonntag    private function getIndivdualChartData(): array
29588de55fdSRico Sonntag    {
29688de55fdSRico Sonntag        // Count how many people have events in each country
29788de55fdSRico Sonntag        $surn_countries = [];
29888de55fdSRico Sonntag        $a_countries    = $this->placeRepository->statsPlaces('INDI');
29988de55fdSRico Sonntag
30088de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
30188de55fdSRico Sonntag        foreach ($a_countries as $place) {
30288de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
3036ccdf4f0SGreg Roach            if (array_key_exists($place->country, $this->country_to_iso3166)) {
30488de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$place->country];
30588de55fdSRico Sonntag
3066ccdf4f0SGreg Roach                if (array_key_exists($country_code, $surn_countries)) {
30788de55fdSRico Sonntag                    $surn_countries[$country_code] += $place->tot;
30888de55fdSRico Sonntag                } else {
30988de55fdSRico Sonntag                    $surn_countries[$country_code] = $place->tot;
31088de55fdSRico Sonntag                }
31188de55fdSRico Sonntag            }
31288de55fdSRico Sonntag        }
31388de55fdSRico Sonntag
31488de55fdSRico Sonntag        return $this->createChartData($surn_countries);
3158add1155SRico Sonntag    }
3168add1155SRico Sonntag
3178add1155SRico Sonntag    /**
3188add1155SRico Sonntag     * Create a chart showing where events occurred.
3198add1155SRico Sonntag     *
32088de55fdSRico Sonntag     * @param string $chart_shows The type of chart map to show
32188de55fdSRico Sonntag     * @param string $chart_type  The type of chart to show
32288de55fdSRico Sonntag     * @param string $surname     The surname for surname based distribution chart
3238add1155SRico Sonntag     *
3248add1155SRico Sonntag     * @return string
3258add1155SRico Sonntag     */
3268add1155SRico Sonntag    public function chartDistribution(
3278add1155SRico Sonntag        string $chart_shows = 'world',
3288add1155SRico Sonntag        string $chart_type = '',
3298add1155SRico Sonntag        string $surname = ''
3308add1155SRico Sonntag    ): string {
3318add1155SRico Sonntag        switch ($chart_type) {
3328add1155SRico Sonntag            case 'surname_distribution_chart':
3338add1155SRico Sonntag                $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname;
33488de55fdSRico Sonntag                $data        = $this->getSurnameChartData($surname);
3358add1155SRico Sonntag                break;
3368add1155SRico Sonntag
3378add1155SRico Sonntag            case 'birth_distribution_chart':
3388add1155SRico Sonntag                $chart_title = I18N::translate('Birth by country');
33988de55fdSRico Sonntag                $data        = $this->getBirthChartData();
3408add1155SRico Sonntag                break;
3418add1155SRico Sonntag
3428add1155SRico Sonntag            case 'death_distribution_chart':
3438add1155SRico Sonntag                $chart_title = I18N::translate('Death by country');
34488de55fdSRico Sonntag                $data        = $this->getDeathChartData();
3458add1155SRico Sonntag                break;
3468add1155SRico Sonntag
3478add1155SRico Sonntag            case 'marriage_distribution_chart':
3488add1155SRico Sonntag                $chart_title = I18N::translate('Marriage by country');
34988de55fdSRico Sonntag                $data        = $this->getMarriageChartData();
3508add1155SRico Sonntag                break;
3518add1155SRico Sonntag
3528add1155SRico Sonntag            case 'indi_distribution_chart':
3538add1155SRico Sonntag            default:
3548add1155SRico Sonntag                $chart_title = I18N::translate('Individual distribution chart');
35588de55fdSRico Sonntag                $data        = $this->getIndivdualChartData();
3568add1155SRico Sonntag                break;
3578add1155SRico Sonntag        }
3588add1155SRico Sonntag
35988de55fdSRico Sonntag        $chart_color2 = $this->theme->parameter('distribution-chart-high-values');
36088de55fdSRico Sonntag        $chart_color3 = $this->theme->parameter('distribution-chart-low-values');
3618add1155SRico Sonntag
36290a2f718SGreg Roach        return view('statistics/other/charts/geo', [
3638add1155SRico Sonntag            'chart_title'  => $chart_title,
3648add1155SRico Sonntag            'chart_color2' => $chart_color2,
3658add1155SRico Sonntag            'chart_color3' => $chart_color3,
36688de55fdSRico Sonntag            'region'       => $chart_shows,
36788de55fdSRico Sonntag            'data'         => $data,
36865cf5706SGreg Roach            'language'     => I18N::languageTag(),
36990a2f718SGreg Roach        ]);
3708add1155SRico Sonntag    }
3718add1155SRico Sonntag}
372