La Bureautique vaincra.

This commit is contained in:
Boris 2022-10-21 21:26:41 +02:00
commit 833b9147c1
57 changed files with 2291 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cache/
tests/

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Ze Ğ1 jeux
> Life is short.
> Play more.

BIN
assets/img/Armurerie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
assets/img/Atelier.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/img/Bunker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/img/Dispensaire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/img/Entrepot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
assets/img/Ferme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
assets/img/Garage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
assets/img/Maison.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/img/Mirador.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/img/Puits.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
assets/img/f1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/img/f2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
assets/img/f3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/img/f4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
assets/img/marq2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/img/marq7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/img/marq8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/img/yourte.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

94
config.php Normal file
View File

@ -0,0 +1,94 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
session_start();
define('DEFAULT_RADIUS', 50);
define('TAILLE_SPRITE', 32);
define('DEMI_TAILLE_SPRITE', (TAILLE_SPRITE/2));
define('SONAR_DURATION', 5);
define("LAT_ASTROPORT_ONE", 44.22484418236386);
define("LON_ASTROPORT_ONE", 1.6395813014177014);
class Player {
private $pubkey;
private $name;
function __construct ($pubkey, $name) {
$this->pubkey = $pubkey;
$this->name = $name;
}
function getPubkey () {
return $this->pubkey;
}
function getName () {
return $this->name;
}
}
$players = [
new Player('2L8vaYixCf97DMT8SistvQFeBj7vb6RQL7tvwyiv1XVH', 'Fred'),
new Player('25zB1gSC7Qhwnx463cuDLDCKLRVieLAgFiPbYq6jVHG9', 'Boris'),
new Player('u3rNW7CzqDC5V4L3FGVbSPsrBd78y8pkRACKHtBtCPx', 'Syoul'),
new Player('8PTThXiUSwwuPoqQWw3tuAn4MpvzQzpKhs6LMuiozS7Z', 'kimamila')
];
if (!isset($_SESSION['radius'])) {
$_SESSION['radius'] = DEFAULT_RADIUS;
}
/*
=====================
Games
=====================
*/
$games = [
'spationaute' => [
'title' => 'SpaceĞ1',
'description' => 'Les extraterrestres viennent terraformer ta planète'
],
'magie' => [
'title' => 'Ğ1Quest',
'description' => 'Ğarry Potier à l\'école des sourciers.'
]
];
define('DEFAULT_GAME', 'spationaute');
if (!isset($_SESSION['gameId'])) {
$_SESSION['gameId'] = DEFAULT_GAME;
}
require_once('functions.php');

17
footer.php Normal file
View File

@ -0,0 +1,17 @@
</main>
<footer>
<p>Created by La Bureautique Incorporated, STI (Société Totalement Irresponsable) au capital de 200 000 Ğ1.</p>
</footer>
<?php
echo '
<script src="themes/'. $_SESSION['gameId'] .'/layout.js"></script>
<script src="themes/'. $_SESSION['gameId'] .'/deco.js"></script>
';
?>
</body>
</html>

34
functions.php Normal file
View File

@ -0,0 +1,34 @@
<?php
function moisFr ($n) {
switch ($n) {
case 1:
return "janvier";
case 2:
return "février";
case 3:
return "mars";
case 4:
return "avril";
case 5:
return "mai";
case 6:
return "juin";
case 7:
return "juillet";
case 8:
return "août";
case 9:
return "septembre";
case 10:
return "octobre";
case 11:
return "novembre";
case 12:
return "décembre";
}
}

35
geoloc.php Normal file
View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Geoloc</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<body>
<button onclick="getLocation()">Me géolocaliser</button>
<p id="result">
</p>
<script>
var result = document.getElementById('result');
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
result.innerHTML = "Geolocation is not supported by this browser.";
}
}
function showPosition(position) {
result.innerHTML = "Latitude: " + position.coords.latitude +
"<br>Longitude: " + position.coords.longitude;
}
</script>
</body>
</html>

24
header.php Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>La bureautique</title>
<?php
echo '
<link rel="stylesheet" type="text/css" href="themes/'. $_SESSION['gameId'] .'/layout.css" />
<link rel="stylesheet" type="text/css" href="themes/'. $_SESSION['gameId'] .'/deco.css" />
';
?>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<?php
$bodyIds = !isset($bodyIds) ? '' : ' id="'. $bodyIds .'"';
?>
<body<?php echo $bodyIds; ?>>
<!--
<h1>La bureautique</h1>
-->
<main>

43
index.php Normal file
View File

@ -0,0 +1,43 @@
<?php
require_once('config.php');
require_once('lib/Gchange.class.php');
require_once('lib/Location.class.php');
$bodyIds = 'home';
include_once('header.php');
$gchange = new Gchange();
echo '
<h1>Ze Ğ1 jeux</h1>
<p>
À quel jeu voulez-vous jouer ?
</p>
<dl>';
foreach ($games as $gameId => $game) {
echo '
<dt>
<a href="select_game.php?gameId='. $gameId .'">
'. $game['title'] .'
</a>
</dt>
<dd>
'. $game['description'] .'
</dd>
';
}
echo '
</dl>';
include_once('footer.php');

50
lib/CesiumPlus.class.php Normal file
View File

@ -0,0 +1,50 @@
<?php
require_once('DAO.class.php');
class CesiumPlus {
private $dao;
public function __construct () {
$this->dao = DAO::getInstance();
}
public function getNearbyUsers ($lat, $lon, $radius) {
$n = 15;
$queryParams = [
'size' => $n,
'query' => [
'bool' => [
'must' => [
[
'geo_distance' => [
"distance" => $radius . 'km',
"geoPoint"=> [
"lat" => $lat,
"lon" => $lon
]
]
]
]
]
],
"sort" => [
[ "time" => ["order" => "desc"] ],
"_score"
]
];
$json = $this->dao->fetchJson('/user/profile/_search?pretty', 'gchange', $queryParams);
$result = json_decode($json);
return $result->hits->hits;
}
}

588
lib/DAO.class.php Normal file
View File

@ -0,0 +1,588 @@
<?php
date_default_timezone_set('Europe/Paris');
class DAO {
/**********************
* Constants
**********************/
const PUBKEY_FORMAT = '#^[a-zA-Z1-9]{43,44}$#';
const DATE_FORMAT = 'Y-m-d';
private $units = ['quantitative','relative'];
private $truePossibleValues = ['true','1', 'yes'];
private $qrCodesFolder = __DIR__ . '/img/qrcodes';
private $qrCodePath = NULL;
private $logosFolder = __DIR__ . '/img/logos';
private $logo = NULL;
private $logoPath = NULL;
private $validDisplayTypes = ['img', 'svg', 'html'];
private $cacheDir = __DIR__ . '/../cache/';
private $isActivatedCache = true;
private $cacheLongevity = 10800; // in seconds
public static $dao;
/**********************
* General parameters
**********************/
private $pubkey;
private $nodes = [
'gchange' => [
'data.gchange.fr'
],
'cesiumplus' => [
'g1.data.le-sou.org',
'g1.data.duniter.fr'
],
'duniter' => [
'duniter.g1.1000i100.fr',
'duniter-g1.p2p.legal',
'duniter.normandie-libre.fr',
'g1.mithril.re',
'g1.presles.fr',
'duniter.vincentux.fr',
'g1.le-sou.org',
'g1.donnadieu.fr',
]
];
private $nodeTimeout = [
'duniter' => 2,
'cesiumplus' => 5,
'gchange' => 5,
];
private $nodeTimeoutIncrement = [
'duniter' => 2,
'cesiumplus' => 10,
'gchange' => 10
];
private $node = NULL;
private $unit = 'quantitative';
/**********************
* Methods
**********************/
public function __construct () {
}
public static function getInstance () {
if (!isset(DAO::$dao)) {
DAO::$dao = new DAO();
}
return DAO::$dao;
}
private function setUnit ($unit) {
if (!empty($unit)) {
if (!in_array($unit, $this->units)) {
$out = [];
$out[] = _('L\'unité renseignée n\'existe pas.');
$out[] = _('Vérifiez votre synthaxe.');
$this->decease($out);
} else {
$this->unit = $unit;
}
}
}
public function decease ($errorMsgs) {
if (!is_array($errorMsgs)) {
$errorMsgs = explode("\n", $errorMsgs);
}
if ($this->displayType == 'img') {
$source = imagecreatetruecolor(500, 200);
$bgColor = imagecolorallocate($source,
255, 255, 255);
imagefill($source,
0, 0,
$bgColor);
$txtColor = imagecolorallocate($source,
0, 0, 0);
$errorMsgFontSize = 3;
$x = 5;
$y = 5;
foreach ($errorMsgs as $msg) {
imagestring($source, $errorMsgFontSize, $x, $y, utf8_decode($msg), $txtColor);
$y += $errorMsgFontSize + 20;
}
imagepng($source);
imagedestroy($source);
} else if ($this->displayType == 'svg') {
echo '<?xml version="1.0" encoding="utf-8"?>
<svg width="580"
height="224"
style="fill:black;"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g style="font-family:sans-serif;">';
$x = 25;
$y = 25;
foreach ($errorMsgs as $msg) {
echo '
<text
style="font-size:.8rem;"
x="'. $x .'"
y="'. $y . '"
dominant-baseline="hanging">
'. $msg . '
</text>';
$y += 25;
}
echo '
</g>
</svg>';
} else {
ob_get_clean(); // to prevent error message to display inside an HTML container (case of error generated by get method calls)
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>'. _('Erreur critique') . '</title>
<style>
div {
overflow: auto;
word-wrap: break-word;
background-color: hsl(0, 100%, 69%);
color: hsl(0, 100%, 19%);
margin: 1em;
padding: 1em;
border-radius: 1em;
position: fixed;
top: 0;
left: 0;
width: calc(100% - 4em);
max-height: calc(100vh - 4em);
}
</style>
</head>
<body>
<div>';
foreach ($errorMsgs as $msg) {
echo '<p>' . $msg . '</p>';
}
echo '
</div>
</body>
</html>';
}
exit;
}
public function printUnit () {
if ($this->unit == 'relative') {
if ($this->displayType == 'img') {
return _('DUĞ1');
} else {
return _('DU<sub>Ğ1</sub>');
}
} else {
return _('Ğ1');
}
}
public function convertIntoChosenUnit ($amountInQuantitative) {
if ($this->unit == 'quantitative') {
return $amountInQuantitative;
} else {
if (!isset($this->startDateUdAmount)) {
$this->startDateUdAmount = $this->getUdAmount($this->startDate);
}
return round($amountInQuantitative / $this->startDateUdAmount, 2);
}
}
public function addNode ($node) {
$node = htmlspecialchars($node);
$this->nodes = array_unique(
array_merge(
(array)$node,
$this->nodes
)
);
}
public function addNodes ($nodes) {
if (!is_array($nodes)) {
$nodes = explode(' ', $nodes);
}
foreach ($nodes as $node) {
$this->addNode($node);
}
}
/**
* @return $nodes array
*/
public function getNodesList ($nodeType = 'duniter') {
switch ($nodeType) {
case 'gchange':
$nodesFilename = 'nodes-gchange';
break;
case 'cesiumplus':
$nodesFilename = 'nodes-cesiumplus';
break;
default:
$nodesFilename = 'nodes';
break;
}
$nodesFilename .= '.txt';
$nodesFullpath = $this->cacheDir . $nodesFilename;
$nodes = $this->nodes[$nodeType];
if ($this->isActivatedCache) {
if (!file_exists($nodesFullpath)) {
shuffle($nodes);
$this->cacheNodes($nodes, $nodeType);
} else {
$nodesStr = file_get_contents($nodesFullpath);
$nodes = explode("\n", $nodesStr);
}
} else {
shuffle($nodes);
}
return $nodes;
}
protected function cacheNodes ($nodes, $nodeType = 'duniter') {
switch ($nodeType) {
case 'gchange':
$nodesFilename = 'nodes-gchange';
break;
case 'cesiumplus':
$nodesFilename = 'nodes-cesiumplus';
break;
default:
$nodesFilename = 'nodes';
break;
}
$nodesFilename .= '.txt';
if (!file_exists($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
file_put_contents($this->cacheDir . $nodesFilename, implode("\n", $nodes));
}
protected function saveNodes ($nodes, $nodeType = 'duniter') {
$this->nodes[$nodeType] = $nodes;
}
protected function fetchJson_aux ($nodes, $uri, $nodeType, $queryParams, $nodesNb, $nodeTimeout) {
// $header = 'Content-Type: application/x-www-form-urlencoded';
// $header = "Content-Type: text/xml\r\n";
if (!empty($queryParams)) {
$opts = [
'http' => [
'method' => 'POST',
'content' => json_encode($queryParams),
// 'header' => $header,
'timeout' => $nodeTimeout
]
];
} else {
$opts = [
'http' => [
'method' => 'GET',
'timeout' => $nodeTimeout
]
];
}
$streamContext = stream_context_create($opts);
$i = 0;
do {
$json = @file_get_contents("https://" . current($nodes) . $uri,
false,
$streamContext);
if (empty($json)) {
$nodes[] = array_shift($nodes);
++$i;
}
} while (empty($json) and ($i < $nodesNb));
if (!empty($json)) {
// Let's save node order for other queries :
$this->saveNodes($nodes, $nodeType);
if ($this->isActivatedCache) {
$this->cacheNodes($nodes, $nodeType);
}
}
return $json;
}
public function fetchJson ($uri, $nodeType = 'duniter', $queryParams = NULL) {
$json = NULL;
$nodes = $this->getNodesList($nodeType);
$nodesNb = count($nodes);
$maxTries = 3;
$nodeTimeout = $this->nodeTimeout[$nodeType];
$nodeTimeoutIncrement = $this->nodeTimeoutIncrement[$nodeType];
for ($i = 0; ($i < 3) and empty($json); ++$i) {
$json = $this->fetchJson_aux($nodes, $uri, $nodeType, $queryParams, $nodesNb, $nodeTimeout);
$nodeTimeout += $nodeTimeoutIncrement;
}
if (empty($json)) {
$out = [];
$out[] = _('Aucun noeud '. $nodeType .' n\'a été trouvé ou la requête n\'a pas abouti.');
$out[] = _('Noeud interrogés : ');
$out = array_merge($out, $nodes);
$out[] = _('URI: ' . $uri);
if (isset($queryParams)) {
$out[] = _('Paramètres de la requête : ');
$out[] = print_r($queryParams, true);
}
$this->decease($out);
}
return $json;
}
protected function fetchUdAmount ($date) {
// On récupère les numéros de chaque blocks de DU journalier
$json = $this->fetchJson('/blockchain/with/ud');
$blocks = json_decode($json)->result->blocks;
if ($date > $this->now) {
// On récupère le dernier block
$blockNum = end($blocks);
} else {
// On récupère le bloc de la date qui nous intéresse
$blockNum = $blocks[count($blocks) - $this->today->diff($date)->format("%a") - 1];
}
// Puis on récupère le montant du DU
$json = $this->fetchJson('/blockchain/block/' . $blockNum);
$block = json_decode($json);
return ($block->dividend / 100);
}
public function getUdAmount ($date) {
$udFilename = $this->getUdFilename($date);
$udsCacheDir = $this->cacheDir . 'uds/';
$udFullPath = $udsCacheDir . $udFilename;
if ($this->isActivatedCache) {
if (file_exists($udFullPath)) {
$udCachedAmount = file_get_contents($udFullPath);
if (is_numeric($udCachedAmount) and $udCachedAmount != 0) {
$udAmount = floatval($udCachedAmount);
}
}
if (!isset($udAmount)) {
$udAmount = $this->fetchUdAmount($date);
// Cache UD amount
if (!file_exists($udsCacheDir)) {
mkdir($udsCacheDir, 0777, true);
}
file_put_contents($udFullPath, $udAmount);
}
} else {
$udAmount = $this->fetchUdAmount($date);
}
return $udAmount;
}
protected function getUdFilename ($date) {
$datePreviousAutumnEquinox = new DateTime($date->format('Y') . '-09-22');
$datePreviousSpringEquinox = new DateTime($date->format('Y') . '-03-20');
if ($date > $datePreviousAutumnEquinox) {
$udFilename = $date->format('Y') . '-autumn';
} elseif ($date > $datePreviousSpringEquinox) {
$udFilename = $date->format('Y') . '-spring';
} else {
$udFilename = ($date->sub(new DateInterval('P1Y'))->format('Y')). '-autumn';
}
return $udFilename . '.txt';
}
}

84
lib/Fred.class.php Normal file
View File

@ -0,0 +1,84 @@
<?php
class Fred {
private $gatewayProtocol = 'http';
private $gatewayDomain = 'libra.copylaradio.com';
private $gatewayPort = '1234';
private $gatewayDelay = 3;
public function __construct () {
}
public function donneMoiLaPutainDeClefIPNS ($prenomNom, $nomDuChienSuivieDeLaDateDeNaissanceDeJohnnyHallyday) {
$salt = $prenomNom;
$pepper = $nomDuChienSuivieDeLaDateDeNaissanceDeJohnnyHallyday;
$query = 'salt='. $salt .'&pepper='. $pepper;
$page1 = @file_get_contents($this->gatewayProtocol . '://'. $this->gatewayDomain .':' . $this->gatewayPort . '/?' . $query)
or die('<p>On a fait du sale.</p>');
preg_match("`url='([^']+)'`isU", $page1, $matches);
$url = $matches[1];
sleep($this->gatewayDelay);
$page2 = @file_get_contents($url)
or die('</p>On a chié dans la colle.</p>');
preg_match("`url='.*/ipns/([^']+)'`isU", $page2, $matches);
$ipnsKey = $matches[1];
return $ipnsKey;
}
public function donneMoiSesPutainDeMessagesGchange ($prenomNom, $nomDuChienSuivieDeLaDateDeNaissanceDeJohnnyHallyday, $gchangeId) {
$salt = $prenomNom;
$pepper = $nomDuChienSuivieDeLaDateDeNaissanceDeJohnnyHallyday;
$query = 'salt='. $salt .'&pepper='. $pepper . '&messaging=' . $gchangeId;
$url = $this->gatewayProtocol . '://'. $this->gatewayDomain .':' . $this->gatewayPort . '/?' . $query;
echo '<pre>'; var_dump(htmlspecialchars($url)); echo '</pre>';
$page1 = file_get_contents($url)
or die('<p>On a fait du sale.</p>');
echo '<pre>'; var_dump(htmlspecialchars($page1)); echo '</pre>';
preg_match("`url='([^']+)'`isU", $page1, $matches);
$url = $matches[0];
echo '<pre>'; var_dump($url); echo '</pre>';
$page2 = '';
while (empty($page2)) {
sleep($this->gatewayDelay);
$page2 = @file_get_contents($url);
}
echo '<pre>'; var_dump($page2); echo '</pre>';
die();
$json = $page2;
return $page1;
}
}

279
lib/Gchange.class.php Normal file
View File

@ -0,0 +1,279 @@
<?php
require_once('DAO.class.php');
class Gchange {
private $dao;
private $cacheDir = __DIR__ . '/../cache/';
private $isActivatedCache = true;
private $cacheLongevity = array(
'placesNearby' => 604800, // 3 jours
'users' => 43200, // 12 heures
'records' => 900 // 15 min
);
public function __construct () {
$this->dao = DAO::getInstance();
}
public function getRecordsByIssuer ($issuer) {
$recordsCacheDir = 'records/user/';
$recordsCacheFile = $issuer . '.json';
$json = $this->getJsonFromCache($recordsCacheDir, $recordsCacheFile, $this->cacheLongevity['records']);
if (empty($json)) {
$n = 20;
$queryParams = [
'size' => $n,
'query' => [
'bool' =>[
'must' => [
'term' => [
'issuer' => $issuer
]
],
'must_not' => [
[ "term" => ["stock" => 0] ]
]
]
],
'sort' => [
['time' => 'desc']
]
];
$json = $this->dao->fetchJson('/market/record/_search', 'gchange', $queryParams);
$this->cacheJson($recordsCacheDir, $recordsCacheFile, $json);
}
$result = json_decode($json);
return $result->hits->hits;
}
private function getJsonFromCache ($dir, $file, $cacheLongevity) {
if (!$this->isActivatedCache) {
return NULL;
}
$json = null;
$jsonFullPath = $this->cacheDir . $dir . $file;
if (file_exists($jsonFullPath) and ((time() - filemtime($jsonFullPath)) < $cacheLongevity)) {
$json = file_get_contents($jsonFullPath);
}
return $json;
}
private function cacheJson ($dir, $file, $json) {
if (!$this->isActivatedCache) {
return NULL;
}
$jsonCacheDir = $this->cacheDir . $dir;
$jsonFullPath = $jsonCacheDir . $file;
if (!file_exists($jsonCacheDir)) {
mkdir($jsonCacheDir, 0777, true);
}
file_put_contents($jsonFullPath, $json);
}
public function getUser ($pubkey) {
$usersCacheDir = 'users/';
$usersCacheFile = $pubkey . '.json';
$json = $this->getJsonFromCache($usersCacheDir, $usersCacheFile, $this->cacheLongevity['users']);
if (empty($json)) {
$json = $this->dao->fetchJson(('/user/profile/'. $pubkey), 'gchange');
$this->cacheJson($usersCacheDir, $usersCacheFile, $json);
}
$result = json_decode($json);
return $result;
}
public function getPlacesNearUser ($user, $radius) {
$placesCacheDir = 'places-nearby/user/';
$placesCacheFile = $user->_id . '.json';
$json = $this->getJsonFromCache($placesCacheDir, $placesCacheFile, $this->cacheLongevity['placesNearby']);
if (empty($json)) {
$json = $this->getNearbyPlacesJson($user->_source->geoPoint->lat, $user->_source->geoPoint->lon, RADIUS);
$this->cacheJson($placesCacheDir, $placesCacheFile, $json);
}
$result = json_decode($json);
return $result->hits->hits;
}
public function getNearbyPlaces ($lat, $lon, $maxDistance, $minDistance = NULL) {
$json = $this->getNearbyPlacesJson($lat, $lon, $maxDistance, $minDistance);
$result = json_decode($json);
return $result->hits->hits;
}
public function getNearbyPlacesJson ($lat, $lon, $maxDistance, $minDistance = NULL) {
$n = 20;
$queryParams = [
'size' => $n,
'query' => [
'bool' => [
'must' => [
[
'geo_distance' => [
"distance" => $maxDistance . 'km',
"geoPoint"=> [
"lat" => $lat,
"lon" => $lon
]
]
]
]
]
]
];
$json = $this->dao->fetchJson('/page/record/_search', 'gchange', $queryParams);
return $json;
}
public function getNearbyRecords ($lat, $lon, $maxDistance, $minDistance = NULL) {
$n = 20;
$queryParams = [
'size' => $n,
'query' => [
'bool' => [
'must' => [
[
'geo_distance' => [
"distance" => $maxDistance . 'km',
"geoPoint"=> [
"lat" => $lat,
"lon" => $lon
]
]
], [
'range' => [
'stock' => [
'gte' => 1
]
]
]
]
]
]
];
$json = $this->dao->fetchJson('/market/record/_search?pretty', 'gchange', $queryParams);
$result = json_decode($json);
return $result->hits->hits;
}
public function getImmaterialRecords () {
$n = 20;
$queryParams = [
'size' => $n,
'query' => [
'nested' => [
'path' => 'category',
'query' => [
'bool' => [
'should' => [
[ 'term' => [ 'category.parent' => 'cat31' ] ],
[ 'term' => [ 'category.id' => 'cat31' ] ],
[ 'term' => [ 'category.parent' => 'cat74' ] ],
[ 'term' => [ 'category.id' => 'cat74' ] ]
],
'must_not' => [
[ "term" => ["category.id" => "cat65"] ]
]
]
]
]
]
];
$json = $this->dao->fetchJson('/market/record/_search?pretty', 'gchange', $queryParams);
$result = json_decode($json);
return $result->hits->hits;
}
public function getShippable () {
$n = 20;
$queryParams = [
'size' => $n,
'query' => [
'match' => [
'description' => 'envoi possible'
]
]
];
$json = $this->dao->fetchJson('/market/record/_search?pretty', 'gchange', $queryParams);
$result = json_decode($json);
return $result->hits->hits;
}
public function getHousingOffers () {
}
public function getShippableOffers () {
}
}

131
lib/Location.class.php Normal file
View File

@ -0,0 +1,131 @@
<?php
class Location {
const API_URL = 'http://nominatim.openstreetmap.org/search?q=%s&format=json';
private $lat;
private $lon;
private $name;
private $city;
private $postCode;
private $successfulQuery;
public function __construct () {
}
public function fetchOpenStreetMap ($searchQuery) {
$json = NULL;
$streamContext = stream_context_create(
array(
"http" => array(
"header" => "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
)
)
);
$url = sprintf(Location::API_URL, urlencode($searchQuery));
$json = @file_get_contents($url, false, $streamContext);
if (!empty($json)) {
$json = json_decode($json);
}
return $json;
}
public function createFromAddress ($searchTerms) {
if (is_array($searchTerms)) {
while (!empty($searchTerms)) {
$searchQuery = implode(' ', $searchTerms);
$results = Location::fetchOpenStreetMap($searchQuery);
if (empty($json)) {
$searchTerms = array_slice($searchTerms, 0, -1);
} else {
break;
}
}
} else {
$searchQuery = $searchTerms;
$results = Location::fetchOpenStreetMap($searchQuery);
}
if (isset($results[0])) {
$firstResult = $results[0];
$loc = new Location();
$loc->setPosition($firstResult->lat, $firstResult->lon);
$loc->successfulQuery = $searchQuery;
return $loc;
} else {
return false;
}
}
public function setPosition ($lat, $lon) {
$this->lat = $lat;
$this->lon = $lon;
}
public function getPosition () {
return [$this->lat, $this->lon];
}
public function getSuccessfulQuery () {
return $this->successfulQuery;
}
public function getLat () {
return $this->lat;
}
public function getLon () {
return $this->lon;
}
static public function geoDist (Location $p1, Location $p2) {
// https://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates
$pos1 = $p1->getPosition();
$pos2 = $p2->getPosition();
$a = sin(deg2rad($pos2[0]-$pos1[0])/2)**2 + sin(deg2rad($pos2[1]-$pos1[1])/2)**2 * cos(deg2rad($pos1[0])) * cos(deg2rad($pos2[0]));
return 12742 * atan2(sqrt($a), sqrt(1-$a));
}
}

63
login.php Normal file
View File

@ -0,0 +1,63 @@
<?php
require_once('config.php');
require_once('lib/Gchange.class.php');
if (isset($_POST['player'])) {
$_SESSION['player_pubkey'] = $_POST['player'];
$gchange = new Gchange();
$user = $gchange->getUser($_SESSION['player_pubkey']);
$_SESSION['player_lat'] = $user->_source->geoPoint->lat;
$_SESSION['player_lon'] = $user->_source->geoPoint->lon;
header('Location:map.php');
} else {
$bodyIds = 'login-page';
include_once('header.php');
echo '
<form method="post" action="">
<p>
<label>Joueur&nbsp;:
<select name="player">';
foreach ($players as $p) {
echo '<option value="'. $p->getPubkey() .'">'. $p->getName() .'</option>';
}
echo '
</select>
</label>
</p>
<p>
<label>
Sel :
<input type="text" name="salt" />
</label>
</p>
<p>
<label>
Poivre:
<input type="text" name="pepper" />
</label>
</p>
<p>
<input type="submit" />
</p>
</form>
';
}

281
map.php Normal file
View File

@ -0,0 +1,281 @@
<?php
require_once('config.php');
require_once('lib/Gchange.class.php');
require_once('lib/Location.class.php');
if (!isset($_SESSION['player_pubkey'])) {
header('Location: index.php');
}
$bodyIds = 'sonar';
include_once('header.php');
$gchange = new Gchange();
$player = $gchange->getUser($_SESSION['player_pubkey']);
$origLat = $player->_source->geoPoint->lat;
$origLon = $player->_source->geoPoint->lon;
$origLoc = new Location();
$origLoc->setPosition($origLat, $origLon);
echo '
<section id="mapview">
<aside id="geoPoint">
<a href=".">'. $origLat . ' , ' . $origLon .'</a>
</aside>
<div
id="map"
data-orig-lat="'. $origLat .'"
data-orig-lon="'. $origLon .'"
data-radius="'. $_SESSION['radius'] .'"
>
<div id="map-deco"></div>
<section class="player" style="left: calc(50% - '. DEMI_TAILLE_SPRITE . 'px); top: calc(50% - '. DEMI_TAILLE_SPRITE . 'px);">';
if (isset($player->_source->avatar->_content) and !empty($player->_source->avatar->_content)) {
$src = 'data:'. $player->_source->avatar->_content_type .';base64,' . $player->_source->avatar->_content;
} else {
$src = 'assets/img/avatars/32/et-in-flying-saucer.png.png';
}
echo '
<img src="'. $src . '"
alt="'. $player->_source->title .'"
width="'. TAILLE_SPRITE .'"
height="'. TAILLE_SPRITE .'" />
</section>
';
$places = $gchange->getPlacesNearUser($player, $_SESSION['radius']);
$selectedPlace = NULL;
foreach ($places as $place) {
if (isset($_GET['place']) and ($place->_id == $_GET['place'])) {
$selectedPlace = $place;
}
$placeLat = $place->_source->geoPoint->lat;
$placeLon = $place->_source->geoPoint->lon;
echo '
<section class="place"
id="place-'. $place->_id .'"
data-place-lat="'. $placeLat .'"
data-place-lon="'. $placeLon .'"
>';
echo '
<a href="?place='. $place->_id .'">';
if (isset($place->_source->avatar->_content) and !empty($place->_source->avatar->_content)) {
$src = 'data:'. $place->_source->avatar->_content_type .';base64,' . $place->_source->avatar->_content;
} else {
$src = 'assets/img/yourte.png';
}
echo '
<img src="'. $src .'"
alt="'. $place->_source->title . '"
title="'. $place->_source->title .'"
width="'. TAILLE_SPRITE .'"
height="'. TAILLE_SPRITE .'" />';
echo '
</a>
<article>';
echo '
</article>
</section>';
}
echo '
</div>
</section>';
if (isset($selectedPlace)) {
echo '
<aside id="place-details">';
$place = $selectedPlace;
$placeLat = $place->_source->geoPoint->lat;
$placeLon = $place->_source->geoPoint->lon;
$placeLoc = new Location();
$placeLoc->setPosition($placeLat, $placeLon);
echo '
<h3 class="place-name">
'. $place->_source->title . '
</h3>';
echo '
<address>
<p class="position">
'. $placeLat . ',' . $placeLon .'
</p>
<p class="distance">
à '. ceil(Location::geoDist($origLoc, $placeLoc)) .'km
</p>
</address>
<p class="get-directions">
Y aller&nbsp;:
<a class="car" href="https://www.google.com/maps/dir/'. $origLat .','. $origLon .'/'.$placeLat .','. $placeLon .'/data=!3m1!4b1!4m2!4m1!3e0">
<span>
en voiture
</span>
</a>,
<a class="bike" href="https://www.google.com/maps/dir/'. $origLat .','. $origLon .'/'.$placeLat .','. $placeLon .'/data=!3m1!4b1!4m2!4m1!3e1">
<span>
en vélo
</span>
</a>,
<br />
<a class="foot" href="https://www.google.com/maps/dir/'. $origLat .','. $origLon .'/'.$placeLat .','. $placeLon .'/data=!3m1!4b1!4m2!4m1!3e2">
<span>
à pied
</span>
</a>
</p>
';
$records = $gchange->getRecordsByIssuer($place->_source->issuer);
$offers = [];
$needs = [];
$crowdfundings = [];
foreach ($records as $record) {
switch ($record->_source->type) {
case 'offer':
$offers[] = $record;
break;
case 'need':
$needs[] = $record;
break;
case 'crowdfunding':
$crowdfundings[] = $record;
break;
}
}
if (!empty($needs)) {
echo '<h4>Demandes</h4>';
echo '<ul>';
foreach ($needs as $item) {
$description = isset($item->_source->description) ? ' title="'. substr($item->_source->description, 0, 30) . '"' : '';
echo '
<li>
<a href="https://www.gchange.fr/#/app/market/view/'. $item->_id . '/"'. $description .'>
' . $item->_source->title . '
</a>
</li>
';
}
echo '</ul>';
}
if (!empty($offers)) {
echo '<h4>Offres</h4>';
echo '<ul>';
foreach ($offers as $item) {
$description = isset($item->_source->description) ? ' title="'. substr($item->_source->description, 0, 30) . '"' : '';
echo '
<li>
<a href="https://www.gchange.fr/#/app/market/view/'. $item->_id . '/"'. $description .'>
' . $item->_source->title . '
</a>
</li>
';
}
echo '</ul>';
}
if (!empty($crowdfundings)) {
echo '<h4>Financements participatifs</h4>';
echo '<ul>';
foreach ($crowdfundings as $item) {
$description = isset($item->description) ? ' title="'. substr($item->_source->description, 0, 30) . '"' : '';
echo '
<li>
<a href="https://www.gchange.fr/#/app/market/view/'. $item->_id . '/"'. $description .'>
' . $item->_source->title . '
</a>
</li>
';
}
echo '</ul>';
}
if (isset($place->_source->description) and !empty($place->_source->description)) {
echo '
<h4>À propos du lieu</h4>
<p class="place-desc">
'. $place->_source->description . '
</p>';
}
echo '
</aside>';
}
include_once('footer.php');

14
select_game.php Normal file
View File

@ -0,0 +1,14 @@
<?php
require_once('config.php');
if ( !isset($_GET['gameId']) or !in_array($_GET['gameId'], array_keys($games)) ) {
header('Location: index.php');
} else {
$_SESSION['gameId'] = $_GET['gameId'];
header('Location: login.php');
}

69
themes/magie/deco.css Normal file
View File

@ -0,0 +1,69 @@
body {
font-family: cursive;
}
#map {
background-color: hsl(240, 7.5%, 60.6%);
}
#map-deco {
background: radial-gradient(circle at 33% 33%, hsl(240, 28.6%, 86.3%) 0%, transparent 50%);
}
.place {
opacity: 0.5;
animation: appear 3s ease-in-out forwards;
}
@keyframes appear {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#place-details {
animation: appear 3s ease-in-out forwards;
}
.distance:after {
content: " à vol d\'OVNI";
}
#place-details > address .distance:after {
content: " en balais volant";
}
#place-details .get-directions a span {
display: none;
}
#place-details .get-directions .car:after {
content: "en poudre de cheminette";
}
#place-details .get-directions .bike:after {
content: "en Nimbus 2000";
}
#place-details .get-directions .foot:after {
content: "en botte de sept lieues";
}

0
themes/magie/deco.js Normal file
View File

1
themes/magie/layout.css Symbolic link
View File

@ -0,0 +1 @@
../spationaute/layout.css

1
themes/magie/layout.js Symbolic link
View File

@ -0,0 +1 @@
../spationaute/layout.js

192
themes/spationaute/deco.css Normal file
View File

@ -0,0 +1,192 @@
:root {
--panel-bg-color: hsl(131.6, 47.7%, 12.5%);
--panel-place-name-bg-color: hsl(131.6, 52.7%, 7.5%);
--panel-place-name-color: white;
}
body {
background: hsl(0, 50%, 2%);
}
#login-page {
color: white;
}
#mapview #geoPoint a,
#mapview #geoPoint a:visited {
color: white;
opacity: 0.4;
text-decoration: none;
}
body#sonar {
background:
/*
repeating-linear-gradient(to right, hsl(132.2, 59.8%, 52.2%) 0vh, hsl(132.2, 59.8%, 52.2%) 0.4vh, transparent 0.4vh, transparent 10vh),
repeating-linear-gradient(transparent, transparent 9.8vh, hsl(132.2, 59.8%, 52.2%) 10.2vh, hsl(132.2, 59.8%, 52.2%) 10.2vh, transparent 10.2vh),
*/
linear-gradient(to right, transparent 0vw, transparent 32.9vw, hsl(132.2, 59.8%, 74.2%) 32.9vw, hsl(132.2, 59.8%, 74.2%) 33.1vw, transparent 33.1vw),
linear-gradient(to right, hsl(132.2, 59.8%, 12.1%) 0vw, hsl(132.2, 59.8%, 12.1%) 0.1vw, transparent 0.1vw),
repeating-linear-gradient(to right, hsl(132.2, 59.8%, 52.2%) 0vw, hsl(132.2, 59.8%, 52.2%) 0.1vw, transparent 0.1vw, transparent 5.4vw, hsl(132.2, 59.8%, 52.2%) 5.4vw, hsl(132.2, 59.8%, 52.2%) 5.5vw),
/*linear-gradient(to bottom, transparent 0vw, transparent 32.9vw, hsl(132.2, 59.8%, 74.2%) 32.9vw, hsl(132.2, 59.8%, 74.2%) 33.1vw, transparent 33.1vw), */
linear-gradient(to bottom, hsl(132.2, 59.8%, 12.1%) 0vw, hsl(132.2, 59.8%, 12.1%) 0.1vw, transparent 0.1vw),
repeating-linear-gradient(to bottom, hsl(132.2, 59.8%, 52.2%) 0vw, hsl(132.2, 59.8%, 52.2%) 0.1vw, transparent 0.1vw, transparent 5.4vw, hsl(132.2, 59.8%, 52.2%) 5.4vw, hsl(132.2, 59.8%, 52.2%) 5.5vw),
hsl(132.2, 59.8%, 12.1%);
}
#map {
background:
linear-gradient( 0deg, transparent 49.85%, hsl(132.2, 59.8%, 74.2%) 49.85%, hsl(132.2, 59.8%, 74.2%) 50.15%, transparent 50.15%),
linear-gradient( 90deg, transparent 49.85%, hsl(132.2, 59.8%, 74.2%) 49.85%, hsl(132.2, 59.8%, 74.2%) 50.15%, transparent 50.15%),
linear-gradient( 45deg, transparent 49.90%, hsl(132.2, 59.8%, 74.2%) 49.90%, hsl(132.2, 59.8%, 74.2%) 50.10%, transparent 50.10%),
linear-gradient(135deg, transparent 49.90%, hsl(132.2, 59.8%, 74.2%) 49.90%, hsl(132.2, 59.8%, 74.2%) 50.10%, transparent 50.10%),
repeating-radial-gradient(transparent 0, transparent 10%, hsl(132.2, 59.8%, 74.2%) 10%, hsl(132.2, 59.8%, 74.2%) 10.4%),
/*radial-gradient(circle at center, hsl(132.2, 59.8%, 37.1%) 0%, hsl(132.2, 59.8%, 74.2%) 50%, hsl(132.2, 59.8%, 37.1%) 100%), */
hsla(132.2, 59.8%, 37.1%, 0.8);
border-style: solid;
border-color: hsl(132.2, 59.8%, 74.2%);
}
#map-deco {
background:
conic-gradient(hsla(132.2, 59.8%, 74.2%, 1) 0deg, hsla(132.2, 59.8%, 74.2%, 0) 45deg, hsla(132.2, 59.8%, 74.2%, 0) 0deg);
animation: sonar 5s linear infinite reverse;
}
@keyframes sonar {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(355deg);
}
}
#map .place {
opacity: 0;
animation: appear infinite 5s;
}
@keyframes appear {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#map .place,
#map .player {
cursor: pointer;
}
#place-details {
background: var(--panel-bg-color);
color: white;
cursor: auto;
animation: slideIn 1s forwards;
}
@keyframes slideIn {
0% {
transform: translateX(33vw);
}
100% {
transform: translateX(0);
}
}
#place-details > .place-name {
background-color: var(--panel-place-name-bg-color);
color: var(--panel-place-name-color);
}
#place-details > address {
opacity: 0.8;
}
#place-details > address .distance:after {
content: " à vol d\'OVNI";
}
#place-details .get-directions a span {
display: none;
}
#place-details .get-directions .car:after {
content: "en hyperespace";
}
#place-details .get-directions .bike:after {
content: "en vitesse supraluminique";
}
#place-details .get-directions .foot:after {
content: "en vitesse subluminique";
}
#place-details > h4 {
background: var(--panel-place-name-bg-color);
}
#place-details a,
#place-details a:hover,
#place-details a:focus,
#place-details a:active,
#place-details a:visited {
color: inherit;
text-decoration: none;
}

View File

@ -0,0 +1,55 @@
const SONAR_DURATION = 5;
function computeLatDistanceInKm (dest, orig) {
// Approximation française
return ((dest - orig) * 111);
}
function computeLonDistanceInKm (dest, orig) {
// Approximation française
return ((dest - orig) * 79);
}
function computeDelay (latDistanceKm, lonDistanceKm, sonarDuration) {
placeDirection = Math.atan(latDistanceKm / lonDistanceKm);
placeWay = lonDistanceKm < 0 ? -1 : 1;
placeAngle = placeDirection * placeWay;
unsignedAngle = placeAngle + (2 * Math.PI);
animOrigAngle = 3 * Math.PI / 2;
animAngle_verbose = animOrigAngle + unsignedAngle;
animAngle_bounded = fmod(animAngle_verbose, (2 * Math.PI));
circleRatio = animAngle_bounded / (2 * Math.PI);
delay = circleRatio * sonarDuration;
return delay;
}
var places = document.getElementsByClassName('place');
var map = document.getElementById('map');
var origLat = map.getAttribute('data-orig-lat');
var origLon = map.getAttribute('data-orig-lon');
for (place of places) {
if ( !( (placeLat == origLat) && (placeLon == origLon) ) ) {
var placeLat = place.getAttribute("data-place-lat");
var placeLon = place.getAttribute("data-place-lon");
var latDistanceKm = computeLatDistanceInKm(placeLat, origLat);
var lonDistanceKm = computeLonDistanceInKm(placeLon, origLon);
var delay = computeDelay(latDistanceKm, lonDistanceKm, SONAR_DURATION);
place.style.animationDelay = delay + 's';
console.log('delay JS : ' + delay);
}
}

View File

@ -0,0 +1,146 @@
:root {
--taille-sprite: 32px;
}
body {
margin: 0;
padding: 0;
}
#aside {
width: 33vw;
}
#mapview {
width: 66vw;
}
#mapview #geoPoint {
padding: 1rem;
position: fixed;
top: 0;
left: 0;
display: inline-block;
}
main {
overflow: auto;
}
#map {
margin: auto;
margin-top: 2vh;
height: 94vh;
width: 94vh;
border-width: 0.125rem;
border-radius: 33vw;
position: relative;
overflow: hidden;
}
#map-deco {
position: absolute;
left: 0%;
top: 0%;
width: 100%;
height: 100%;
border-radius: 33vw;
}
#map .place,
#map .player {
position: absolute;
}
#map .place header {
z-index: 50;
}
#map .place a:active + article,
#map .place a:focus + article {
z-index: 100;
display: block;
position: fixed;
top: 0;
right: 0;
width: 33vw;
overflow: hidden;
/*box-shadow: 0.125rem 0.125rem 1em black;*/
}
#place-details {
position: fixed;
top: 0;
right: 0;
width: 33vw;
height: 100vh;
padding: 0rem;
}
#place-details > * {
margin-left: 1rem;
margin-right: 1rem;
}
#place-details > .place-name {
margin: 0;
padding: 1rem;
text-align: center;
}
#place-details > address {
text-align: center;
margin-top: 0.5rem;
}
#place-details > h4 {
margin-left: 0;
margin-right: 0;
padding: 0.5rem 1rem;
background: var(--panel-place-name-bg-color);
}

View File

@ -0,0 +1,83 @@
function fmod (x, y) {
// discuss at: https://locutus.io/php/fmod/
// original by: Onno Marsman (https://twitter.com/onnomarsman)
// input by: Brett Zamir (https://brett-zamir.me)
// bugfixed by: Kevin van Zonneveld (https://kvz.io)
// example 1: fmod(5.7, 1.3)
// returns 1: 0.5
let tmp
let tmp2
let p = 0
let pY = 0
let l = 0.0
let l2 = 0.0
tmp = x.toExponential().match(/^.\.?(.*)e(.+)$/)
p = parseInt(tmp[2], 10) - (tmp[1] + '').length
tmp = y.toExponential().match(/^.\.?(.*)e(.+)$/)
pY = parseInt(tmp[2], 10) - (tmp[1] + '').length
if (pY > p) {
p = pY
}
tmp2 = (x % y)
if (p < -100 || p > 20) {
// toFixed will give an out of bound error so we fix it like this:
l = Math.round(Math.log(tmp2) / Math.log(10))
l2 = Math.pow(10, l)
return (tmp2 / l2).toFixed(l - p) * l2
} else {
return parseFloat(tmp2.toFixed(-p))
}
}
const TAILLE_SPRITE = 32;
var places = document.getElementsByClassName('place');
var map = document.getElementById('map');
var origLat = map.getAttribute('data-orig-lat');
var origLon = map.getAttribute('data-orig-lon');
var radius = map.getAttribute('data-radius');
function computeLatDistanceInKm (dest, orig) {
// Approximation française
return ((dest - orig) * 111);
}
function computeLonDistanceInKm (dest, orig) {
// Approximation française
return ((dest - orig) * 79);
}
// Position places on map
for (place of places) {
var placeLat = place.getAttribute("data-place-lat");
var placeLon = place.getAttribute("data-place-lon");
var latDistanceKm = computeLatDistanceInKm(placeLat, origLat);
var lonDistanceKm = computeLonDistanceInKm(placeLon, origLon);
var latDistancePcts = latDistanceKm / (2 * radius) * 100;
var lonDistancePcts = lonDistanceKm / (2 * radius) * 100;
var leftPcts = 50 + lonDistancePcts;
var topPcts = 50 - latDistancePcts;
var cssLeft = 'calc(' + leftPcts + '% - ' + (TAILLE_SPRITE / 2) + 'px)';
var cssTop = 'calc(' + topPcts + '% - ' + (TAILLE_SPRITE / 2) + 'px)';
place.style.left = cssLeft;
place.style.top = cssTop;
}