xref: /webtrees/app/Module/PedigreeMapModule.php (revision 6ccdf4f0fd1b65a5d54259c969912382ce49629d)
11f374598SGreg Roach<?php
21f374598SGreg Roach/**
31f374598SGreg Roach * webtrees: online genealogy
48fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team
51f374598SGreg Roach * This program is free software: you can redistribute it and/or modify
61f374598SGreg Roach * it under the terms of the GNU General Public License as published by
71f374598SGreg Roach * the Free Software Foundation, either version 3 of the License, or
81f374598SGreg Roach * (at your option) any later version.
91f374598SGreg Roach * This program is distributed in the hope that it will be useful,
101f374598SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
111f374598SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
121f374598SGreg Roach * GNU General Public License for more details.
131f374598SGreg Roach * You should have received a copy of the GNU General Public License
141f374598SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
151f374598SGreg Roach */
161f374598SGreg Roachdeclare(strict_types=1);
171f374598SGreg Roach
181f374598SGreg Roachnamespace Fisharebest\Webtrees\Module;
191f374598SGreg Roach
201f374598SGreg Roachuse Exception;
21*6ccdf4f0SGreg Roachuse Fig\Http\Message\StatusCodeInterface;
22b6e50991SGreg Roachuse Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException;
23b6e50991SGreg Roachuse Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
241f374598SGreg Roachuse Fisharebest\Webtrees\Fact;
258af6bbf8SGreg Roachuse Fisharebest\Webtrees\Family;
268af6bbf8SGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsCharts;
278af6bbf8SGreg Roachuse Fisharebest\Webtrees\GedcomTag;
281f374598SGreg Roachuse Fisharebest\Webtrees\I18N;
291f374598SGreg Roachuse Fisharebest\Webtrees\Individual;
308af6bbf8SGreg Roachuse Fisharebest\Webtrees\Location;
311f374598SGreg Roachuse Fisharebest\Webtrees\Menu;
32aca28033SGreg Roachuse Fisharebest\Webtrees\Services\ChartService;
331f374598SGreg Roachuse Fisharebest\Webtrees\Tree;
348d0ebef0SGreg Roachuse Fisharebest\Webtrees\Webtrees;
35*6ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
36*6ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
371f374598SGreg Roach
381f374598SGreg Roach/**
391f374598SGreg Roach * Class PedigreeMapModule
401f374598SGreg Roach */
4137eb8894SGreg Roachclass PedigreeMapModule extends AbstractModule implements ModuleChartInterface
421f374598SGreg Roach{
4349a243cbSGreg Roach    use ModuleChartTrait;
4449a243cbSGreg Roach
45e759aebbSGreg Roach    // Defaults
46e759aebbSGreg Roach    public const DEFAULT_GENERATIONS = '4';
47e759aebbSGreg Roach
48e759aebbSGreg Roach    // Limits
49e759aebbSGreg Roach    public const MAXIMUM_GENERATIONS = 10;
50e759aebbSGreg Roach
5116d6367aSGreg Roach    private const LINE_COLORS = [
521f374598SGreg Roach        '#FF0000',
531f374598SGreg Roach        // Red
541f374598SGreg Roach        '#00FF00',
551f374598SGreg Roach        // Green
561f374598SGreg Roach        '#0000FF',
571f374598SGreg Roach        // Blue
581f374598SGreg Roach        '#FFB300',
591f374598SGreg Roach        // Gold
601f374598SGreg Roach        '#00FFFF',
611f374598SGreg Roach        // Cyan
621f374598SGreg Roach        '#FF00FF',
631f374598SGreg Roach        // Purple
641f374598SGreg Roach        '#7777FF',
651f374598SGreg Roach        // Light blue
661f374598SGreg Roach        '#80FF80'
671f374598SGreg Roach        // Light green
681f374598SGreg Roach    ];
691f374598SGreg Roach
701f374598SGreg Roach    private static $map_providers  = null;
711f374598SGreg Roach    private static $map_selections = null;
721f374598SGreg Roach
73961ec755SGreg Roach    /**
740cfd6963SGreg Roach     * How should this module be identified in the control panel, etc.?
75961ec755SGreg Roach     *
76961ec755SGreg Roach     * @return string
77961ec755SGreg Roach     */
7849a243cbSGreg Roach    public function title(): string
791f374598SGreg Roach    {
80bbb76c12SGreg Roach        /* I18N: Name of a module */
81bbb76c12SGreg Roach        return I18N::translate('Pedigree map');
821f374598SGreg Roach    }
831f374598SGreg Roach
84961ec755SGreg Roach    /**
85961ec755SGreg Roach     * A sentence describing what this module does.
86961ec755SGreg Roach     *
87961ec755SGreg Roach     * @return string
88961ec755SGreg Roach     */
8949a243cbSGreg Roach    public function description(): string
901f374598SGreg Roach    {
91bbb76c12SGreg Roach        /* I18N: Description of the “OSM” module */
92bbb76c12SGreg Roach        return I18N::translate('Show the birthplace of ancestors on a map.');
931f374598SGreg Roach    }
941f374598SGreg Roach
951f374598SGreg Roach    /**
96377a2979SGreg Roach     * CSS class for the URL.
97377a2979SGreg Roach     *
98377a2979SGreg Roach     * @return string
99377a2979SGreg Roach     */
100377a2979SGreg Roach    public function chartMenuClass(): string
101377a2979SGreg Roach    {
102377a2979SGreg Roach        return 'menu-chart-pedigreemap';
103377a2979SGreg Roach    }
104377a2979SGreg Roach
105377a2979SGreg Roach    /**
1061f374598SGreg Roach     * Return a menu item for this chart - for use in individual boxes.
1071f374598SGreg Roach     *
1081f374598SGreg Roach     * @param Individual $individual
1091f374598SGreg Roach     *
11049a243cbSGreg Roach     * @return Menu|null
1111f374598SGreg Roach     */
112377a2979SGreg Roach    public function chartBoxMenu(Individual $individual): ?Menu
1131f374598SGreg Roach    {
114e6562982SGreg Roach        return $this->chartMenu($individual);
115e6562982SGreg Roach    }
116e6562982SGreg Roach
117e6562982SGreg Roach    /**
118e6562982SGreg Roach     * The title for a specific instance of this chart.
119e6562982SGreg Roach     *
120e6562982SGreg Roach     * @param Individual $individual
121e6562982SGreg Roach     *
122e6562982SGreg Roach     * @return string
123e6562982SGreg Roach     */
124e6562982SGreg Roach    public function chartTitle(Individual $individual): string
125e6562982SGreg Roach    {
126e6562982SGreg Roach        /* I18N: %s is an individual’s name */
12739ca88baSGreg Roach        return I18N::translate('Pedigree map of %s', $individual->fullName());
128e6562982SGreg Roach    }
129e6562982SGreg Roach
130e6562982SGreg Roach    /**
131e6562982SGreg Roach     * The URL for this chart.
132e6562982SGreg Roach     *
133e6562982SGreg Roach     * @param Individual $individual
134e6562982SGreg Roach     * @param string[]   $parameters
135e6562982SGreg Roach     *
136e6562982SGreg Roach     * @return string
137e6562982SGreg Roach     */
138e6562982SGreg Roach    public function chartUrl(Individual $individual, array $parameters = []): string
139e6562982SGreg Roach    {
140e6562982SGreg Roach        return route('module', [
14126684e68SGreg Roach                'module' => $this->name(),
142e6562982SGreg Roach                'action' => 'PedigreeMap',
143e6562982SGreg Roach                'xref'   => $individual->xref(),
144e6562982SGreg Roach                'ged'    => $individual->tree()->name(),
145e6562982SGreg Roach            ] + $parameters);
146e6562982SGreg Roach    }
147e6562982SGreg Roach
148e6562982SGreg Roach    /**
149*6ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
150b6db7c1fSGreg Roach     * @param Tree                   $tree
151aca28033SGreg Roach     * @param ChartService           $chart_service
1521f374598SGreg Roach     *
153*6ccdf4f0SGreg Roach     * @return ResponseInterface
1541f374598SGreg Roach     */
155*6ccdf4f0SGreg Roach    public function getMapDataAction(ServerRequestInterface $request, Tree $tree, ChartService $chart_service): ResponseInterface
1561f374598SGreg Roach    {
1571f374598SGreg Roach        $xref        = $request->get('reference');
1581f374598SGreg Roach        $indi        = Individual::getInstance($xref, $tree);
1591f374598SGreg Roach        $color_count = count(self::LINE_COLORS);
1601f374598SGreg Roach
161aca28033SGreg Roach        $facts = $this->getPedigreeMapFacts($request, $tree, $chart_service);
1621f374598SGreg Roach
1631f374598SGreg Roach        $geojson = [
1641f374598SGreg Roach            'type'     => 'FeatureCollection',
1651f374598SGreg Roach            'features' => [],
1661f374598SGreg Roach        ];
1677d988ec3SGreg Roach
1687d988ec3SGreg Roach        $sosa_points = [];
1697d988ec3SGreg Roach
1701f374598SGreg Roach        foreach ($facts as $id => $fact) {
1718af6bbf8SGreg Roach            $location = new Location($fact->place()->gedcomName());
1728af6bbf8SGreg Roach
1738af6bbf8SGreg Roach            // Use the co-ordinates from the fact (if they exist).
1748af6bbf8SGreg Roach            $latitude  = $fact->latitude();
1758af6bbf8SGreg Roach            $longitude = $fact->longitude();
1768af6bbf8SGreg Roach
1778af6bbf8SGreg Roach            // Use the co-ordinates from the location otherwise.
1788af6bbf8SGreg Roach            if ($latitude === 0.0 && $longitude === 0.0) {
1798af6bbf8SGreg Roach                $latitude  = $location->latitude();
1808af6bbf8SGreg Roach                $longitude = $location->longitude();
1818af6bbf8SGreg Roach            }
1828af6bbf8SGreg Roach
1838af6bbf8SGreg Roach            $icon = ['color' => 'Gold', 'name' => 'bullseye '];
1848af6bbf8SGreg Roach            if ($latitude !== 0.0 || $longitude !== 0.0) {
1851f374598SGreg Roach                $polyline         = null;
1861f374598SGreg Roach                $color            = self::LINE_COLORS[log($id, 2) % $color_count];
1871f374598SGreg Roach                $icon['color']    = $color; //make icon color the same as the line
1888af6bbf8SGreg Roach                $sosa_points[$id] = [$latitude, $longitude];
189cdaafeeeSGreg Roach                $sosa_parent      = intdiv($id, 2);
1901f374598SGreg Roach                if (array_key_exists($sosa_parent, $sosa_points)) {
1911f374598SGreg Roach                    // Would like to use a GeometryCollection to hold LineStrings
1921f374598SGreg Roach                    // rather than generate polylines but the MarkerCluster library
1931f374598SGreg Roach                    // doesn't seem to like them
1941f374598SGreg Roach                    $polyline = [
1951f374598SGreg Roach                        'points'  => [
1961f374598SGreg Roach                            $sosa_points[$sosa_parent],
1978af6bbf8SGreg Roach                            [$latitude, $longitude],
1981f374598SGreg Roach                        ],
1991f374598SGreg Roach                        'options' => [
2001f374598SGreg Roach                            'color' => $color,
2011f374598SGreg Roach                        ],
2021f374598SGreg Roach                    ];
2031f374598SGreg Roach                }
2041f374598SGreg Roach                $geojson['features'][] = [
2051f374598SGreg Roach                    'type'       => 'Feature',
2061f374598SGreg Roach                    'id'         => $id,
2071f374598SGreg Roach                    'valid'      => true,
2081f374598SGreg Roach                    'geometry'   => [
2091f374598SGreg Roach                        'type'        => 'Point',
2108af6bbf8SGreg Roach                        'coordinates' => [$longitude, $latitude],
2111f374598SGreg Roach                    ],
2121f374598SGreg Roach                    'properties' => [
2131f374598SGreg Roach                        'polyline' => $polyline,
2141f374598SGreg Roach                        'icon'     => $icon,
2158af6bbf8SGreg Roach                        'tooltip'  => strip_tags($fact->place()->fullName()),
2168af6bbf8SGreg Roach                        'summary'  => view('modules/pedigree-map/events', $this->summaryData($indi, $fact, $id)),
2178af6bbf8SGreg Roach                        'zoom'     => $location->zoom() ?: 2,
2181f374598SGreg Roach                    ],
2191f374598SGreg Roach                ];
2201f374598SGreg Roach            }
2211f374598SGreg Roach        }
2227d988ec3SGreg Roach
223*6ccdf4f0SGreg Roach        $code = empty($facts) ? StatusCodeInterface::STATUS_NO_CONTENT : StatusCodeInterface::STATUS_OK;
2241f374598SGreg Roach
225*6ccdf4f0SGreg Roach        return response($geojson, $code);
226*6ccdf4f0SGreg Roach    }
227*6ccdf4f0SGreg Roach
228*6ccdf4f0SGreg Roach    /**
229*6ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
230*6ccdf4f0SGreg Roach     * @param Tree                   $tree
231*6ccdf4f0SGreg Roach     * @param ChartService           $chart_service
232*6ccdf4f0SGreg Roach     *
233*6ccdf4f0SGreg Roach     * @return array
234*6ccdf4f0SGreg Roach     */
235*6ccdf4f0SGreg Roach    private function getPedigreeMapFacts(ServerRequestInterface $request, Tree $tree, ChartService $chart_service): array
236*6ccdf4f0SGreg Roach    {
237*6ccdf4f0SGreg Roach        $xref        = $request->get('reference');
238*6ccdf4f0SGreg Roach        $individual  = Individual::getInstance($xref, $tree);
239*6ccdf4f0SGreg Roach        $generations = (int) $request->get('generations', '4');
240*6ccdf4f0SGreg Roach        $ancestors   = $chart_service->sosaStradonitzAncestors($individual, $generations);
241*6ccdf4f0SGreg Roach        $facts       = [];
242*6ccdf4f0SGreg Roach        foreach ($ancestors as $sosa => $person) {
243*6ccdf4f0SGreg Roach            if ($person->canShow()) {
244*6ccdf4f0SGreg Roach                $birth = $person->facts(['BIRT'])->first();
245*6ccdf4f0SGreg Roach                if ($birth instanceof Fact && $birth->place()->gedcomName() !== '') {
246*6ccdf4f0SGreg Roach                    $facts[$sosa] = $birth;
247*6ccdf4f0SGreg Roach                }
248*6ccdf4f0SGreg Roach            }
249*6ccdf4f0SGreg Roach        }
250*6ccdf4f0SGreg Roach
251*6ccdf4f0SGreg Roach        return $facts;
2521f374598SGreg Roach    }
2531f374598SGreg Roach
2541f374598SGreg Roach    /**
2558af6bbf8SGreg Roach     * @param Individual $individual
2568af6bbf8SGreg Roach     * @param Fact       $fact
2578af6bbf8SGreg Roach     * @param int        $sosa
2588af6bbf8SGreg Roach     *
2598af6bbf8SGreg Roach     * @return array
2608af6bbf8SGreg Roach     */
2618af6bbf8SGreg Roach    private function summaryData(Individual $individual, Fact $fact, int $sosa): array
2628af6bbf8SGreg Roach    {
2638af6bbf8SGreg Roach        $record      = $fact->record();
2648af6bbf8SGreg Roach        $name        = '';
2658af6bbf8SGreg Roach        $url         = '';
2668af6bbf8SGreg Roach        $tag         = $fact->label();
2678af6bbf8SGreg Roach        $addbirthtag = false;
2688af6bbf8SGreg Roach
2698af6bbf8SGreg Roach        if ($record instanceof Family) {
2708af6bbf8SGreg Roach            // Marriage
27139ca88baSGreg Roach            $spouse = $record->spouse($individual);
2728af6bbf8SGreg Roach            if ($spouse) {
2738af6bbf8SGreg Roach                $url  = $spouse->url();
27439ca88baSGreg Roach                $name = $spouse->fullName();
2758af6bbf8SGreg Roach            }
2768af6bbf8SGreg Roach        } elseif ($record !== $individual) {
2778af6bbf8SGreg Roach            // Birth of a child
2788af6bbf8SGreg Roach            $url  = $record->url();
27939ca88baSGreg Roach            $name = $record->fullName();
2808af6bbf8SGreg Roach            $tag  = GedcomTag::getLabel('_BIRT_CHIL', $record);
2818af6bbf8SGreg Roach        }
2828af6bbf8SGreg Roach
2838af6bbf8SGreg Roach        if ($sosa > 1) {
2848af6bbf8SGreg Roach            $addbirthtag = true;
2858af6bbf8SGreg Roach            $tag         = ucfirst(FunctionsCharts::getSosaName($sosa));
2868af6bbf8SGreg Roach        }
2878af6bbf8SGreg Roach
2888af6bbf8SGreg Roach        return [
2898af6bbf8SGreg Roach            'tag'    => $tag,
2908af6bbf8SGreg Roach            'url'    => $url,
2918af6bbf8SGreg Roach            'name'   => $name,
2928af6bbf8SGreg Roach            'value'  => $fact->value(),
2938af6bbf8SGreg Roach            'date'   => $fact->date()->display(true),
2948af6bbf8SGreg Roach            'place'  => $fact->place(),
2958af6bbf8SGreg Roach            'addtag' => $addbirthtag,
2968af6bbf8SGreg Roach        ];
2978af6bbf8SGreg Roach    }
2988af6bbf8SGreg Roach
2998af6bbf8SGreg Roach    /**
300*6ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
3011f374598SGreg Roach     *
302*6ccdf4f0SGreg Roach     * @return ResponseInterface
3031f374598SGreg Roach     */
304*6ccdf4f0SGreg Roach    public function getProviderStylesAction(ServerRequestInterface $request): ResponseInterface
3051f374598SGreg Roach    {
3061f374598SGreg Roach        $styles = $this->getMapProviderData($request);
3071f374598SGreg Roach
308*6ccdf4f0SGreg Roach        return response($styles);
3091f374598SGreg Roach    }
3101f374598SGreg Roach
3111f374598SGreg Roach    /**
312*6ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
3131f374598SGreg Roach     *
3141f374598SGreg Roach     * @return array|null
3151f374598SGreg Roach     */
316*6ccdf4f0SGreg Roach    private function getMapProviderData(ServerRequestInterface $request): ?array
3171f374598SGreg Roach    {
3181f374598SGreg Roach        if (self::$map_providers === null) {
3198d0ebef0SGreg Roach            $providersFile        = WT_ROOT . Webtrees::MODULES_PATH . 'openstreetmap/providers/providers.xml';
3201f374598SGreg Roach            self::$map_selections = [
3211f374598SGreg Roach                'provider' => $this->getPreference('provider', 'openstreetmap'),
3221f374598SGreg Roach                'style'    => $this->getPreference('provider_style', 'mapnik'),
3231f374598SGreg Roach            ];
3241f374598SGreg Roach
3251f374598SGreg Roach            try {
326*6ccdf4f0SGreg Roach                $xml = simplexml_load_string(file_get_contents($providersFile));
3271f374598SGreg Roach                // need to convert xml structure into arrays & strings
3281f374598SGreg Roach                foreach ($xml as $provider) {
3291f374598SGreg Roach                    $style_keys = array_map(
33018d7a90dSGreg Roach                        function (string $item): string {
3311f374598SGreg Roach                            return preg_replace('/[^a-z\d]/i', '', strtolower($item));
3321f374598SGreg Roach                        },
3331f374598SGreg Roach                        (array) $provider->styles
3341f374598SGreg Roach                    );
3351f374598SGreg Roach
3361f374598SGreg Roach                    $key = preg_replace('/[^a-z\d]/i', '', strtolower((string) $provider->name));
3371f374598SGreg Roach
3381f374598SGreg Roach                    self::$map_providers[$key] = [
3391f374598SGreg Roach                        'name'   => (string) $provider->name,
3401f374598SGreg Roach                        'styles' => array_combine($style_keys, (array) $provider->styles),
3411f374598SGreg Roach                    ];
3421f374598SGreg Roach                }
3431f374598SGreg Roach            } catch (Exception $ex) {
3441f374598SGreg Roach                // Default provider is OpenStreetMap
3451f374598SGreg Roach                self::$map_selections = [
3461f374598SGreg Roach                    'provider' => 'openstreetmap',
3471f374598SGreg Roach                    'style'    => 'mapnik',
3481f374598SGreg Roach                ];
3491f374598SGreg Roach                self::$map_providers  = [
3501f374598SGreg Roach                    'openstreetmap' => [
3511f374598SGreg Roach                        'name'   => 'OpenStreetMap',
3521f374598SGreg Roach                        'styles' => ['mapnik' => 'Mapnik'],
3531f374598SGreg Roach                    ],
3541f374598SGreg Roach                ];
355e364afe4SGreg Roach            }
3561f374598SGreg Roach        }
3571f374598SGreg Roach
3581f374598SGreg Roach        //Ugly!!!
3591f374598SGreg Roach        switch ($request->get('action')) {
3601f374598SGreg Roach            case 'BaseData':
3611f374598SGreg Roach                $varName = (self::$map_selections['style'] === '') ? '' : self::$map_providers[self::$map_selections['provider']]['styles'][self::$map_selections['style']];
3621f374598SGreg Roach                $payload = [
3631f374598SGreg Roach                    'selectedProvIndex' => self::$map_selections['provider'],
3641f374598SGreg Roach                    'selectedProvName'  => self::$map_providers[self::$map_selections['provider']]['name'],
3651f374598SGreg Roach                    'selectedStyleName' => $varName,
3661f374598SGreg Roach                ];
3671f374598SGreg Roach                break;
3681f374598SGreg Roach            case 'ProviderStyles':
3691f374598SGreg Roach                $provider = $request->get('provider', 'openstreetmap');
3701f374598SGreg Roach                $payload  = self::$map_providers[$provider]['styles'];
3711f374598SGreg Roach                break;
3721f374598SGreg Roach            case 'AdminConfig':
3731f374598SGreg Roach                $providers = [];
3741f374598SGreg Roach                foreach (self::$map_providers as $key => $provider) {
3751f374598SGreg Roach                    $providers[$key] = $provider['name'];
3761f374598SGreg Roach                }
3771f374598SGreg Roach                $payload = [
3781f374598SGreg Roach                    'providers'     => $providers,
3791f374598SGreg Roach                    'selectedProv'  => self::$map_selections['provider'],
3801f374598SGreg Roach                    'styles'        => self::$map_providers[self::$map_selections['provider']]['styles'],
3811f374598SGreg Roach                    'selectedStyle' => self::$map_selections['style'],
3821f374598SGreg Roach                ];
3831f374598SGreg Roach                break;
3841f374598SGreg Roach            default:
3851f374598SGreg Roach                $payload = null;
3861f374598SGreg Roach        }
3871f374598SGreg Roach
3881f374598SGreg Roach        return $payload;
3891f374598SGreg Roach    }
3901f374598SGreg Roach
3911f374598SGreg Roach    /**
392*6ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
393b6db7c1fSGreg Roach     * @param Tree    $tree
3941f374598SGreg Roach     *
3951f374598SGreg Roach     * @return object
3961f374598SGreg Roach     */
397*6ccdf4f0SGreg Roach    public function getPedigreeMapAction(ServerRequestInterface $request, Tree $tree)
3981f374598SGreg Roach    {
39947b1b9caSGreg Roach        $xref        = $request->get('xref', '');
4001f374598SGreg Roach        $individual  = Individual::getInstance($xref, $tree);
401e759aebbSGreg Roach        $generations = $request->get('generations', self::DEFAULT_GENERATIONS);
4021f374598SGreg Roach
403b6e50991SGreg Roach        if ($individual === null) {
40459f2f229SGreg Roach            throw new IndividualNotFoundException();
405b2ce94c6SRico Sonntag        }
406b2ce94c6SRico Sonntag
407b2ce94c6SRico Sonntag        if (!$individual->canShow()) {
40859f2f229SGreg Roach            throw new IndividualAccessDeniedException();
409b6e50991SGreg Roach        }
410b6e50991SGreg Roach
4113dcc812bSGreg Roach        return $this->viewResponse('modules/pedigree-map/page', [
4123dcc812bSGreg Roach            'module_name'    => $this->name(),
413bbb76c12SGreg Roach            /* I18N: %s is an individual’s name */
41439ca88baSGreg Roach            'title'          => I18N::translate('Pedigree map of %s', $individual->fullName()),
4151f374598SGreg Roach            'tree'           => $tree,
4161f374598SGreg Roach            'individual'     => $individual,
4171f374598SGreg Roach            'generations'    => $generations,
4185553c41dSGreg Roach            'maxgenerations' => self::MAXIMUM_GENERATIONS,
4191f374598SGreg Roach            'map'            => view(
4203dcc812bSGreg Roach                'modules/pedigree-map/chart',
4211f374598SGreg Roach                [
42226684e68SGreg Roach                    'module'      => $this->name(),
423c0935879SGreg Roach                    'ref'         => $individual->xref(),
4241f374598SGreg Roach                    'type'        => 'pedigree',
4251f374598SGreg Roach                    'generations' => $generations,
4261f374598SGreg Roach                ]
427e6562982SGreg Roach            ),
428aca28033SGreg Roach        ]);
4291f374598SGreg Roach    }
4301f374598SGreg Roach}
431