Merged in feature/MAPG-100-store-panoid-in-database-for-an (pull request #85)

Feature/MAPG-100 store panoid in database for an
This commit is contained in:
Bence Pőcze 2020-06-03 21:43:08 +00:00
commit e817a34a3a
5 changed files with 184 additions and 112 deletions

View File

@ -0,0 +1,6 @@
ALTER TABLE
`places`
ADD
`pano_id_cached` varchar(255) NULL DEFAULT NULL,
ADD
`pano_id_cached_timestamp` timestamp NULL DEFAULT NULL;

View File

@ -4,7 +4,7 @@
panorama: null, panorama: null,
selectedMarker: null, selectedMarker: null,
getPlace: function (placeId) { getPlace: function (placeId, marker) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = 'json'; xhr.responseType = 'json';
xhr.onload = function () { xhr.onload = function () {
@ -12,6 +12,9 @@
if (!this.response.panoId) { if (!this.response.panoId) {
document.getElementById('noPano').style.visibility = 'visible'; document.getElementById('noPano').style.visibility = 'visible';
marker.noPano = true;
return; return;
} }
@ -41,11 +44,7 @@
MapEditor.resetSelected(); MapEditor.resetSelected();
MapEditor.selectedMarker = marker; MapEditor.selectedMarker = marker;
marker.setIcon(L.icon({ marker.setIcon(IconCollection.iconBlue);
iconUrl: '/static/img/markers/marker-blue.svg',
iconSize: [24, 32],
iconAnchor: [12, 32]
}));
marker.setZIndexOffset(2000); marker.setZIndexOffset(2000);
MapEditor.map.invalidateSize(true); MapEditor.map.invalidateSize(true);
@ -53,7 +52,7 @@
MapEditor.panorama.setVisible(false); MapEditor.panorama.setVisible(false);
MapEditor.getPlace(marker.placeId); MapEditor.getPlace(marker.placeId, marker);
}, },
resetSelected: function () { resetSelected: function () {
@ -61,13 +60,27 @@
return; return;
} }
MapEditor.selectedMarker.setIcon(L.icon({ MapEditor.selectedMarker.setIcon(MapEditor.selectedMarker.noPano ? IconCollection.iconRed : IconCollection.iconGreen);
MapEditor.selectedMarker.setZIndexOffset(1000);
}
};
var IconCollection = {
iconGreen: L.icon({
iconUrl: '/static/img/markers/marker-green.svg', iconUrl: '/static/img/markers/marker-green.svg',
iconSize: [24, 32], iconSize: [24, 32],
iconAnchor: [12, 32] iconAnchor: [12, 32]
})); }),
MapEditor.selectedMarker.setZIndexOffset(1000); iconRed: L.icon({
} iconUrl: '/static/img/markers/marker-red.svg',
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
iconBlue: L.icon({
iconUrl: '/static/img/markers/marker-blue.svg',
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
}; };
var Util = { var Util = {
@ -102,11 +115,7 @@
for (var i = 0; i < places.length; ++i) { for (var i = 0; i < places.length; ++i) {
var marker = L.marker({ lat: places[i].lat, lng: places[i].lng }, { var marker = L.marker({ lat: places[i].lat, lng: places[i].lng }, {
icon: L.icon({ icon: places[i].noPano ? IconCollection.iconRed : IconCollection.iconGreen,
iconUrl: '/static/img/markers/marker-green.svg',
iconSize: [24, 32],
iconAnchor: [12, 32]
}),
zIndexOffset: 1000 zIndexOffset: 1000
}) })
.addTo(MapEditor.map) .addTo(MapEditor.map)
@ -119,6 +128,7 @@
}); });
marker.placeId = places[i].id; marker.placeId = places[i].id;
marker.noPano = places[i].noPano;
} }
MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), { MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {

View File

@ -1,22 +1,31 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use MapGuesser\Database\Query\Select; use MapGuesser\Database\Query\Select;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Database\IResultSet; use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds; use MapGuesser\Util\Geo\Bounds;
class MapAdminController class MapAdminController
{ {
public function getMaps(): IContent { private PlaceRepository $placeRepository;
public function __construct()
{
$this->placeRepository = new PlaceRepository();
}
public function getMaps(): IContent
{
//TODO //TODO
return new HtmlContent('admin/maps'); return new HtmlContent('admin/maps');
} }
public function getMapEditor(array $parameters): IContent { public function getMapEditor(array $parameters): IContent
{
$mapId = (int) $parameters['mapId']; $mapId = (int) $parameters['mapId'];
$bounds = $this->getMapBounds($mapId); $bounds = $this->getMapBounds($mapId);
@ -27,33 +36,13 @@ class MapAdminController
return new HtmlContent('admin/map_editor', $data); return new HtmlContent('admin/map_editor', $data);
} }
public function getPlace(array $parameters) { public function getPlace(array $parameters)
{
$placeId = (int) $parameters['placeId']; $placeId = (int) $parameters['placeId'];
$select = new Select(\Container::$dbConnection, 'places'); $placeData = $this->placeRepository->getById($placeId);
$select->columns(['id', 'lat', 'lng']);
$select->whereId($placeId);
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC); $data = ['panoId' => $placeData['panoId']];
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
$request->setQuery([
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
'location' => $place['lat'] . ',' . $place['lng'],
'source' => 'outdoor'
]);
$response = $request->send();
$panoData = json_decode($response->getBody(), true);
if ($panoData['status'] !== 'OK') {
$panoId = null;
} else {
$panoId = $panoData['pano_id'];
}
$data = ['panoId' => $panoId];
return new JsonContent($data); return new JsonContent($data);
} }
@ -73,12 +62,19 @@ class MapAdminController
private function &getPlaces(int $mapId): array private function &getPlaces(int $mapId): array
{ {
$select = new Select(\Container::$dbConnection, 'places'); $select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id', 'lat', 'lng']); $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
$select->where('map_id', '=', $mapId); $select->where('map_id', '=', $mapId);
$select->orderBy('lng'); $select->orderBy('lng');
//$select->limit(100);
$places = $select->execute()->fetchAll(IResultSet::FETCH_ASSOC); $result = $select->execute();
$places = [];
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
$noPano = $place['pano_id_cached_timestamp'] && $place['pano_id_cached'] === null;
$places[] = ['id' => $place['id'], 'lat' => $place['lat'], 'lng' => $place['lng'], 'noPano' => $noPano];
}
return $places; return $places;
} }

View File

@ -1,17 +1,22 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use MapGuesser\Database\Query\Select;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Util\Geo\Position; use MapGuesser\Util\Geo\Position;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Repository\PlaceRepository;
class PositionController class PositionController
{ {
const NUMBER_OF_ROUNDS = 5; const NUMBER_OF_ROUNDS = 5;
const MAX_SCORE = 1000; const MAX_SCORE = 1000;
private PlaceRepository $placeRepository;
public function __construct()
{
$this->placeRepository = new PlaceRepository();
}
public function getPosition(array $parameters): IContent public function getPosition(array $parameters): IContent
{ {
$mapId = (int) $parameters['mapId']; $mapId = (int) $parameters['mapId'];
@ -22,7 +27,7 @@ class PositionController
} }
if (count($_SESSION['state']['rounds']) === 0) { if (count($_SESSION['state']['rounds']) === 0) {
$newPosition = $this->getNewPosition($mapId); $newPosition = $this->placeRepository->getForMapWithValidPano($mapId);
$_SESSION['state']['rounds'][] = $newPosition; $_SESSION['state']['rounds'][] = $newPosition;
$data = ['panoId' => $newPosition['panoId']]; $data = ['panoId' => $newPosition['panoId']];
@ -79,7 +84,7 @@ class PositionController
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]); $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
} }
$newPosition = $this->getNewPosition($mapId, $exclude); $newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
$_SESSION['state']['rounds'][] = $newPosition; $_SESSION['state']['rounds'][] = $newPosition;
$panoId = $newPosition['panoId']; $panoId = $newPosition['panoId'];
@ -100,66 +105,6 @@ class PositionController
return new JsonContent($data); return new JsonContent($data);
} }
private function getNewPosition(int $mapId, array $exclude = []): array
{
$placesWithoutPano = [];
do {
$place = $this->selectNewPlace($mapId, $exclude);
$position = new Position($place['lat'], $place['lng']);
$panoId = $this->getPanorama($position);
if ($panoId === null) {
$placesWithoutPano[] = $place['id'];
}
} while ($panoId === null);
return [
'placesWithoutPano' => $placesWithoutPano,
'placeId' => $place['id'],
'position' => $position,
'panoId' => $panoId
];
}
private function selectNewPlace(int $mapId, array $exclude): array
{
$select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id', 'lat', 'lng']);
$select->where('id', 'NOT IN', $exclude);
$select->where('map_id', '=', $mapId);
$numberOfPlaces = $select->count();// TODO: what if 0
$randomOffset = random_int(0, $numberOfPlaces - 1);
$select->orderBy('id');
$select->limit(1, $randomOffset);
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
return $place;
}
private function getPanorama(Position $position): ?string
{
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
$request->setQuery([
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
'location' => $position->getLat() . ',' . $position->getLng(),
'source' => 'outdoor'
]);
$response = $request->send();
$panoData = json_decode($response->getBody(), true);
if ($panoData['status'] !== 'OK') {
return null;
}
return $panoData['pano_id'];
}
private function calculateDistance(Position $realPosition, Position $guessPosition): float private function calculateDistance(Position $realPosition, Position $guessPosition): float
{ {
return $realPosition->calculateDistanceTo($guessPosition); return $realPosition->calculateDistanceTo($guessPosition);

View File

@ -0,0 +1,115 @@
<?php namespace MapGuesser\Repository;
use MapGuesser\Util\Geo\Position;
use MapGuesser\Database\Query\Select;
use MapGuesser\Database\Query\Modify;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Database\IResultSet;
use DateTime;
use DateInterval;
class PlaceRepository
{
public function getById(int $placeId)
{
$place = $this->selectFromDbById($placeId);
$panoId = $this->requestPanoId($place);
$position = new Position($place['lat'], $place['lng']);
return [
'position' => $position,
'panoId' => $panoId
];
}
public function getForMapWithValidPano(int $mapId, array $exclude = []): array
{
$placesWithoutPano = [];
do {
$place = $this->selectFromDbForMap($mapId, $exclude);
$panoId = $this->requestPanoId($place);
if ($panoId === null) {
$placesWithoutPano[] = $place['id'];
}
} while ($panoId === null);
$position = new Position($place['lat'], $place['lng']);
return [
'placesWithoutPano' => $placesWithoutPano,
'placeId' => $place['id'],
'position' => $position,
'panoId' => $panoId
];
}
private function selectFromDbById(int $placeId): array
{
$select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
$select->whereId($placeId);
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
return $place;
}
private function selectFromDbForMap(int $mapId, array $exclude): array
{
$select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
$select->where('id', 'NOT IN', $exclude);
$select->where('map_id', '=', $mapId);
$numberOfPlaces = $select->count();// TODO: what if 0
$randomOffset = random_int(0, $numberOfPlaces - 1);
$select->orderBy('id');
$select->limit(1, $randomOffset);
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
return $place;
}
private function requestPanoId(array $place): ?string
{
if (
$place['pano_id_cached_timestamp'] &&
(new DateTime($place['pano_id_cached_timestamp']))->add(new DateInterval('P1D')) > new DateTime()
) {
return $place['pano_id_cached'];
}
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
$request->setQuery([
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
'location' => $place['lat'] . ',' . $place['lng'],
'source' => 'outdoor'
]);
$response = $request->send();
$panoData = json_decode($response->getBody(), true);
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
$this->saveCachedPanoId($place['id'], $panoId);
return $panoId;
}
private function saveCachedPanoId(int $placeId, ?string $panoId): void
{
$modify = new Modify(\Container::$dbConnection, 'places');
$modify->setId($placeId);
$modify->set('pano_id_cached', $panoId);
$modify->set('pano_id_cached_timestamp', (new DateTime())->format('Y-m-d H:i:s'));
$modify->save();
}
}