Merged in develop (pull request #12)
Develop
This commit is contained in:
		
						commit
						d1b0522fdd
					
				@ -1,2 +1,6 @@
 | 
			
		||||
DEV=true
 | 
			
		||||
DEV=1
 | 
			
		||||
DB_HOST=mariadb
 | 
			
		||||
DB_USER=mapguesser
 | 
			
		||||
DB_PASSWORD=mapguesser
 | 
			
		||||
DB_NAME=mapguesser
 | 
			
		||||
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
*.gif filter=lfs diff=lfs merge=lfs -text
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,3 @@
 | 
			
		||||
.env
 | 
			
		||||
installed
 | 
			
		||||
vendor
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								db/mapguesser.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								db/mapguesser.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
SET NAMES utf8mb4;
 | 
			
		||||
SET foreign_key_checks = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `maps`;
 | 
			
		||||
CREATE TABLE `maps` (
 | 
			
		||||
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 | 
			
		||||
  `name` varchar(255) NOT NULL,
 | 
			
		||||
  `description` text NOT NULL,
 | 
			
		||||
  `bound_south_lat` decimal(8,6) NOT NULL,
 | 
			
		||||
  `bound_west_lng` decimal(9,6) NOT NULL,
 | 
			
		||||
  `bound_north_lat` decimal(8,6) NOT NULL,
 | 
			
		||||
  `bound_east_lng` decimal(9,6) NOT NULL,
 | 
			
		||||
  PRIMARY KEY (`id`)
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `places`;
 | 
			
		||||
CREATE TABLE `places` (
 | 
			
		||||
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 | 
			
		||||
  `map_id` int(10) unsigned NOT NULL,
 | 
			
		||||
  `lat` decimal(8,6) NOT NULL,
 | 
			
		||||
  `lng` decimal(9,6) NOT NULL,
 | 
			
		||||
  PRIMARY KEY (`id`),
 | 
			
		||||
  KEY `map_id` (`map_id`),
 | 
			
		||||
  CONSTRAINT `places_map_id` FOREIGN KEY (`map_id`) REFERENCES `maps` (`id`)
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 | 
			
		||||
@ -13,7 +13,6 @@ services:
 | 
			
		||||
        volumes:
 | 
			
		||||
            - mysql:/var/lib/mysql
 | 
			
		||||
        environment:
 | 
			
		||||
            #TZ: Europe/Budapest
 | 
			
		||||
            MYSQL_ROOT_PASSWORD: 'root'
 | 
			
		||||
            MYSQL_DATABASE: 'mapguesser'
 | 
			
		||||
            MYSQL_USER: 'mapguesser'
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,9 @@ ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
 | 
			
		||||
# Install Apache, PHP and further necessary packages
 | 
			
		||||
RUN apt update
 | 
			
		||||
RUN apt install -y curl git apache2 \
 | 
			
		||||
RUN apt install -y curl git mariadb-client apache2 \
 | 
			
		||||
    php-apcu php-xdebug php7.4-cli php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip
 | 
			
		||||
 | 
			
		||||
# Configure tzdata
 | 
			
		||||
#RUN ln -fs /usr/share/zoneinfo/Europe/Budapest /etc/localtime
 | 
			
		||||
#RUN dpkg-reconfigure --frontend noninteractive tzdata
 | 
			
		||||
 | 
			
		||||
# Configure Apache with PHP
 | 
			
		||||
RUN mkdir -p /run/php
 | 
			
		||||
RUN a2enmod proxy_fcgi rewrite
 | 
			
		||||
@ -24,6 +20,12 @@ RUN echo "xdebug.remote_connect_back = 1" >> /etc/php/7.4/mods-available/xdebug.
 | 
			
		||||
COPY scripts/install-composer.sh install-composer.sh
 | 
			
		||||
RUN ./install-composer.sh
 | 
			
		||||
 | 
			
		||||
# Install Node.js and required packages
 | 
			
		||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
 | 
			
		||||
RUN apt install -y nodejs
 | 
			
		||||
RUN npm install -g uglify-js
 | 
			
		||||
RUN npm install -g clean-css-cli
 | 
			
		||||
 | 
			
		||||
EXPOSE 80
 | 
			
		||||
VOLUME /var/www/mapguesser
 | 
			
		||||
WORKDIR /var/www/mapguesser
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
EXPECTED_CHECKSUM="$(curl -s https://composer.github.io/installer.sig)"
 | 
			
		||||
EXPECTED_CHECKSUM="$(curl -sL https://composer.github.io/installer.sig)"
 | 
			
		||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
 | 
			
		||||
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								main.php
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.php
									
									
									
									
									
								
							@ -2,7 +2,9 @@
 | 
			
		||||
 | 
			
		||||
require 'vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
 | 
			
		||||
const ROOT = __DIR__;
 | 
			
		||||
 | 
			
		||||
$dotenv = Dotenv\Dotenv::createImmutable(ROOT);
 | 
			
		||||
$dotenv->load();
 | 
			
		||||
 | 
			
		||||
if (!empty($_ENV['DEV'])) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								public/.htaccess
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								public/.htaccess
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
RewriteEngine On
 | 
			
		||||
RewriteBase /
 | 
			
		||||
RewriteCond %{REQUEST_FILENAME} !-d
 | 
			
		||||
RewriteCond %{REQUEST_FILENAME} !-f
 | 
			
		||||
RewriteRule ^ index.php [L]
 | 
			
		||||
@ -2,39 +2,26 @@
 | 
			
		||||
 | 
			
		||||
require '../main.php';
 | 
			
		||||
 | 
			
		||||
// demo position
 | 
			
		||||
$realPosition = new MapGuesser\Geo\Position(47.85239, 13.35101);
 | 
			
		||||
// very basic routing
 | 
			
		||||
$host = $_SERVER["REQUEST_SCHEME"] . '://' . $_SERVER["SERVER_NAME"];
 | 
			
		||||
$url = $_SERVER['REQUEST_URI'];
 | 
			
		||||
switch($url) {
 | 
			
		||||
    case '/game':
 | 
			
		||||
        $controller = new MapGuesser\Controller\GameController();
 | 
			
		||||
        break;
 | 
			
		||||
    case '/getNewPosition.json':
 | 
			
		||||
        $controller = new MapGuesser\Controller\GetNewPosition();
 | 
			
		||||
        break;
 | 
			
		||||
    case '/':
 | 
			
		||||
        header('Location: ' . $host  . '/game', true, 302);
 | 
			
		||||
        die;
 | 
			
		||||
    default:
 | 
			
		||||
        echo 'Error 404';
 | 
			
		||||
        die;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// demo bounds
 | 
			
		||||
$bounds = new MapGuesser\Geo\Bounds($realPosition);
 | 
			
		||||
$bounds->extend(new MapGuesser\Geo\Position(48.07683,7.35758));
 | 
			
		||||
$bounds->extend(new MapGuesser\Geo\Position(47.57496, 19.08077));
 | 
			
		||||
$view = $controller->run();
 | 
			
		||||
 | 
			
		||||
?>
 | 
			
		||||
header('Content-Type: ' . $view->getContentType() . '; charset=UTF-8');
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>MapGuesser</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/css/mapguesser.css">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="panorama"></div>
 | 
			
		||||
    <div id="guess">
 | 
			
		||||
        <div id="guessMap"></div>
 | 
			
		||||
        <div id="guessButtonContainer">
 | 
			
		||||
            <button id="guessButton" disabled>Guess</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        var realPosition = <?= $realPosition->toJson() ?>;
 | 
			
		||||
        var guessMapBounds = <?= $bounds->toJson() ?>;
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src="static/js/mapguesser.js" async defer></script>
 | 
			
		||||
    <script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>&callback=initialize" async defer></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
echo $view->render();
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,65 @@
 | 
			
		||||
* {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    font-size: 100%;
 | 
			
		||||
    vertical-align: baseline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html, body {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, button {
 | 
			
		||||
    font-family: 'Roboto', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p {
 | 
			
		||||
    font-weight: 300;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bold {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    background-color: #5e77aa;
 | 
			
		||||
    padding: 8px 15px;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button:hover, button:focus {
 | 
			
		||||
    background-color: #29457f;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button:disabled {
 | 
			
		||||
    cursor: no-drop;
 | 
			
		||||
    color: #dddddd;
 | 
			
		||||
    background-color: #808080;
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#loading {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    margin-top: -20px;
 | 
			
		||||
    margin-left: -20px;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#panorama {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
@ -18,6 +74,7 @@ html, body {
 | 
			
		||||
    height: 150px;
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
    transition-property: width, height, opacity;
 | 
			
		||||
    transition-duration: 0.1s;
 | 
			
		||||
    transition-delay: 0.8s;
 | 
			
		||||
@ -26,11 +83,11 @@ html, body {
 | 
			
		||||
#guess:hover {
 | 
			
		||||
    width: 500px;
 | 
			
		||||
    height: 350px;
 | 
			
		||||
    opacity: 1.0;
 | 
			
		||||
    opacity: 0.95;
 | 
			
		||||
    transition-delay: 0s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess #guessMap {
 | 
			
		||||
#guess > #guessMap {
 | 
			
		||||
    height: 115px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    transition-property: height;
 | 
			
		||||
@ -39,7 +96,7 @@ html, body {
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guess:hover #guessMap {
 | 
			
		||||
#guess:hover > #guessMap {
 | 
			
		||||
    height: 315px;
 | 
			
		||||
    transition-delay: 0s;
 | 
			
		||||
}
 | 
			
		||||
@ -50,26 +107,61 @@ html, body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guessButton {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    background-color: #5e77aa;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guessButton:hover, #guessButton:focus {
 | 
			
		||||
    background-color: #29457f;
 | 
			
		||||
    outline: none;
 | 
			
		||||
#result {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50px;
 | 
			
		||||
    left: 50px;
 | 
			
		||||
    right: 50px;
 | 
			
		||||
    bottom: 50px;
 | 
			
		||||
    opacity: 0.95;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    background-color: #ffffff;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#guessButton:disabled {
 | 
			
		||||
    cursor: no-drop;
 | 
			
		||||
    color: #dddddd;
 | 
			
		||||
    background-color: #808080;
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
#resultMap {
 | 
			
		||||
    height: 70%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#resultInfo {
 | 
			
		||||
    height: 30%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#resultInfo > div {
 | 
			
		||||
    height: 25%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#resultInfo p {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#scoreBarBase {
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    width: 60%;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#scoreBar {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    transition-property: width;
 | 
			
		||||
    transition-duration: 2.0s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/static/img/loading.gif
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/static/img/loading.gif
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@ -1,108 +1,248 @@
 | 
			
		||||
Math.deg2rad = function (deg) {
 | 
			
		||||
    return deg * (this.PI / 180.0);
 | 
			
		||||
};
 | 
			
		||||
(function () {
 | 
			
		||||
    var Core = {
 | 
			
		||||
        MAX_SCORE: 1000,
 | 
			
		||||
 | 
			
		||||
var Util = {
 | 
			
		||||
    EARTH_RADIUS_IN_METER: 6371000,
 | 
			
		||||
        realPosition: null,
 | 
			
		||||
        panorama: null,
 | 
			
		||||
        guessMap: null,
 | 
			
		||||
        guessMarker: null,
 | 
			
		||||
        resultMap: null,
 | 
			
		||||
        resultMarkers: { guess: null, real: null },
 | 
			
		||||
        googleLink: null,
 | 
			
		||||
 | 
			
		||||
    calculateDistance: function (position1, position2) {
 | 
			
		||||
        var lat1 = Math.deg2rad(position1.lat);
 | 
			
		||||
        var lng1 = Math.deg2rad(position1.lng);
 | 
			
		||||
        var lat2 = Math.deg2rad(position2.lat);
 | 
			
		||||
        var lng2 = Math.deg2rad(position2.lng);
 | 
			
		||||
        getNewPosition: function () {
 | 
			
		||||
            Core.panorama.setVisible(false);
 | 
			
		||||
 | 
			
		||||
        var latDelta = lat2 - lat1;
 | 
			
		||||
        var lonDelta = lng2 - lng1;
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
        var angle = 2 * Math.asin(
 | 
			
		||||
            Math.sqrt(
 | 
			
		||||
                Math.pow(Math.sin(latDelta / 2), 2) +
 | 
			
		||||
                Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(lonDelta / 2), 2)
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
            var xhr = new XMLHttpRequest();
 | 
			
		||||
            xhr.responseType = 'json';
 | 
			
		||||
            xhr.onreadystatechange = function () {
 | 
			
		||||
                if (this.readyState == 4 && this.status == 200) {
 | 
			
		||||
                    Core.realPosition = this.response.position;
 | 
			
		||||
 | 
			
		||||
        return angle * Util.EARTH_RADIUS_IN_METER;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
                    var sv = new google.maps.StreetViewService();
 | 
			
		||||
                    sv.getPanorama({ location: this.response.position, preference: google.maps.StreetViewPreference.NEAREST }, Core.loadPano);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            xhr.open('GET', 'getNewPosition.json', true);
 | 
			
		||||
            xhr.send();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
var MapManipulator = {
 | 
			
		||||
    rewriteGoogleLink: function () {
 | 
			
		||||
        if (!googleLink) {
 | 
			
		||||
            var anchors = document.getElementById('panorama').getElementsByTagName('a');
 | 
			
		||||
            for (var i = 0; i < anchors.length; i++) {
 | 
			
		||||
                var a = anchors[i];
 | 
			
		||||
                if (a.href.indexOf('maps.google.com/maps') !== -1) {
 | 
			
		||||
                    googleLink = a;
 | 
			
		||||
                    break;
 | 
			
		||||
        loadPano: function (data, status) {
 | 
			
		||||
            if (status !== google.maps.StreetViewStatus.OK) {
 | 
			
		||||
                Core.getNewPosition();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
            Core.panorama.setVisible(true);
 | 
			
		||||
            Core.panorama.setPov({ heading: 0, pitch: 0, zoom: 1 });
 | 
			
		||||
            Core.panorama.setPano(data.location.pano);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        calculateScore: function (distance) {
 | 
			
		||||
            var goodness = 1.0 - distance / Math.sqrt(mapArea);
 | 
			
		||||
 | 
			
		||||
            return Math.pow(Core.MAX_SCORE, goodness);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        calculateScoreBarProperties: function (score) {
 | 
			
		||||
            var percent = Math.ceil((score / Core.MAX_SCORE) * 100);
 | 
			
		||||
 | 
			
		||||
            var color;
 | 
			
		||||
            if (percent >= 90) {
 | 
			
		||||
                color = '#11ca00';
 | 
			
		||||
            } else if (percent >= 10) {
 | 
			
		||||
                color = '#ea9000';
 | 
			
		||||
            } else {
 | 
			
		||||
                color = '#ca1100';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return { width: percent + '%', backgroundColor: color };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        rewriteGoogleLink: function () {
 | 
			
		||||
            if (!Core.googleLink) {
 | 
			
		||||
                var anchors = document.getElementById('panorama').getElementsByTagName('a');
 | 
			
		||||
                for (var i = 0; i < anchors.length; i++) {
 | 
			
		||||
                    var a = anchors[i];
 | 
			
		||||
                    if (a.href.indexOf('maps.google.com/maps') !== -1) {
 | 
			
		||||
                        Core.googleLink = a;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                Core.googleLink.title = 'Google Maps'
 | 
			
		||||
                Core.googleLink.href = 'https://maps.google.com/maps'
 | 
			
		||||
            }, 1);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
        setTimeout(function () {
 | 
			
		||||
            googleLink.title = 'Google Maps'
 | 
			
		||||
            googleLink.href = 'https://maps.google.com/maps'
 | 
			
		||||
        }, 1);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
    var Util = {
 | 
			
		||||
        EARTH_RADIUS_IN_METER: 6371000,
 | 
			
		||||
 | 
			
		||||
var panorama;
 | 
			
		||||
var guessMap;
 | 
			
		||||
var guessMarker;
 | 
			
		||||
var googleLink;
 | 
			
		||||
 | 
			
		||||
function initialize() {
 | 
			
		||||
    panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
 | 
			
		||||
        position: realPosition,
 | 
			
		||||
        pov: {
 | 
			
		||||
            heading: 34,
 | 
			
		||||
            pitch: 10
 | 
			
		||||
        deg2rad: function (deg) {
 | 
			
		||||
            return deg * (Math.PI / 180.0);
 | 
			
		||||
        },
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        linksControl: true,
 | 
			
		||||
        showRoadLabels: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    panorama.addListener('position_changed', function () {
 | 
			
		||||
        MapManipulator.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
        calculateDistance: function (position1, position2) {
 | 
			
		||||
            var lat1 = Util.deg2rad(position1.lat);
 | 
			
		||||
            var lng1 = Util.deg2rad(position1.lng);
 | 
			
		||||
            var lat2 = Util.deg2rad(position2.lat);
 | 
			
		||||
            var lng2 = Util.deg2rad(position2.lng);
 | 
			
		||||
 | 
			
		||||
    panorama.addListener('pov_changed', function () {
 | 
			
		||||
        MapManipulator.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
            var angleCos = Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) +
 | 
			
		||||
                Math.sin(lat1) * Math.sin(lat2);
 | 
			
		||||
 | 
			
		||||
    guessMap = new google.maps.Map(document.getElementById('guessMap'), {
 | 
			
		||||
            if (angleCos > 1.0) {
 | 
			
		||||
                angleCos = 1.0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var angle = Math.acos(angleCos);
 | 
			
		||||
 | 
			
		||||
            return angle * Util.EARTH_RADIUS_IN_METER;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        printDistanceForHuman: function (distance) {
 | 
			
		||||
            if (distance < 1000) {
 | 
			
		||||
                return Number.parseFloat(distance).toFixed(0) + ' m';
 | 
			
		||||
            } else if (distance < 10000) {
 | 
			
		||||
                return Number.parseFloat(distance / 1000).toFixed(2) + ' km';
 | 
			
		||||
            } else if (distance < 100000) {
 | 
			
		||||
                return Number.parseFloat(distance / 1000).toFixed(1) + ' km';
 | 
			
		||||
            } else {
 | 
			
		||||
                return Number.parseFloat(distance / 1000).toFixed(0) + ' km';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Core.guessMap = new google.maps.Map(document.getElementById('guessMap'), {
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        clickableIcons: false,
 | 
			
		||||
        draggableCursor: 'crosshair'
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    guessMap.fitBounds(guessMapBounds);
 | 
			
		||||
    Core.guessMap.fitBounds(guessMapBounds);
 | 
			
		||||
 | 
			
		||||
    guessMap.addListener('click', function (e) {
 | 
			
		||||
        if (guessMarker) {
 | 
			
		||||
            guessMarker.setPosition(e.latLng);
 | 
			
		||||
    Core.guessMap.addListener('click', function (e) {
 | 
			
		||||
        if (Core.guessMarker) {
 | 
			
		||||
            Core.guessMarker.setPosition(e.latLng);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        guessMarker = new google.maps.Marker({
 | 
			
		||||
            map: guessMap,
 | 
			
		||||
        Core.guessMarker = new google.maps.Marker({
 | 
			
		||||
            map: Core.guessMap,
 | 
			
		||||
            position: e.latLng,
 | 
			
		||||
            draggable: true
 | 
			
		||||
            clickable: false,
 | 
			
		||||
            draggable: true,
 | 
			
		||||
            label: {
 | 
			
		||||
                color: '#ffffff',
 | 
			
		||||
                fontFamily: 'Roboto',
 | 
			
		||||
                fontSize: '18px',
 | 
			
		||||
                fontWeight: '500',
 | 
			
		||||
                text: '?'
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        document.getElementById('guessButton').disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.getElementById('guessButton').onclick = function () {
 | 
			
		||||
    if (!guessMarker) {
 | 
			
		||||
        return;
 | 
			
		||||
    Core.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        linksControl: true,
 | 
			
		||||
        showRoadLabels: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.panorama.addListener('position_changed', function () {
 | 
			
		||||
        Core.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.panorama.addListener('pov_changed', function () {
 | 
			
		||||
        Core.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.resultMap = new google.maps.Map(document.getElementById('resultMap'), {
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        clickableIcons: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.getNewPosition();
 | 
			
		||||
 | 
			
		||||
    document.getElementById('guessButton').onclick = function () {
 | 
			
		||||
        if (!Core.guessMarker) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var guessedPosition = Core.guessMarker.getPosition();
 | 
			
		||||
 | 
			
		||||
        this.disabled = true;
 | 
			
		||||
        Core.guessMarker.setMap(null);
 | 
			
		||||
        Core.guessMarker = null;
 | 
			
		||||
 | 
			
		||||
        var distance = Util.calculateDistance(Core.realPosition, { lat: guessedPosition.lat(), lng: guessedPosition.lng() });
 | 
			
		||||
 | 
			
		||||
        document.getElementById('guess').style.visibility = 'hidden';
 | 
			
		||||
        document.getElementById('result').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
        var resultBounds = new google.maps.LatLngBounds();
 | 
			
		||||
        resultBounds.extend(Core.realPosition);
 | 
			
		||||
        resultBounds.extend(guessedPosition);
 | 
			
		||||
 | 
			
		||||
        Core.resultMap.fitBounds(resultBounds);
 | 
			
		||||
 | 
			
		||||
        Core.resultMarkers.real = new google.maps.Marker({
 | 
			
		||||
            map: Core.resultMap,
 | 
			
		||||
            position: Core.realPosition,
 | 
			
		||||
            clickable: true,
 | 
			
		||||
            draggable: false
 | 
			
		||||
        });
 | 
			
		||||
        Core.resultMarkers.guess = new google.maps.Marker({
 | 
			
		||||
            map: Core.resultMap,
 | 
			
		||||
            position: guessedPosition,
 | 
			
		||||
            clickable: false,
 | 
			
		||||
            draggable: false,
 | 
			
		||||
            label: {
 | 
			
		||||
                color: '#ffffff',
 | 
			
		||||
                fontFamily: 'Roboto',
 | 
			
		||||
                fontSize: '18px',
 | 
			
		||||
                fontWeight: '500',
 | 
			
		||||
                text: '?'
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Core.resultMarkers.real.addListener('click', function () {
 | 
			
		||||
            window.open('https://www.google.com/maps/search/?api=1&query=' + Core.realPosition.lat + ',' + Core.realPosition.lng, '_blank');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        document.getElementById('distance').innerHTML = Util.printDistanceForHuman(distance);
 | 
			
		||||
 | 
			
		||||
        var score = Core.calculateScore(distance);
 | 
			
		||||
        var scoreBarProperties = Core.calculateScoreBarProperties(score);
 | 
			
		||||
 | 
			
		||||
        document.getElementById('score').innerHTML = Number.parseFloat(score).toFixed(0);
 | 
			
		||||
 | 
			
		||||
        var scoreBar = document.getElementById('scoreBar');
 | 
			
		||||
        scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
 | 
			
		||||
        scoreBar.style.width = scoreBarProperties.width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var guessedPosition = guessMarker.getPosition();
 | 
			
		||||
    var distance = Util.calculateDistance(realPosition, { lat: guessedPosition.lat(), lng: guessedPosition.lng() });
 | 
			
		||||
    document.getElementById('continueButton').onclick = function () {
 | 
			
		||||
        document.getElementById('scoreBar').style.width = '0';
 | 
			
		||||
 | 
			
		||||
    alert('You were ' + distance + 'm close!');
 | 
			
		||||
        Core.resultMarkers.real.setMap(null);
 | 
			
		||||
        Core.resultMarkers.real = null;
 | 
			
		||||
        Core.resultMarkers.guess.setMap(null);
 | 
			
		||||
        Core.resultMarkers.guess = null;
 | 
			
		||||
 | 
			
		||||
    this.blur();
 | 
			
		||||
}
 | 
			
		||||
        document.getElementById('guess').style.visibility = 'visible';
 | 
			
		||||
        document.getElementById('result').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
        Core.guessMap.fitBounds(guessMapBounds);
 | 
			
		||||
 | 
			
		||||
        Core.getNewPosition();
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								scripts/install.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								scripts/install.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
 | 
			
		||||
 | 
			
		||||
. ${ROOT_DIR}/.env
 | 
			
		||||
 | 
			
		||||
if [ -f ${ROOT_DIR}/installed ]; then
 | 
			
		||||
    echo "Mapguesser is already installed! To force reinstall, delete file 'installed' from the root directory!"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo "Installing MapGuesser DB..."
 | 
			
		||||
 | 
			
		||||
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/db/mapguesser.sql
 | 
			
		||||
 | 
			
		||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
 | 
			
		||||
    echo "Uglifying JS and CSS files..."
 | 
			
		||||
 | 
			
		||||
    uglifyjs ${ROOT_DIR}/public/static/js/mapguesser.js -c -m -o ${ROOT_DIR}/public/static/js/mapguesser.js
 | 
			
		||||
    cleancss ${ROOT_DIR}/public/static/css/mapguesser.css -o ${ROOT_DIR}/public/static/css/mapguesser.css
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
touch ${ROOT_DIR}/installed
 | 
			
		||||
							
								
								
									
										12
									
								
								scripts/update.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								scripts/update.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
 | 
			
		||||
 | 
			
		||||
. ${ROOT_DIR}/.env
 | 
			
		||||
 | 
			
		||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
 | 
			
		||||
    echo "Uglifying JS and CSS files..."
 | 
			
		||||
 | 
			
		||||
    uglifyjs ${ROOT_DIR}/public/static/js/mapguesser.js -c -m -o ${ROOT_DIR}/public/static/js/mapguesser.js
 | 
			
		||||
    cleancss ${ROOT_DIR}/public/static/css/mapguesser.css -o ${ROOT_DIR}/public/static/css/mapguesser.css
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										8
									
								
								src/Controller/ControllerInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Controller/ControllerInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\View\ViewBase;
 | 
			
		||||
 | 
			
		||||
interface ControllerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function run(): ViewBase;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/Controller/GameController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/Controller/GameController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
use MapGuesser\Util\Geo\Position;
 | 
			
		||||
use MapGuesser\View\HtmlView;
 | 
			
		||||
use MapGuesser\View\ViewBase;
 | 
			
		||||
use mysqli;
 | 
			
		||||
 | 
			
		||||
class GameController implements ControllerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function run(): ViewBase
 | 
			
		||||
    {
 | 
			
		||||
        $mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
 | 
			
		||||
 | 
			
		||||
        // demo map
 | 
			
		||||
        $mapId = 1;
 | 
			
		||||
 | 
			
		||||
        $stmt = $mysql->prepare('SELECT bound_south_lat, bound_west_lng, bound_north_lat, bound_east_lng FROM maps WHERE id=?');
 | 
			
		||||
        $stmt->bind_param("i", $mapId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $map = $stmt->get_result()->fetch_assoc();
 | 
			
		||||
 | 
			
		||||
        // using RAND() for the time being, could be changed in the future
 | 
			
		||||
        $stmt = $mysql->prepare('SELECT lat, lng FROM places WHERE map_id=? ORDER BY RAND() LIMIT 1');
 | 
			
		||||
        $stmt->bind_param("i", $mapId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $place = $stmt->get_result()->fetch_assoc();
 | 
			
		||||
 | 
			
		||||
        $realPosition = new Position($place['lat'], $place['lng']);
 | 
			
		||||
        $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
 | 
			
		||||
        $data = compact('bounds');
 | 
			
		||||
        return new HtmlView('game', $data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/Controller/GetNewPosition.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/Controller/GetNewPosition.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Util\Geo\Position;
 | 
			
		||||
use MapGuesser\View\JsonView;
 | 
			
		||||
use MapGuesser\View\ViewBase;
 | 
			
		||||
use mysqli;
 | 
			
		||||
 | 
			
		||||
class GetNewPosition implements ControllerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function run(): ViewBase
 | 
			
		||||
    {
 | 
			
		||||
        $mysql = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
 | 
			
		||||
 | 
			
		||||
        // demo map
 | 
			
		||||
        $mapId = 1;
 | 
			
		||||
 | 
			
		||||
        // using RAND() for the time being, could be changed in the future
 | 
			
		||||
        $stmt = $mysql->prepare('SELECT lat, lng FROM places WHERE map_id=? ORDER BY RAND() LIMIT 1');
 | 
			
		||||
        $stmt->bind_param("i", $mapId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $place = $stmt->get_result()->fetch_assoc();
 | 
			
		||||
 | 
			
		||||
        $position = new Position($place['lat'], $place['lng']);
 | 
			
		||||
 | 
			
		||||
        $data = ['position' => $position->toArray()];
 | 
			
		||||
        return new JsonView($data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
<?php namespace MapGuesser\Geo;
 | 
			
		||||
 | 
			
		||||
class Position
 | 
			
		||||
{
 | 
			
		||||
    private float $lat;
 | 
			
		||||
    private float $lng;
 | 
			
		||||
 | 
			
		||||
    public function __construct(float $lat, float $lng)
 | 
			
		||||
    {
 | 
			
		||||
        $this->lat = $lat;
 | 
			
		||||
        $this->lng = $lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLat(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLng(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toJson(): string
 | 
			
		||||
    {
 | 
			
		||||
        return json_encode([
 | 
			
		||||
            'lat' => $this->lat,
 | 
			
		||||
            'lng' => $this->lng,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,9 @@
 | 
			
		||||
<?php namespace MapGuesser\Geo;
 | 
			
		||||
<?php namespace MapGuesser\Util\Geo;
 | 
			
		||||
 | 
			
		||||
class Bounds
 | 
			
		||||
{
 | 
			
		||||
    const ONE_DEGREE_OF_LATITUDE_IN_METER = 111132.954;
 | 
			
		||||
 | 
			
		||||
    private float $southLat;
 | 
			
		||||
    private float $westLng;
 | 
			
		||||
 | 
			
		||||
@ -10,13 +12,27 @@ class Bounds
 | 
			
		||||
 | 
			
		||||
    private bool $initialized = false;
 | 
			
		||||
 | 
			
		||||
    public function __construct(Position $position = null)
 | 
			
		||||
    public static function createWithPosition(Position $position): Bounds
 | 
			
		||||
    {
 | 
			
		||||
        if ($position === null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $instance = new static();
 | 
			
		||||
 | 
			
		||||
        $this->initialize($position);
 | 
			
		||||
        $instance->initialize($position);
 | 
			
		||||
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
 | 
			
		||||
    {
 | 
			
		||||
        $instance = new static();
 | 
			
		||||
 | 
			
		||||
        $instance->southLat = $southLat;
 | 
			
		||||
        $instance->westLng = $westLng;
 | 
			
		||||
        $instance->northLat = $northLat;
 | 
			
		||||
        $instance->eastLng = $eastLng;
 | 
			
		||||
 | 
			
		||||
        $instance->initialized = true;
 | 
			
		||||
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function extend(Position $position): void
 | 
			
		||||
@ -47,6 +63,18 @@ class Bounds
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function calculateApproximateArea(): float
 | 
			
		||||
    {
 | 
			
		||||
        $dLat = $this->northLat - $this->southLat;
 | 
			
		||||
        $dLng = $this->eastLng - $this->westLng;
 | 
			
		||||
 | 
			
		||||
        $m = $dLat * static::ONE_DEGREE_OF_LATITUDE_IN_METER;
 | 
			
		||||
        $a = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_METER * cos(deg2rad($this->northLat));
 | 
			
		||||
        $c = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_METER * cos(deg2rad($this->southLat));
 | 
			
		||||
 | 
			
		||||
        return $m * ($a + $c) / 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toJson(): string
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->initialized) {
 | 
			
		||||
							
								
								
									
										56
									
								
								src/Util/Geo/Position.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/Util/Geo/Position.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
<?php namespace MapGuesser\Util\Geo;
 | 
			
		||||
 | 
			
		||||
class Position
 | 
			
		||||
{
 | 
			
		||||
    const EARTH_RADIUS_IN_METER = 6371000;
 | 
			
		||||
 | 
			
		||||
    private float $lat;
 | 
			
		||||
    private float $lng;
 | 
			
		||||
 | 
			
		||||
    public function __construct(float $lat, float $lng)
 | 
			
		||||
    {
 | 
			
		||||
        $this->lat = $lat;
 | 
			
		||||
        $this->lng = $lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLat(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLng(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function calculateDistanceTo(Position $otherPosition): float
 | 
			
		||||
    {
 | 
			
		||||
        $lat1 = deg2rad($this->lat);
 | 
			
		||||
        $lng1 = deg2rad($this->lng);
 | 
			
		||||
        $lat2 = deg2rad($otherPosition->lat);
 | 
			
		||||
        $lng2 = deg2rad($otherPosition->lng);
 | 
			
		||||
 | 
			
		||||
        $angleCos = cos($lat1) * cos($lat2) * cos($lng2 - $lng1) + sin($lat1) * sin($lat2);
 | 
			
		||||
 | 
			
		||||
        if ($angleCos > 1.0) {
 | 
			
		||||
            $angleCos = 1.0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $angle = acos($angleCos);
 | 
			
		||||
 | 
			
		||||
        return $angle * static::EARTH_RADIUS_IN_METER;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toArray(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'lat' => $this->lat,
 | 
			
		||||
            'lng' => $this->lng,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toJson(): string
 | 
			
		||||
    {
 | 
			
		||||
        return json_encode($this->toArray());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/View/HtmlView.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/View/HtmlView.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<?php namespace MapGuesser\View;
 | 
			
		||||
 | 
			
		||||
class HtmlView extends ViewBase
 | 
			
		||||
{
 | 
			
		||||
    private string $template;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $template, array &$data = [])
 | 
			
		||||
    {
 | 
			
		||||
        $this->template = $template;
 | 
			
		||||
        $this->data = &$data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function &render(): string
 | 
			
		||||
    {
 | 
			
		||||
        extract($this->data);
 | 
			
		||||
 | 
			
		||||
        ob_start();
 | 
			
		||||
        require ROOT . '/views/' . $this->template . '.php';
 | 
			
		||||
        $content = ob_get_contents();
 | 
			
		||||
        ob_end_clean();
 | 
			
		||||
 | 
			
		||||
        return $content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getContentType(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'text/html';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/View/JsonView.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/View/JsonView.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
<?php namespace MapGuesser\View;
 | 
			
		||||
 | 
			
		||||
class JsonView extends ViewBase
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(array &$data = [])
 | 
			
		||||
    {
 | 
			
		||||
        $this->data = &$data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function &render(): string
 | 
			
		||||
    {
 | 
			
		||||
        $content = json_encode($this->data);
 | 
			
		||||
 | 
			
		||||
        return $content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getContentType(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'application/json';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/View/ViewBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/View/ViewBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
<?php namespace MapGuesser\View;
 | 
			
		||||
 | 
			
		||||
abstract class ViewBase
 | 
			
		||||
{
 | 
			
		||||
    protected array $data;
 | 
			
		||||
 | 
			
		||||
    public function &getData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract public function &render(): string;
 | 
			
		||||
 | 
			
		||||
    abstract public function getContentType(): string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								views/game.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								views/game.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>MapGuesser</title>
 | 
			
		||||
    <link href="static/css/mapguesser.css" rel="stylesheet">
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="loading">
 | 
			
		||||
        <img src="static/img/loading.gif">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="panorama"></div>
 | 
			
		||||
    <div id="guess">
 | 
			
		||||
        <div id="guessMap"></div>
 | 
			
		||||
        <div id="guessButtonContainer">
 | 
			
		||||
            <button id="guessButton" disabled>Guess</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="result">
 | 
			
		||||
        <div id="resultMap"></div>
 | 
			
		||||
        <div id="resultInfo">
 | 
			
		||||
            <div>
 | 
			
		||||
                <p>You were <span id="distance" class="bold"></span> close.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <p>You earned <span id="score" class="bold"></span> points.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <div id="scoreBarBase"><div id="scoreBar"></div></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <button id="continueButton">Continue</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        var mapArea = <?= $bounds->calculateApproximateArea() ?>;
 | 
			
		||||
        var guessMapBounds = <?= $bounds->toJson() ?>;
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
 | 
			
		||||
    <script src="static/js/mapguesser.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user