1*5333da53SGreg Roach<?php 2*5333da53SGreg Roach 3*5333da53SGreg Roach/** 4*5333da53SGreg Roach * webtrees: online genealogy 5*5333da53SGreg Roach * Copyright (C) 2020 webtrees development team 6*5333da53SGreg Roach * This program is free software: you can redistribute it and/or modify 7*5333da53SGreg Roach * it under the terms of the GNU General Public License as published by 8*5333da53SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9*5333da53SGreg Roach * (at your option) any later version. 10*5333da53SGreg Roach * This program is distributed in the hope that it will be useful, 11*5333da53SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12*5333da53SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13*5333da53SGreg Roach * GNU General Public License for more details. 14*5333da53SGreg Roach * You should have received a copy of the GNU General Public License 15*5333da53SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16*5333da53SGreg Roach */ 17*5333da53SGreg Roach 18*5333da53SGreg Roachdeclare(strict_types=1); 19*5333da53SGreg Roach 20*5333da53SGreg Roachnamespace Fisharebest\Webtrees; 21*5333da53SGreg Roach 22*5333da53SGreg Roachuse Fisharebest\Webtrees\Services\GedcomService; 23*5333da53SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 24*5333da53SGreg Roachuse Illuminate\Support\Collection; 25*5333da53SGreg Roachuse stdClass; 26*5333da53SGreg Roach 27*5333da53SGreg Roach/** 28*5333da53SGreg Roach * Class PlaceLocation 29*5333da53SGreg Roach */ 30*5333da53SGreg Roachclass PlaceLocation 31*5333da53SGreg Roach{ 32*5333da53SGreg Roach /** @var string e.g. "Westminster, London, England" */ 33*5333da53SGreg Roach private $location_name; 34*5333da53SGreg Roach 35*5333da53SGreg Roach /** @var Collection|string[] The parts of a location name, e.g. ["Westminster", "London", "England"] */ 36*5333da53SGreg Roach private $parts; 37*5333da53SGreg Roach 38*5333da53SGreg Roach /** 39*5333da53SGreg Roach * Create a place-location. 40*5333da53SGreg Roach * 41*5333da53SGreg Roach * @param string $location_name 42*5333da53SGreg Roach */ 43*5333da53SGreg Roach public function __construct(string $location_name) 44*5333da53SGreg Roach { 45*5333da53SGreg Roach // Ignore any empty parts in location names such as "Village, , , Country". 46*5333da53SGreg Roach $this->parts = (new Collection(preg_split(Gedcom::PLACE_SEPARATOR_REGEX, $location_name))) 47*5333da53SGreg Roach ->filter(); 48*5333da53SGreg Roach 49*5333da53SGreg Roach // Rebuild the location name in the correct format. 50*5333da53SGreg Roach $this->location_name = $this->parts->implode(Gedcom::PLACE_SEPARATOR); 51*5333da53SGreg Roach } 52*5333da53SGreg Roach 53*5333da53SGreg Roach /** 54*5333da53SGreg Roach * Get the higher level location. 55*5333da53SGreg Roach * 56*5333da53SGreg Roach * @return PlaceLocation 57*5333da53SGreg Roach */ 58*5333da53SGreg Roach public function parent(): PlaceLocation 59*5333da53SGreg Roach { 60*5333da53SGreg Roach return new self($this->parts->slice(1)->implode(Gedcom::PLACE_SEPARATOR)); 61*5333da53SGreg Roach } 62*5333da53SGreg Roach 63*5333da53SGreg Roach /** 64*5333da53SGreg Roach * The database row that contains this location. 65*5333da53SGreg Roach * Note that due to database collation, both "Quebec" and "Québec" will share the same row. 66*5333da53SGreg Roach * 67*5333da53SGreg Roach * @return int 68*5333da53SGreg Roach */ 69*5333da53SGreg Roach public function id(): int 70*5333da53SGreg Roach { 71*5333da53SGreg Roach return app('cache.array')->remember('location-' . $this->location_name, function () { 72*5333da53SGreg Roach // The "top-level" location won't exist in the database. 73*5333da53SGreg Roach if ($this->parts->isEmpty()) { 74*5333da53SGreg Roach return 0; 75*5333da53SGreg Roach } 76*5333da53SGreg Roach 77*5333da53SGreg Roach $parent_location_id = $this->parent()->id(); 78*5333da53SGreg Roach 79*5333da53SGreg Roach $location_id = (int) DB::table('placelocation') 80*5333da53SGreg Roach ->where('pl_place', '=', $this->parts->first()) 81*5333da53SGreg Roach ->where('pl_parent_id', '=', $parent_location_id) 82*5333da53SGreg Roach ->value('pl_id'); 83*5333da53SGreg Roach 84*5333da53SGreg Roach if ($location_id === 0) { 85*5333da53SGreg Roach $location = $this->parts->first(); 86*5333da53SGreg Roach 87*5333da53SGreg Roach $location_id = 1 + (int) DB::table('placelocation')->max('pl_id'); 88*5333da53SGreg Roach 89*5333da53SGreg Roach DB::table('placelocation')->insert([ 90*5333da53SGreg Roach 'pl_id' => $location_id, 91*5333da53SGreg Roach 'pl_place' => $location, 92*5333da53SGreg Roach 'pl_parent_id' => $parent_location_id, 93*5333da53SGreg Roach 'pl_level' => $this->parts->count() - 1, 94*5333da53SGreg Roach 'pl_lati' => '', 95*5333da53SGreg Roach 'pl_long' => '', 96*5333da53SGreg Roach 'pl_icon' => '', 97*5333da53SGreg Roach 'pl_zoom' => 2, 98*5333da53SGreg Roach ]); 99*5333da53SGreg Roach } 100*5333da53SGreg Roach 101*5333da53SGreg Roach return $location_id; 102*5333da53SGreg Roach }); 103*5333da53SGreg Roach } 104*5333da53SGreg Roach 105*5333da53SGreg Roach /** 106*5333da53SGreg Roach * Does this location exist in the database? Note that calls to PlaceLocation::id() will 107*5333da53SGreg Roach * create the row, so this function is only meaningful when called before a call to PlaceLocation::id(). 108*5333da53SGreg Roach * 109*5333da53SGreg Roach * @return bool 110*5333da53SGreg Roach */ 111*5333da53SGreg Roach public function exists(): bool 112*5333da53SGreg Roach { 113*5333da53SGreg Roach $location_id = 0; 114*5333da53SGreg Roach 115*5333da53SGreg Roach $this->parts->reverse()->each(static function (string $place) use (&$location_id) { 116*5333da53SGreg Roach if ($location_id !== null) { 117*5333da53SGreg Roach $location_id = DB::table('placelocation') 118*5333da53SGreg Roach ->where('pl_parent_id', '=', $location_id) 119*5333da53SGreg Roach ->where('pl_place', '=', $place) 120*5333da53SGreg Roach ->value('pl_id'); 121*5333da53SGreg Roach } 122*5333da53SGreg Roach }); 123*5333da53SGreg Roach 124*5333da53SGreg Roach return $location_id !== null; 125*5333da53SGreg Roach } 126*5333da53SGreg Roach 127*5333da53SGreg Roach /** 128*5333da53SGreg Roach * @return stdClass 129*5333da53SGreg Roach */ 130*5333da53SGreg Roach private function details(): stdClass 131*5333da53SGreg Roach { 132*5333da53SGreg Roach return app('cache.array')->remember('location-details-' . $this->id(), function () { 133*5333da53SGreg Roach // The "top-level" location won't exist in the database. 134*5333da53SGreg Roach if ($this->parts->isEmpty()) { 135*5333da53SGreg Roach return (object) [ 136*5333da53SGreg Roach 'pl_id' => '0', 137*5333da53SGreg Roach 'pl_parent_id' => '0', 138*5333da53SGreg Roach 'pl_level' => null, 139*5333da53SGreg Roach 'pl_place' => '', 140*5333da53SGreg Roach 'pl_lati' => null, 141*5333da53SGreg Roach 'pl_long' => null, 142*5333da53SGreg Roach 'pl_zoom' => null, 143*5333da53SGreg Roach 'pl_icon' => null, 144*5333da53SGreg Roach 'pl_media' => null, 145*5333da53SGreg Roach ]; 146*5333da53SGreg Roach } 147*5333da53SGreg Roach 148*5333da53SGreg Roach return DB::table('placelocation') 149*5333da53SGreg Roach ->where('pl_id', '=', $this->id()) 150*5333da53SGreg Roach ->first(); 151*5333da53SGreg Roach }); 152*5333da53SGreg Roach } 153*5333da53SGreg Roach 154*5333da53SGreg Roach /** 155*5333da53SGreg Roach * Latitude of the location. 156*5333da53SGreg Roach * 157*5333da53SGreg Roach * @return float 158*5333da53SGreg Roach */ 159*5333da53SGreg Roach public function latitude(): float 160*5333da53SGreg Roach { 161*5333da53SGreg Roach $gedcom_service = new GedcomService(); 162*5333da53SGreg Roach 163*5333da53SGreg Roach return $gedcom_service->readLatitude($this->details()->pl_lati ?? ''); 164*5333da53SGreg Roach } 165*5333da53SGreg Roach 166*5333da53SGreg Roach /** 167*5333da53SGreg Roach * Latitude of the longitude. 168*5333da53SGreg Roach * 169*5333da53SGreg Roach * @return float 170*5333da53SGreg Roach */ 171*5333da53SGreg Roach public function longitude(): float 172*5333da53SGreg Roach { 173*5333da53SGreg Roach $gedcom_service = new GedcomService(); 174*5333da53SGreg Roach 175*5333da53SGreg Roach return $gedcom_service->readLongitude($this->details()->pl_long ?? ''); 176*5333da53SGreg Roach } 177*5333da53SGreg Roach 178*5333da53SGreg Roach /** 179*5333da53SGreg Roach * The icon for the location. 180*5333da53SGreg Roach * 181*5333da53SGreg Roach * @return string 182*5333da53SGreg Roach */ 183*5333da53SGreg Roach public function icon(): string 184*5333da53SGreg Roach { 185*5333da53SGreg Roach return (string) $this->details()->pl_icon; 186*5333da53SGreg Roach } 187*5333da53SGreg Roach 188*5333da53SGreg Roach /** 189*5333da53SGreg Roach * Zoom level for the location. 190*5333da53SGreg Roach * 191*5333da53SGreg Roach * @return int 192*5333da53SGreg Roach */ 193*5333da53SGreg Roach public function zoom(): int 194*5333da53SGreg Roach { 195*5333da53SGreg Roach return (int) $this->details()->pl_zoom; 196*5333da53SGreg Roach } 197*5333da53SGreg Roach 198*5333da53SGreg Roach /** 199*5333da53SGreg Roach * @return string 200*5333da53SGreg Roach */ 201*5333da53SGreg Roach public function locationName(): string 202*5333da53SGreg Roach { 203*5333da53SGreg Roach return (string) $this->parts->first(); 204*5333da53SGreg Roach } 205*5333da53SGreg Roach} 206