commit a61d2951d75ad30fc9d875bfdc6daead3d1e8443 Author: Pőcze Bence Date: Sun May 17 19:29:06 2020 +0200 MAPG-2 Init project MapGuesser * add composer.json and basic environment * add basic classes * add basic map with JS and CSS (with demo functionality) * add files for docker-compose * add launch.json for Docker debug * add README diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1570bcb --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +DEV=true +GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..226ca36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +vendor diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..eafbd3f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for XDebug in Docker", + "type": "php", + "request": "launch", + "port": 9000, + "pathMappings": { + "/var/www/mapguesser": "${workspaceRoot}", + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..899fe7b --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# MapGuesser + +This is the MapGuesser Application project. + +License: GNU GPL 3.0 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8010c24 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "esoko/mapguesser", + "type": "project", + "description": "MapGuesser Application", + "license": "GNU GPL 3.0", + "require": { + "vlucas/phpdotenv": "^4.1" + }, + "require-dev": {}, + "autoload": { + "psr-4": { + "MapGuesser\\": "src" + } + }, + "scripts": { + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ] + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..4bbcd4f --- /dev/null +++ b/composer.lock @@ -0,0 +1,220 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "29431cf83ee884f01ee954b1068c0ccc", + "packages": [ + { + "name": "phpoption/phpoption", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/4acfd6a4b33a509d8c88f50e5222f734b6aeebae", + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.3", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "time": "2020-03-21T18:07:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:14:59+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v4.1.5", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/539bb6927c101a5605d31d11a2d17185a2ce2bf1", + "reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "phpoption/phpoption": "^1.7.2", + "symfony/polyfill-ctype": "^1.9" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.3", + "ext-filter": "*", + "ext-pcre": "*", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator.", + "ext-pcre": "Required to use most of the library." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://vancelucas.com/" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2020-05-02T14:08:57+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e00f9aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3' +services: + app: + build: ./docker + ports: + - 80:80 + volumes: + - .:/var/www/mapguesser + links: + - 'mariadb' + mariadb: + image: mariadb:10.1 + volumes: + - mysql:/var/lib/mysql + environment: + #TZ: Europe/Budapest + MYSQL_ROOT_PASSWORD: 'root' + MYSQL_DATABASE: 'mapguesser' + MYSQL_USER: 'mapguesser' + MYSQL_PASSWORD: 'mapguesser' +volumes: + mysql: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..16f7048 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:focal + +ENV DEBIAN_FRONTEND noninteractive + +# Install Apache, PHP and further necessary packages +RUN apt update +RUN apt install -y curl git 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 +RUN a2enconf php7.4-fpm +COPY configs/apache.conf /etc/apache2/sites-available/000-default.conf +RUN echo "xdebug.remote_enable = 1" >> /etc/php/7.4/mods-available/xdebug.ini +RUN echo "xdebug.remote_autostart = 1" >> /etc/php/7.4/mods-available/xdebug.ini +RUN echo "xdebug.remote_connect_back = 1" >> /etc/php/7.4/mods-available/xdebug.ini + +# Install Composer +COPY scripts/install-composer.sh install-composer.sh +RUN ./install-composer.sh + +EXPOSE 80 +VOLUME /var/www/mapguesser +WORKDIR /var/www/mapguesser + +ENTRYPOINT /usr/sbin/php-fpm7.4 -F & /usr/sbin/apache2ctl -DFOREGROUND diff --git a/docker/configs/apache.conf b/docker/configs/apache.conf new file mode 100644 index 0000000..36c8063 --- /dev/null +++ b/docker/configs/apache.conf @@ -0,0 +1,15 @@ + + ServerName mapguesser-dev.ch + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/mapguesser/public + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + + Options FollowSymLinks + AllowOverride All + Require all granted + diff --git a/docker/scripts/install-composer.sh b/docker/scripts/install-composer.sh new file mode 100755 index 0000000..f55d62e --- /dev/null +++ b/docker/scripts/install-composer.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +EXPECTED_CHECKSUM="$(curl -s 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');")" + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] +then + >&2 echo 'ERROR: Invalid installer checksum' + rm composer-setup.php + exit 1 +fi + +php composer-setup.php --install-dir=/usr/local/bin --filename=composer +RESULT=$? +rm composer-setup.php +exit $RESULT diff --git a/main.php b/main.php new file mode 100644 index 0000000..bd37059 --- /dev/null +++ b/main.php @@ -0,0 +1,14 @@ +load(); + +if (!empty($_ENV['DEV'])) { + error_reporting(E_ALL); + + ini_set('display_errors', '1'); +} else { + ini_set('display_errors', '0'); +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..2bb1e39 --- /dev/null +++ b/public/index.php @@ -0,0 +1,40 @@ +extend(new MapGuesser\Geo\Position(48.07683,7.35758)); +$bounds->extend(new MapGuesser\Geo\Position(47.57496, 19.08077)); + +?> + + + + + + + MapGuesser + + + + +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/public/static/css/mapguesser.css b/public/static/css/mapguesser.css new file mode 100644 index 0000000..71e354e --- /dev/null +++ b/public/static/css/mapguesser.css @@ -0,0 +1,75 @@ +html, body { + height: 100%; + margin: 0; + padding: 0; +} + +#panorama { + height: 100%; + width: 100%; + z-index: 1; +} + +#guess { + position: absolute; + right: 20px; + bottom: 20px; + width: 250px; + height: 150px; + opacity: 0.5; + z-index: 2; + transition-property: width, height, opacity; + transition-duration: 0.1s; + transition-delay: 0.8s; +} + +#guess:hover { + width: 500px; + height: 350px; + opacity: 1.0; + transition-delay: 0s; +} + +#guess #guessMap { + height: 115px; + width: 100%; + transition-property: height; + transition-duration: 0.1s; + transition-delay: 0.8s; + border-radius: 3px; +} + +#guess:hover #guessMap { + height: 315px; + transition-delay: 0s; +} + +#guessButtonContainer { + margin-top: 5px; + height: 30px; +} + +#guessButton { + cursor: pointer; + font-size: 14px; + font-weight: bold; + color: #ffffff; + background-color: #5e77aa; + border: none; + border-radius: 3px; + width: 100%; + height: 100%; + box-sizing: border-box; +} + +#guessButton:hover, #guessButton:focus { + background-color: #29457f; + outline: none; +} + +#guessButton:disabled { + cursor: no-drop; + color: #dddddd; + background-color: #808080; + opacity: 0.7; +} diff --git a/public/static/js/mapguesser.js b/public/static/js/mapguesser.js new file mode 100644 index 0000000..bf76f05 --- /dev/null +++ b/public/static/js/mapguesser.js @@ -0,0 +1,108 @@ +Math.deg2rad = function (deg) { + return deg * (this.PI / 180.0); +}; + +var Util = { + EARTH_RADIUS_IN_METER: 6371000, + + 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); + + var latDelta = lat2 - lat1; + var lonDelta = lng2 - lng1; + + 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) + ) + ); + + return angle * Util.EARTH_RADIUS_IN_METER; + } +}; + +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; + } + } + } + + setTimeout(function () { + googleLink.title = 'Google Maps' + googleLink.href = 'https://maps.google.com/maps' + }, 1); + } +}; + +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 + }, + disableDefaultUI: true, + linksControl: true, + showRoadLabels: false + }); + + panorama.addListener('position_changed', function () { + MapManipulator.rewriteGoogleLink(); + }); + + panorama.addListener('pov_changed', function () { + MapManipulator.rewriteGoogleLink(); + }); + + guessMap = new google.maps.Map(document.getElementById('guessMap'), { + disableDefaultUI: true, + clickableIcons: false, + draggableCursor: 'crosshair' + }); + + guessMap.fitBounds(guessMapBounds); + + guessMap.addListener('click', function (e) { + if (guessMarker) { + guessMarker.setPosition(e.latLng); + return; + } + + guessMarker = new google.maps.Marker({ + map: guessMap, + position: e.latLng, + draggable: true + }); + + document.getElementById('guessButton').disabled = false; + }); +} + +document.getElementById('guessButton').onclick = function () { + if (!guessMarker) { + return; + } + + var guessedPosition = guessMarker.getPosition(); + var distance = Util.calculateDistance(realPosition, { lat: guessedPosition.lat(), lng: guessedPosition.lng() }); + + alert('You were ' + distance + 'm close!'); + + this.blur(); +} diff --git a/src/Geo/Bounds.php b/src/Geo/Bounds.php new file mode 100644 index 0000000..f813f0e --- /dev/null +++ b/src/Geo/Bounds.php @@ -0,0 +1,76 @@ +initialize($position); + } + + public function extend(Position $position): void + { + if (!$this->initialized) { + $this->initialize($position); + + return; + } + + $lat = $position->getLat(); + $lng = $position->getLng(); + + if ($lat < $this->southLat) { + $this->southLat = $lat; + } + + if ($lng < $this->westLng) { + $this->westLng = $lng; + } + + if ($lat > $this->northLat) { + $this->northLat = $lat; + } + + if ($lng > $this->eastLng) { + $this->eastLng = $lng; + } + } + + public function toJson(): string + { + if (!$this->initialized) { + throw new \Exception("Bounds are not initialized!"); + } + + return json_encode([ + 'south' => $this->southLat, + 'west' => $this->westLng, + 'north' => $this->northLat, + 'east' => $this->eastLng, + ]); + } + + private function initialize(Position $position) + { + $lat = $position->getLat(); + $lng = $position->getLng(); + + $this->northLat = $lat; + $this->westLng = $lng; + $this->southLat = $lat; + $this->eastLng = $lng; + + $this->initialized = true; + } +} diff --git a/src/Geo/Position.php b/src/Geo/Position.php new file mode 100644 index 0000000..c79d22b --- /dev/null +++ b/src/Geo/Position.php @@ -0,0 +1,31 @@ +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, + ]); + } +}