src/Controller/LocationController.php line 19
<?phpnamespace App\Controller;use App\Entity\LocationDetail;use App\Repository\CityRepository;use App\Repository\LocationRepository;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Annotation\Route;class LocationController extends AbstractController{/*** @Route("/map", name="app_map")*/public function map(LocationRepository $locationRepository,CityRepository $cityRepository,\App\Service\Mercure\MercurePublisher $mercure): Response {// Fetch all active locations with their details, city and country in one query$locations = $locationRepository->findAllActiveWithDetails();// Cities for the filter dropdown$cities = $cityRepository->findAllActive();/** @var \App\Entity\User|null $user */$user = $this->getUser();$topic = $user !== null? \App\Service\Mercure\MercurePublisher::userBookingTopic($user->getId()): null;$response = $this->render('@web/page/index.html.twig', ['locations' => $locations,'cities' => $cities,'yandex_api_key' => $this->getParameter('app.yandex_api_key'),'mercure_url' => $mercure->getPublicUrl(),'mercure_topic' => $topic,]);// A signed-in passenger may subscribe to their own live-order topic.if ($topic !== null) {$response->headers->setCookie($mercure->authorizationCookie([$topic]));}return $response;}/*** Live "drive-by" endpoint.** The map page polls this with the visitor's current GPS coordinates. If an* active location sits within the given radius, we return it together with* the audio guide (mp3) so the browser can autoplay the explanation.*/#[Route('/map/nearby', name: 'app_map_nearby', methods: ['GET'])]public function nearby(Request $request,LocationRepository $locationRepository): JsonResponse {$lat = $request->query->get('lat');$lng = $request->query->get('lng');if ($lat === null || $lng === null || !is_numeric($lat) || !is_numeric($lng)) {return $this->json(['found' => false, 'error' => 'lat and lng are required numeric parameters'],Response::HTTP_BAD_REQUEST);}// Trigger radius in metres. Clamp so a caller can't ask for the world.$radius = (float) $request->query->get('radius', '150');$radius = max(10.0, min($radius, 2000.0));// Optional preferred language for the audio guide (ISO 639-1).$lang = $request->query->get('lang');$result = $locationRepository->findNearestActiveWithDetails((float) $lat,(float) $lng,$radius);if ($result === null) {// Nothing within the radius — still report the closest place and how// far it is, so the client can show useful feedback.$nearest = $locationRepository->findNearestInfo((float) $lat, (float) $lng);return $this->json(['found' => false,'nearest' => $nearest === null ? null : ['name' => $nearest['name'],'distance' => round($nearest['distance'], 1),],]);}/** @var \App\Entity\Location $location */$location = $result['location'];// Pick the audio guide: active audio details, in sort order, preferring// the requested language, then falling back to the first available.$audioDetails = [];foreach ($location->getDetails() as $detail) {if ($detail->isActive()&& $detail->getType() === LocationDetail::TYPE_AUDIO&& $detail->getFilePath()) {$audioDetails[] = $detail;}}usort($audioDetails, static fn (LocationDetail $a, LocationDetail $b) => $a->getSortOrder() <=> $b->getSortOrder());$audio = null;if ($lang !== null && $lang !== '') {// Strict: when a language is requested, only play that language's// audio (no fallback to other languages).foreach ($audioDetails as $detail) {if ($detail->getLanguage() === $lang) {$audio = $detail;break;}}} else {$audio = $audioDetails[0] ?? null;}return $this->json(['found' => true,'location' => ['id' => $location->getId(),'name' => $location->getName(),'slug' => $location->getSlug(),'category' => $location->getCategory(),'address' => $location->getAddress(),'lat' => $location->getLatitude(),'lng' => $location->getLongitude(),'distance' => round($result['distance'], 1),],'audio' => $audio === null ? null : ['id' => $audio->getId(),'title' => $audio->getTitle(),'filePath' => $audio->getFilePath(),'content' => $audio->getContent(),'language' => $audio->getLanguage(),],]);}}