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:
commit
e817a34a3a
@ -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;
|
@ -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,15 +60,29 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MapEditor.selectedMarker.setIcon(L.icon({
|
MapEditor.selectedMarker.setIcon(MapEditor.selectedMarker.noPano ? IconCollection.iconRed : IconCollection.iconGreen);
|
||||||
iconUrl: '/static/img/markers/marker-green.svg',
|
|
||||||
iconSize: [24, 32],
|
|
||||||
iconAnchor: [12, 32]
|
|
||||||
}));
|
|
||||||
MapEditor.selectedMarker.setZIndexOffset(1000);
|
MapEditor.selectedMarker.setZIndexOffset(1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var IconCollection = {
|
||||||
|
iconGreen: L.icon({
|
||||||
|
iconUrl: '/static/img/markers/marker-green.svg',
|
||||||
|
iconSize: [24, 32],
|
||||||
|
iconAnchor: [12, 32]
|
||||||
|
}),
|
||||||
|
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 = {
|
||||||
getHighResData: function () {
|
getHighResData: function () {
|
||||||
if (window.devicePixelRatio >= 4) {
|
if (window.devicePixelRatio >= 4) {
|
||||||
@ -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'), {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
115
src/Repository/PlaceRepository.php
Normal file
115
src/Repository/PlaceRepository.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user