xref: /webtrees/app/Statistics/Google/ChartDistribution.php (revision 22d65e5ad7724941da33d875027b68b86648a321)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees\Statistics\Google;
19
20use function array_key_exists;
21use Fisharebest\Webtrees\I18N;
22use Fisharebest\Webtrees\Module\ModuleThemeInterface;
23use Fisharebest\Webtrees\Statistics\Repository\IndividualRepository;
24use Fisharebest\Webtrees\Statistics\Repository\PlaceRepository;
25use Fisharebest\Webtrees\Statistics\Service\CountryService;
26use Fisharebest\Webtrees\Tree;
27use Illuminate\Database\Capsule\Manager as DB;
28use Illuminate\Database\Query\JoinClause;
29use stdClass;
30
31/**
32 * A chart showing the distribution of different events on a map.
33 */
34class ChartDistribution
35{
36    /**
37     * @var Tree
38     */
39    private $tree;
40
41    /**
42     * @var ModuleThemeInterface
43     */
44    private $theme;
45
46    /**
47     * @var CountryService
48     */
49    private $country_service;
50
51    /**
52     * @var IndividualRepository
53     */
54    private $individualRepository;
55
56    /**
57     * @var PlaceRepository
58     */
59    private $placeRepository;
60
61    /**
62     * @var string[]
63     */
64    private $country_to_iso3166;
65
66    /**
67     * Constructor.
68     *
69     * @param Tree $tree
70     */
71    public function __construct(Tree $tree)
72    {
73        $this->tree                 = $tree;
74        $this->theme                = app(ModuleThemeInterface::class);
75        $this->country_service      = new CountryService();
76        $this->individualRepository = new IndividualRepository($tree);
77        $this->placeRepository      = new PlaceRepository($tree);
78
79        // Get the country names for each language
80        $this->country_to_iso3166 = $this->getIso3166Countries();
81    }
82
83    /**
84     * Returns the country names for each language.
85     *
86     * @return string[]
87     */
88    private function getIso3166Countries(): array
89    {
90        $countries = $this->country_service->getAllCountries();
91
92        // Get the country names for each language
93        $country_to_iso3166 = [];
94
95        foreach (I18N::activeLocales() as $locale) {
96            I18N::init($locale->languageTag());
97
98            foreach ($this->country_service->iso3166() as $three => $two) {
99                $country_to_iso3166[$three]             = $two;
100                $country_to_iso3166[$countries[$three]] = $two;
101            }
102        }
103
104        return $country_to_iso3166;
105    }
106
107    /**
108     * Returns the data structure required by google geochart.
109     *
110     * @param array $places
111     *
112     * @return array
113     */
114    private function createChartData(array $places): array
115    {
116        $data = [
117            [
118                I18N::translate('Country'),
119                I18N::translate('Total'),
120            ],
121        ];
122
123        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
124        foreach ($places as $country => $count) {
125            $data[] = [
126                [
127                    'v' => $country,
128                    'f' => $this->country_service->mapTwoLetterToName($country),
129                ],
130                $count
131            ];
132        }
133
134        return $data;
135    }
136
137    /**
138     * Returns the google geochart data for birth fact.
139     *
140     * @return array
141     */
142    private function getBirthChartData(): array
143    {
144        // Count how many people were born in each country
145        $surn_countries = [];
146        $b_countries    = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true);
147
148        foreach ($b_countries as $country => $count) {
149            // Consolidate places (Germany, DEU => DE)
150            if (array_key_exists($country, $this->country_to_iso3166)) {
151                $country_code = $this->country_to_iso3166[$country];
152
153                if (array_key_exists($country_code, $surn_countries)) {
154                    $surn_countries[$country_code] += $count;
155                } else {
156                    $surn_countries[$country_code] = $count;
157                }
158            }
159        }
160
161        return $this->createChartData($surn_countries);
162    }
163
164    /**
165     * Returns the google geochart data for death fact.
166     *
167     * @return array
168     */
169    private function getDeathChartData(): array
170    {
171        // Count how many people were death in each country
172        $surn_countries = [];
173        $d_countries    = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true);
174
175        foreach ($d_countries as $country => $count) {
176            // Consolidate places (Germany, DEU => DE)
177            if (array_key_exists($country, $this->country_to_iso3166)) {
178                $country_code = $this->country_to_iso3166[$country];
179
180                if (array_key_exists($country_code, $surn_countries)) {
181                    $surn_countries[$country_code] += $count;
182                } else {
183                    $surn_countries[$country_code] = $count;
184                }
185            }
186        }
187
188        return $this->createChartData($surn_countries);
189    }
190
191    /**
192     * Returns the google geochart data for marriages.
193     *
194     * @return array
195     */
196    private function getMarriageChartData(): array
197    {
198        // Count how many families got marriage in each country
199        $surn_countries = [];
200        $m_countries    = $this->placeRepository->statsPlaces('FAM');
201
202        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
203        foreach ($m_countries as $place) {
204            // Consolidate places (Germany, DEU => DE)
205            if (array_key_exists($place->country, $this->country_to_iso3166)) {
206                $country_code = $this->country_to_iso3166[$place->country];
207
208                if (array_key_exists($country_code, $surn_countries)) {
209                    $surn_countries[$country_code] += $place->tot;
210                } else {
211                    $surn_countries[$country_code] = $place->tot;
212                }
213            }
214        }
215
216        return $this->createChartData($surn_countries);
217    }
218
219    /**
220     * Returns the related database records.
221     *
222     * @param string $surname
223     *
224     * @return stdClass[]
225     */
226    private function queryRecords(string $surname): array
227    {
228        $query = DB::table('individuals')
229            ->select(['i_gedcom'])
230            ->join('name', static function (JoinClause $join): void {
231                $join->on('n_id', '=', 'i_id')
232                    ->on('n_file', '=', 'i_file');
233            })
234            ->where('n_file', '=', $this->tree->id())
235            ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname);
236
237        return $query->get()->all();
238    }
239
240    /**
241     * Returns the google geochart data for surnames.
242     *
243     * @param string $surname The surname used to create the chart
244     *
245     * @return array
246     */
247    private function getSurnameChartData(string $surname): array
248    {
249        if ($surname === '') {
250            $surname = $this->individualRepository->getCommonSurname();
251        }
252
253        // Count how many people are events in each country
254        $surn_countries = [];
255        $records        = $this->queryRecords($surname);
256
257        foreach ($records as $row) {
258            /** @var string[][] $matches */
259            if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) {
260                // webtrees uses 3 letter country codes and localised country names,
261                // but google uses 2 letter codes.
262                foreach ($matches[1] as $country) {
263                    // Consolidate places (Germany, DEU => DE)
264                    if (array_key_exists($country, $this->country_to_iso3166)) {
265                        $country_code = $this->country_to_iso3166[$country];
266
267                        if (array_key_exists($country_code, $surn_countries)) {
268                            $surn_countries[$country_code]++;
269                        } else {
270                            $surn_countries[$country_code] = 1;
271                        }
272                    }
273                }
274            }
275        }
276
277        return $this->createChartData($surn_countries);
278    }
279
280    /**
281     * Returns the google geochart data for individuals.
282     *
283     * @return array
284     */
285    private function getIndivdualChartData(): array
286    {
287        // Count how many people have events in each country
288        $surn_countries = [];
289        $a_countries    = $this->placeRepository->statsPlaces('INDI');
290
291        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
292        foreach ($a_countries as $place) {
293            // Consolidate places (Germany, DEU => DE)
294            if (array_key_exists($place->country, $this->country_to_iso3166)) {
295                $country_code = $this->country_to_iso3166[$place->country];
296
297                if (array_key_exists($country_code, $surn_countries)) {
298                    $surn_countries[$country_code] += $place->tot;
299                } else {
300                    $surn_countries[$country_code] = $place->tot;
301                }
302            }
303        }
304
305        return $this->createChartData($surn_countries);
306    }
307
308    /**
309     * Create a chart showing where events occurred.
310     *
311     * @param string $chart_shows The type of chart map to show
312     * @param string $chart_type  The type of chart to show
313     * @param string $surname     The surname for surname based distribution chart
314     *
315     * @return string
316     */
317    public function chartDistribution(
318        string $chart_shows = 'world',
319        string $chart_type = '',
320        string $surname = ''
321    ): string {
322        I18N::init(WT_LOCALE);
323
324        switch ($chart_type) {
325            case 'surname_distribution_chart':
326                $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname;
327                $data        = $this->getSurnameChartData($surname);
328                break;
329
330            case 'birth_distribution_chart':
331                $chart_title = I18N::translate('Birth by country');
332                $data        = $this->getBirthChartData();
333                break;
334
335            case 'death_distribution_chart':
336                $chart_title = I18N::translate('Death by country');
337                $data        = $this->getDeathChartData();
338                break;
339
340            case 'marriage_distribution_chart':
341                $chart_title = I18N::translate('Marriage by country');
342                $data        = $this->getMarriageChartData();
343                break;
344
345            case 'indi_distribution_chart':
346            default:
347                $chart_title = I18N::translate('Individual distribution chart');
348                $data        = $this->getIndivdualChartData();
349                break;
350        }
351
352        $chart_color2 = $this->theme->parameter('distribution-chart-high-values');
353        $chart_color3 = $this->theme->parameter('distribution-chart-low-values');
354
355        return view(
356            'statistics/other/charts/geo',
357            [
358                'chart_title'  => $chart_title,
359                'chart_color2' => $chart_color2,
360                'chart_color3' => $chart_color3,
361                'region'       => $chart_shows,
362                'data'         => $data,
363            ]
364        );
365    }
366}
367