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