1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Http\RequestHandlers; 21 22use Fisharebest\Webtrees\DB; 23use Fisharebest\Webtrees\Gedcom; 24use Fisharebest\Webtrees\PlaceLocation; 25use Fisharebest\Webtrees\Services\MapDataService; 26use Psr\Http\Message\ResponseInterface; 27use Psr\Http\Message\ServerRequestInterface; 28use Psr\Http\Server\RequestHandlerInterface; 29 30use function addcslashes; 31use function array_reverse; 32use function array_unshift; 33use function count; 34use function implode; 35use function preg_replace; 36use function response; 37 38/** 39 * Export geographic data. 40 */ 41class MapDataExportGeoJson implements RequestHandlerInterface 42{ 43 private MapDataService $map_data_service; 44 45 /** 46 * Dependency injection. 47 * 48 * @param MapDataService $map_data_service 49 */ 50 public function __construct(MapDataService $map_data_service) 51 { 52 $this->map_data_service = $map_data_service; 53 } 54 55 /** 56 * @param ServerRequestInterface $request 57 * 58 * @return ResponseInterface 59 */ 60 public function handle(ServerRequestInterface $request): ResponseInterface 61 { 62 $parent_id = $request->getAttribute('parent_id'); 63 64 if ($parent_id === null) { 65 $parent = new PlaceLocation(''); 66 } else { 67 $parent = $this->map_data_service->findById((int) $parent_id); 68 } 69 70 for ($tmp = $parent, $hierarchy = []; $tmp->id() !== null; $tmp = $tmp->parent()) { 71 $hierarchy[] = $tmp->locationName(); 72 } 73 74 // Create the file name 75 $filename = preg_replace('/[^\p{L}]+/u', '-', $hierarchy[0] ?? 'Global') . '.geojson'; 76 77 // Recursively search for child places 78 $features = []; 79 $queue = [[ 80 $parent->id(), array_reverse($hierarchy), $parent->latitude(), $parent->longitude() 81 ]]; 82 83 while ($queue !== []) { 84 [$id, $hierarchy, $latitude, $longitude] = array_shift($queue); 85 86 if ($latitude !== null && !$longitude !== null) { 87 $features[] = [ 88 'type' => 'Feature', 89 'geometry' => [ 90 'type' => 'Point', 91 'coordinates' => [ 92 (float) $longitude, 93 (float) $latitude, 94 ], 95 ], 96 'properties' => [ 97 'name' => implode(Gedcom::PLACE_SEPARATOR, array_reverse($hierarchy)), 98 ], 99 ]; 100 } 101 102 $query = DB::table('place_location'); 103 // Data for the next level. 104 105 if ($id === null) { 106 $query->whereNull('parent_id'); 107 } else { 108 $query->where('parent_id', '=', $id); 109 } 110 111 $rows = $query 112 ->orderBy('place', 'DESC') 113 ->select(['id', 'place', 'latitude', 'longitude']) 114 ->get(); 115 116 $next_level = count($hierarchy); 117 118 foreach ($rows as $row) { 119 $hierarchy[$next_level] = $row->place; 120 array_unshift($queue, [$row->id, $hierarchy, $row->latitude, $row->longitude]); 121 } 122 } 123 124 $geojson = [ 125 'type' => 'FeatureCollection', 126 'features' => $features, 127 ]; 128 129 $filename = addcslashes($filename, '"'); 130 131 return response($geojson) 132 ->withHeader('content-type', 'application/vnd.geo+json') 133 ->withHeader('content-disposition', 'attachment; filename="' . $filename . '"'); 134 } 135} 136