xref: /webtrees/app/Statistics/Google/ChartDistribution.php (revision 242a78626998a767db1568b24919e94ae4b38ac3)
18add1155SRico Sonntag<?php
28add1155SRico Sonntag/**
38add1155SRico Sonntag * webtrees: online genealogy
4*242a7862SGreg 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;
218add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\AbstractGoogle;
2288de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Helper\Country;
238add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\IndividualRepository;
2488de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\PlaceRepository;
258add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
2688de55fdSRico Sonntaguse Illuminate\Database\Capsule\Manager as DB;
2788de55fdSRico Sonntaguse Illuminate\Database\Query\JoinClause;
288add1155SRico Sonntag
298add1155SRico Sonntag/**
308add1155SRico Sonntag * Create a chart showing where events occurred.
318add1155SRico Sonntag */
328add1155SRico Sonntagclass ChartDistribution extends AbstractGoogle
338add1155SRico Sonntag{
348add1155SRico Sonntag    /**
358add1155SRico Sonntag     * @var Country
368add1155SRico Sonntag     */
378add1155SRico Sonntag    private $countryHelper;
388add1155SRico Sonntag
398add1155SRico Sonntag    /**
408add1155SRico Sonntag     * @var IndividualRepository
418add1155SRico Sonntag     */
428add1155SRico Sonntag    private $individualRepository;
438add1155SRico Sonntag
448add1155SRico Sonntag    /**
458add1155SRico Sonntag     * @var PlaceRepository
468add1155SRico Sonntag     */
478add1155SRico Sonntag    private $placeRepository;
488add1155SRico Sonntag
498add1155SRico Sonntag    /**
5088de55fdSRico Sonntag     * @var string[]
5188de55fdSRico Sonntag     */
5288de55fdSRico Sonntag    private $country_to_iso3166;
5388de55fdSRico Sonntag
5488de55fdSRico Sonntag    /**
558add1155SRico Sonntag     * Constructor.
568add1155SRico Sonntag     *
578add1155SRico Sonntag     * @param Tree $tree
588add1155SRico Sonntag     */
598add1155SRico Sonntag    public function __construct(Tree $tree)
608add1155SRico Sonntag    {
6188de55fdSRico Sonntag        parent::__construct($tree);
6288de55fdSRico Sonntag
638add1155SRico Sonntag        $this->countryHelper        = new Country();
648add1155SRico Sonntag        $this->individualRepository = new IndividualRepository($tree);
658add1155SRico Sonntag        $this->placeRepository      = new PlaceRepository($tree);
6688de55fdSRico Sonntag
6788de55fdSRico Sonntag        // Get the country names for each language
6888de55fdSRico Sonntag        $this->country_to_iso3166 = $this->getIso3166Countries();
6988de55fdSRico Sonntag    }
7088de55fdSRico Sonntag
7188de55fdSRico Sonntag    /**
7288de55fdSRico Sonntag     * Returns the country names for each language.
7388de55fdSRico Sonntag     *
7488de55fdSRico Sonntag     * @return string[]
7588de55fdSRico Sonntag     */
7688de55fdSRico Sonntag    private function getIso3166Countries(): array
7788de55fdSRico Sonntag    {
7888de55fdSRico Sonntag        $countries = $this->countryHelper->getAllCountries();
7988de55fdSRico Sonntag
8088de55fdSRico Sonntag        // Get the country names for each language
8188de55fdSRico Sonntag        $this->country_to_iso3166 = [];
8288de55fdSRico Sonntag
8388de55fdSRico Sonntag        foreach (I18N::activeLocales() as $locale) {
8488de55fdSRico Sonntag            I18N::init($locale->languageTag());
8588de55fdSRico Sonntag
8688de55fdSRico Sonntag            foreach ($this->countryHelper->iso3166() as $three => $two) {
8788de55fdSRico Sonntag                $this->country_to_iso3166[$three]             = $two;
8888de55fdSRico Sonntag                $this->country_to_iso3166[$countries[$three]] = $two;
8988de55fdSRico Sonntag            }
9088de55fdSRico Sonntag        }
9188de55fdSRico Sonntag
9288de55fdSRico Sonntag        return $this->country_to_iso3166;
9388de55fdSRico Sonntag    }
9488de55fdSRico Sonntag
9588de55fdSRico Sonntag    /**
9688de55fdSRico Sonntag     * Returns the data structure required by google geochart.
9788de55fdSRico Sonntag     *
9888de55fdSRico Sonntag     * @param array $places
9988de55fdSRico Sonntag     *
10088de55fdSRico Sonntag     * @return array
10188de55fdSRico Sonntag     */
10288de55fdSRico Sonntag    private function createChartData(array $places): array
10388de55fdSRico Sonntag    {
10488de55fdSRico Sonntag        $data = [
10588de55fdSRico Sonntag            [
10688de55fdSRico Sonntag                I18N::translate('Country'),
10788de55fdSRico Sonntag                I18N::translate('Total'),
10888de55fdSRico Sonntag            ],
10988de55fdSRico Sonntag        ];
11088de55fdSRico Sonntag
11188de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
11288de55fdSRico Sonntag        foreach ($places as $country => $count) {
11388de55fdSRico Sonntag            $data[] = [
11488de55fdSRico Sonntag                [
11588de55fdSRico Sonntag                    'v' => $country,
11688de55fdSRico Sonntag                    'f' => $this->countryHelper->mapTwoLetterToName($country),
11788de55fdSRico Sonntag                ],
11888de55fdSRico Sonntag                $count
11988de55fdSRico Sonntag            ];
12088de55fdSRico Sonntag        }
12188de55fdSRico Sonntag
12288de55fdSRico Sonntag        return $data;
12388de55fdSRico Sonntag    }
12488de55fdSRico Sonntag
12588de55fdSRico Sonntag    /**
12688de55fdSRico Sonntag     * Returns the google geochart data for birth fact.
12788de55fdSRico Sonntag     *
12888de55fdSRico Sonntag     * @return array
12988de55fdSRico Sonntag     */
13088de55fdSRico Sonntag    private function getBirthChartData(): array
13188de55fdSRico Sonntag    {
13288de55fdSRico Sonntag        // Count how many people were born in each country
13388de55fdSRico Sonntag        $surn_countries = [];
13488de55fdSRico Sonntag        $b_countries    = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true);
13588de55fdSRico Sonntag
13688de55fdSRico Sonntag        foreach ($b_countries as $country => $count) {
13788de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
13888de55fdSRico Sonntag            if (\array_key_exists($country, $this->country_to_iso3166)) {
13988de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$country];
14088de55fdSRico Sonntag
14188de55fdSRico Sonntag                if (\array_key_exists($country_code, $surn_countries)) {
14288de55fdSRico Sonntag                    $surn_countries[$country_code] += $count;
14388de55fdSRico Sonntag                } else {
14488de55fdSRico Sonntag                    $surn_countries[$country_code] = $count;
14588de55fdSRico Sonntag                }
14688de55fdSRico Sonntag            }
14788de55fdSRico Sonntag        }
14888de55fdSRico Sonntag
14988de55fdSRico Sonntag        return $this->createChartData($surn_countries);
15088de55fdSRico Sonntag    }
15188de55fdSRico Sonntag
15288de55fdSRico Sonntag    /**
15388de55fdSRico Sonntag     * Returns the google geochart data for death fact.
15488de55fdSRico Sonntag     *
15588de55fdSRico Sonntag     * @return array
15688de55fdSRico Sonntag     */
15788de55fdSRico Sonntag    private function getDeathChartData(): array
15888de55fdSRico Sonntag    {
15988de55fdSRico Sonntag        // Count how many people were death in each country
16088de55fdSRico Sonntag        $surn_countries = [];
16188de55fdSRico Sonntag        $d_countries    = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true);
16288de55fdSRico Sonntag
16388de55fdSRico Sonntag        foreach ($d_countries as $country => $count) {
16488de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
16588de55fdSRico Sonntag            if (\array_key_exists($country, $this->country_to_iso3166)) {
16688de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$country];
16788de55fdSRico Sonntag
16888de55fdSRico Sonntag                if (\array_key_exists($country_code, $surn_countries)) {
16988de55fdSRico Sonntag                    $surn_countries[$country_code] += $count;
17088de55fdSRico Sonntag                } else {
17188de55fdSRico Sonntag                    $surn_countries[$country_code] = $count;
17288de55fdSRico Sonntag                }
17388de55fdSRico Sonntag            }
17488de55fdSRico Sonntag        }
17588de55fdSRico Sonntag
17688de55fdSRico Sonntag        return $this->createChartData($surn_countries);
17788de55fdSRico Sonntag    }
17888de55fdSRico Sonntag
17988de55fdSRico Sonntag    /**
18088de55fdSRico Sonntag     * Returns the google geochart data for marriages.
18188de55fdSRico Sonntag     *
18288de55fdSRico Sonntag     * @return array
18388de55fdSRico Sonntag     */
18488de55fdSRico Sonntag    private function getMarriageChartData(): array
18588de55fdSRico Sonntag    {
18688de55fdSRico Sonntag        // Count how many families got marriage in each country
18788de55fdSRico Sonntag        $surn_countries = [];
18888de55fdSRico Sonntag        $m_countries    = $this->placeRepository->statsPlaces('FAM');
18988de55fdSRico Sonntag
19088de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
19188de55fdSRico Sonntag        foreach ($m_countries as $place) {
19288de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
19388de55fdSRico Sonntag            if (\array_key_exists($place->country, $this->country_to_iso3166)) {
19488de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$place->country];
19588de55fdSRico Sonntag
19688de55fdSRico Sonntag                if (\array_key_exists($country_code, $surn_countries)) {
19788de55fdSRico Sonntag                    $surn_countries[$country_code] += $place->tot;
19888de55fdSRico Sonntag                } else {
19988de55fdSRico Sonntag                    $surn_countries[$country_code] = $place->tot;
20088de55fdSRico Sonntag                }
20188de55fdSRico Sonntag            }
20288de55fdSRico Sonntag        }
20388de55fdSRico Sonntag
20488de55fdSRico Sonntag        return $this->createChartData($surn_countries);
20588de55fdSRico Sonntag    }
20688de55fdSRico Sonntag
20788de55fdSRico Sonntag    /**
20888de55fdSRico Sonntag     * Returns the related database records.
20988de55fdSRico Sonntag     *
21088de55fdSRico Sonntag     * @param string $surname
21188de55fdSRico Sonntag     *
21288de55fdSRico Sonntag     * @return \stdClass[]
21388de55fdSRico Sonntag     */
21488de55fdSRico Sonntag    private function queryRecords(string $surname): array
21588de55fdSRico Sonntag    {
21688de55fdSRico Sonntag        $query = DB::table('individuals')
21788de55fdSRico Sonntag            ->select(['i_gedcom'])
21888de55fdSRico Sonntag            ->join('name', function (JoinClause $join) {
21988de55fdSRico Sonntag                $join->on('n_id', '=', 'i_id')
22088de55fdSRico Sonntag                    ->on('n_file', '=', 'i_file');
22188de55fdSRico Sonntag            })
22288de55fdSRico Sonntag            ->where('n_file', '=', $this->tree->id())
22388de55fdSRico Sonntag            ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname);
22488de55fdSRico Sonntag
22588de55fdSRico Sonntag        return $query->get()->all();
22688de55fdSRico Sonntag    }
22788de55fdSRico Sonntag
22888de55fdSRico Sonntag    /**
22988de55fdSRico Sonntag     * Returns the google geochart data for surnames.
23088de55fdSRico Sonntag     *
23188de55fdSRico Sonntag     * @param string $surname The surname used to create the chart
23288de55fdSRico Sonntag     *
23388de55fdSRico Sonntag     * @return array
23488de55fdSRico Sonntag     */
23588de55fdSRico Sonntag    private function getSurnameChartData(string $surname): array
23688de55fdSRico Sonntag    {
23788de55fdSRico Sonntag        if ($surname === '') {
23888de55fdSRico Sonntag            $surname = $this->individualRepository->getCommonSurname();
23988de55fdSRico Sonntag        }
24088de55fdSRico Sonntag
24188de55fdSRico Sonntag        // Count how many people are events in each country
24288de55fdSRico Sonntag        $surn_countries = [];
24388de55fdSRico Sonntag        $records        = $this->queryRecords($surname);
24488de55fdSRico Sonntag
24588de55fdSRico Sonntag        foreach ($records as $row) {
24688de55fdSRico Sonntag            /** @var string[][] $matches */
24788de55fdSRico Sonntag            if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) {
24888de55fdSRico Sonntag                // webtrees uses 3 letter country codes and localised country names,
24988de55fdSRico Sonntag                // but google uses 2 letter codes.
25088de55fdSRico Sonntag                foreach ($matches[1] as $country) {
25188de55fdSRico Sonntag                    // Consolidate places (Germany, DEU => DE)
25288de55fdSRico Sonntag                    if (\array_key_exists($country, $this->country_to_iso3166)) {
25388de55fdSRico Sonntag                        $country_code = $this->country_to_iso3166[$country];
25488de55fdSRico Sonntag
25588de55fdSRico Sonntag                        if (\array_key_exists($country_code, $surn_countries)) {
25688de55fdSRico Sonntag                            $surn_countries[$country_code]++;
25788de55fdSRico Sonntag                        } else {
25888de55fdSRico Sonntag                            $surn_countries[$country_code] = 1;
25988de55fdSRico Sonntag                        }
26088de55fdSRico Sonntag                    }
26188de55fdSRico Sonntag                }
26288de55fdSRico Sonntag            }
26388de55fdSRico Sonntag        }
26488de55fdSRico Sonntag
26588de55fdSRico Sonntag        return $this->createChartData($surn_countries);
26688de55fdSRico Sonntag    }
26788de55fdSRico Sonntag
26888de55fdSRico Sonntag    /**
26988de55fdSRico Sonntag     * Returns the google geochart data for individuals.
27088de55fdSRico Sonntag     *
27188de55fdSRico Sonntag     * @return array
27288de55fdSRico Sonntag     */
27388de55fdSRico Sonntag    private function getIndivdualChartData(): array
27488de55fdSRico Sonntag    {
27588de55fdSRico Sonntag        // Count how many people have events in each country
27688de55fdSRico Sonntag        $surn_countries = [];
27788de55fdSRico Sonntag        $a_countries    = $this->placeRepository->statsPlaces('INDI');
27888de55fdSRico Sonntag
27988de55fdSRico Sonntag        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
28088de55fdSRico Sonntag        foreach ($a_countries as $place) {
28188de55fdSRico Sonntag            // Consolidate places (Germany, DEU => DE)
28288de55fdSRico Sonntag            if (\array_key_exists($place->country, $this->country_to_iso3166)) {
28388de55fdSRico Sonntag                $country_code = $this->country_to_iso3166[$place->country];
28488de55fdSRico Sonntag
28588de55fdSRico Sonntag                if (\array_key_exists($country_code, $surn_countries)) {
28688de55fdSRico Sonntag                    $surn_countries[$country_code] += $place->tot;
28788de55fdSRico Sonntag                } else {
28888de55fdSRico Sonntag                    $surn_countries[$country_code] = $place->tot;
28988de55fdSRico Sonntag                }
29088de55fdSRico Sonntag            }
29188de55fdSRico Sonntag        }
29288de55fdSRico Sonntag
29388de55fdSRico Sonntag        return $this->createChartData($surn_countries);
2948add1155SRico Sonntag    }
2958add1155SRico Sonntag
2968add1155SRico Sonntag    /**
2978add1155SRico Sonntag     * Create a chart showing where events occurred.
2988add1155SRico Sonntag     *
29988de55fdSRico Sonntag     * @param string $chart_shows The type of chart map to show
30088de55fdSRico Sonntag     * @param string $chart_type  The type of chart to show
30188de55fdSRico Sonntag     * @param string $surname     The surname for surname based distribution chart
3028add1155SRico Sonntag     *
3038add1155SRico Sonntag     * @return string
3048add1155SRico Sonntag     */
3058add1155SRico Sonntag    public function chartDistribution(
3068add1155SRico Sonntag        string $chart_shows = 'world',
3078add1155SRico Sonntag        string $chart_type  = '',
3088add1155SRico Sonntag        string $surname     = ''
3098add1155SRico Sonntag    ): string {
3108add1155SRico Sonntag        I18N::init(WT_LOCALE);
3118add1155SRico Sonntag
3128add1155SRico Sonntag        switch ($chart_type) {
3138add1155SRico Sonntag            case 'surname_distribution_chart':
3148add1155SRico Sonntag                $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname;
31588de55fdSRico Sonntag                $data        = $this->getSurnameChartData($surname);
3168add1155SRico Sonntag                break;
3178add1155SRico Sonntag
3188add1155SRico Sonntag            case 'birth_distribution_chart':
3198add1155SRico Sonntag                $chart_title = I18N::translate('Birth by country');
32088de55fdSRico Sonntag                $data        = $this->getBirthChartData();
3218add1155SRico Sonntag                break;
3228add1155SRico Sonntag
3238add1155SRico Sonntag            case 'death_distribution_chart':
3248add1155SRico Sonntag                $chart_title = I18N::translate('Death by country');
32588de55fdSRico Sonntag                $data        = $this->getDeathChartData();
3268add1155SRico Sonntag                break;
3278add1155SRico Sonntag
3288add1155SRico Sonntag            case 'marriage_distribution_chart':
3298add1155SRico Sonntag                $chart_title = I18N::translate('Marriage by country');
33088de55fdSRico Sonntag                $data        = $this->getMarriageChartData();
3318add1155SRico Sonntag                break;
3328add1155SRico Sonntag
3338add1155SRico Sonntag            case 'indi_distribution_chart':
3348add1155SRico Sonntag            default:
3358add1155SRico Sonntag                $chart_title = I18N::translate('Individual distribution chart');
33688de55fdSRico Sonntag                $data        = $this->getIndivdualChartData();
3378add1155SRico Sonntag                break;
3388add1155SRico Sonntag        }
3398add1155SRico Sonntag
34088de55fdSRico Sonntag        $chart_color2 = $this->theme->parameter('distribution-chart-high-values');
34188de55fdSRico Sonntag        $chart_color3 = $this->theme->parameter('distribution-chart-low-values');
3428add1155SRico Sonntag
3438add1155SRico Sonntag        return view(
34488de55fdSRico Sonntag            'statistics/other/charts/geo',
3458add1155SRico Sonntag            [
3468add1155SRico Sonntag                'chart_title'  => $chart_title,
3478add1155SRico Sonntag                'chart_color2' => $chart_color2,
3488add1155SRico Sonntag                'chart_color3' => $chart_color3,
34988de55fdSRico Sonntag                'region'       => $chart_shows,
35088de55fdSRico Sonntag                'data'         => $data,
3518add1155SRico Sonntag            ]
3528add1155SRico Sonntag        );
3538add1155SRico Sonntag    }
3548add1155SRico Sonntag}
355