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\Module; 19 20use Exception; 21use Fisharebest\Webtrees\Fact; 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\GedcomTag; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Location; 27use Fisharebest\Webtrees\Site; 28use Illuminate\Support\Collection; 29use stdClass; 30 31/** 32 * Class PlacesMapModule 33 */ 34class PlacesModule extends AbstractModule implements ModuleTabInterface 35{ 36 use ModuleTabTrait; 37 38 public const ICONS = [ 39 'BIRT' => ['color' => 'Crimson', 'name' => 'birthday-cake'], 40 'MARR' => ['color' => 'Green', 'name' => 'venus-mars'], 41 'DEAT' => ['color' => 'Black', 'name' => 'plus'], 42 'CENS' => ['color' => 'MediumBlue', 'name' => 'users'], 43 'RESI' => ['color' => 'MediumBlue', 'name' => 'home'], 44 'OCCU' => ['color' => 'MediumBlue', 'name' => 'briefcase'], 45 'GRAD' => ['color' => 'MediumBlue', 'name' => 'graduation-cap'], 46 'EDUC' => ['color' => 'MediumBlue', 'name' => 'university'], 47 ]; 48 49 public const DEFAULT_ICON = ['color' => 'Gold', 'name' => 'bullseye ']; 50 51 /** 52 * How should this module be identified in the control panel, etc.? 53 * 54 * @return string 55 */ 56 public function title(): string 57 { 58 /* I18N: Name of a module */ 59 return I18N::translate('Places'); 60 } 61 62 /** 63 * A sentence describing what this module does. 64 * 65 * @return string 66 */ 67 public function description(): string 68 { 69 /* I18N: Description of the “OSM” module */ 70 return I18N::translate('Show the location of events on a map.'); 71 } 72 73 /** 74 * The default position for this tab. It can be changed in the control panel. 75 * 76 * @return int 77 */ 78 public function defaultTabOrder(): int 79 { 80 return 8; 81 } 82 83 /** {@inheritdoc} */ 84 public function hasTabContent(Individual $individual): bool 85 { 86 return Site::getPreference('map-provider') !== ''; 87 } 88 89 /** {@inheritdoc} */ 90 public function isGrayedOut(Individual $individual): bool 91 { 92 return false; 93 } 94 95 /** {@inheritdoc} */ 96 public function canLoadAjax(): bool 97 { 98 return true; 99 } 100 101 /** {@inheritdoc} */ 102 public function getTabContent(Individual $individual): string 103 { 104 return view('modules/places/tab', [ 105 'data' => $this->getMapData($individual), 106 ]); 107 } 108 109 /** 110 * @param Individual $indi 111 * 112 * @return stdClass 113 */ 114 private function getMapData(Individual $indi): stdClass 115 { 116 $facts = $this->getPersonalFacts($indi); 117 118 $geojson = [ 119 'type' => 'FeatureCollection', 120 'features' => [], 121 ]; 122 123 foreach ($facts as $id => $fact) { 124 $location = new Location($fact->place()->gedcomName()); 125 126 // Use the co-ordinates from the fact (if they exist). 127 $latitude = $fact->latitude(); 128 $longitude = $fact->longitude(); 129 130 // Use the co-ordinates from the location otherwise. 131 if ($latitude === 0.0 && $longitude === 0.0) { 132 $latitude = $location->latitude(); 133 $longitude = $location->longitude(); 134 } 135 136 $icon = self::ICONS[$fact->getTag()] ?? self::DEFAULT_ICON; 137 138 if ($latitude !== 0.0 || $longitude !== 0.0) { 139 $geojson['features'][] = [ 140 'type' => 'Feature', 141 'id' => $id, 142 'valid' => true, 143 'geometry' => [ 144 'type' => 'Point', 145 'coordinates' => [$longitude, $latitude], 146 ], 147 'properties' => [ 148 'polyline' => null, 149 'icon' => $icon, 150 'tooltip' => strip_tags($fact->place()->fullName()), 151 'summary' => view('modules/places/event-sidebar', $this->summaryData($indi, $fact)), 152 'zoom' => $location->zoom(), 153 ], 154 ]; 155 } 156 } 157 158 return (object) $geojson; 159 } 160 161 /** 162 * @param Individual $individual 163 * 164 * @return Collection 165 * @return Fact[] 166 * @throws Exception 167 */ 168 private function getPersonalFacts(Individual $individual): Collection 169 { 170 $facts = $individual->facts(); 171 172 foreach ($individual->spouseFamilies() as $family) { 173 $facts = $facts->merge($family->facts()); 174 // Add birth of children from this family to the facts array 175 foreach ($family->children() as $child) { 176 $childsBirth = $child->facts(['BIRT'])->first(); 177 if ($childsBirth instanceof Fact && $childsBirth->place()->gedcomName() !== '') { 178 $facts->push($childsBirth); 179 } 180 } 181 } 182 183 $facts = Fact::sortFacts($facts); 184 185 return $facts->filter(static function (Fact $item): bool { 186 return $item->place()->gedcomName() !== ''; 187 }); 188 } 189 190 /** 191 * @param Individual $individual 192 * @param Fact $fact 193 * 194 * @return mixed[] 195 */ 196 private function summaryData(Individual $individual, Fact $fact): array 197 { 198 $record = $fact->record(); 199 $name = ''; 200 $url = ''; 201 $tag = $fact->label(); 202 203 if ($record instanceof Family) { 204 // Marriage 205 $spouse = $record->spouse($individual); 206 if ($spouse instanceof Individual) { 207 $url = $spouse->url(); 208 $name = $spouse->fullName(); 209 } 210 } elseif ($record !== $individual) { 211 // Birth of a child 212 $url = $record->url(); 213 $name = $record->fullName(); 214 $tag = GedcomTag::getLabel('_BIRT_CHIL', $record); 215 } 216 217 return [ 218 'tag' => $tag, 219 'url' => $url, 220 'name' => $name, 221 'value' => $fact->value(), 222 'date' => $fact->date()->display(true), 223 'place' => $fact->place(), 224 'addtag' => false, 225 ]; 226 } 227} 228