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