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