Spot v2: Project Management

This commit is contained in:
2019-01-26 10:13:06 +01:00
parent 55bb972c5b
commit a538357aba
25 changed files with 1649 additions and 9510 deletions

View File

@@ -0,0 +1,25 @@
CREATE TABLE `projects` (
`id_project` int(10) UNSIGNED auto_increment,
`codename` VARCHAR(100),
`name` VARCHAR(100),
`active_from` DATETIME,
`active_to` DATETIME,
`geofile` VARCHAR(50),
`timezone` VARCHAR(100),
`led` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_project`));
INSERT INTO projects (name, codename, active_from, active_to, geofile, timezone) VALUES ('Te Araroa', 'te_araroa', '2015-12-29 00:00:00', '2016-03-05 23:59:59', 'te_araroa.geojson', 'Pacific/Auckland');
INSERT INTO projects (name, codename, active_from, active_to, geofile, timezone) VALUES ('HRP', 'hrp', '2019-06-01 00:00:00', '2019-09-10 23:59:59', 'hrp.geojson', 'Europe/Paris');
ALTER TABLE feeds ADD COLUMN id_project int(10) UNSIGNED AFTER id_spot;
ALTER TABLE feeds ADD last_update DATETIME AFTER status;
ALTER TABLE feeds ADD FOREIGN KEY (`id_project`) REFERENCES projects(`id_project`);
UPDATE feeds SET last_update = led;
UPDATE feeds SET id_project = 1;
ALTER TABLE posts ADD COLUMN id_project int(10) UNSIGNED AFTER id_post;
ALTER TABLE posts ADD timestamp DATETIME AFTER content;
ALTER TABLE posts ADD FOREIGN KEY (`id_project`) REFERENCES projects(`id_project`);
UPDATE posts SET timestamp = led;
UPDATE posts SET id_project = 1;

36
geo/hrp.geojson Normal file

File diff suppressed because one or more lines are too long

153
geo/te_araroa.geojson Normal file

File diff suppressed because one or more lines are too long

BIN
images/layers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
images/marker-icon-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
images/marker-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/marker-shadow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@@ -1,103 +1,109 @@
<?php
class Cacher extends PhpObject
{
const CACHE_FOLDER = 'cache/';
const DATA_RETENTION = 60 * 60 * 24 * 90; //3 months
private $sPattern;
//Params
private $sId;
private $iX;
private $iY;
private $iZ;
private $sToken;
private $asDomains;
public function __construct($sPattern, $sId='')
{
parent::__construct(__CLASS__, Settings::DEBUG);
$this->setPattern($sPattern);
$this->setId($sId);
$this->iX = 0;
$this->iY = 0;
$this->iZ = 0;
$this->sToken = '';
$this->asDomains = array();
}
public function setId($sId) {
$this->sId = $sId;
}
public function setPattern($sPattern) {
$this->sPattern = $sPattern;
}
public function setToken($sToken) {
$this->sToken = $sToken;
}
public function setDomains($asDomains) {
$this->asDomains = $asDomains;
}
public function setGeoPos($iX, $iY, $iZ) {
$this->iX = $iX;
$this->iY = $iY;
$this->iZ = $iZ;
}
public function pushTile($iX, $iY, $iZ) {
$this->setGeoPos($iX, $iY, $iZ);
$sTilePath = $this->getFilePath();
if(!$this->isFileAvailable($sTilePath)) {
$oCurl = curl_init();
curl_setopt($oCurl, CURLOPT_URL, $this->getUrl());
curl_setopt($oCurl, CURLOPT_HEADER, false);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($oCurl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($oCurl, CURLOPT_REFERER, 'http://spot.lutran.fr');
$sContent = curl_exec($oCurl);
curl_close($oCurl);
file_put_contents($sTilePath, $sContent);
}
else $sContent = file_get_contents($sTilePath);
header('Content-type: '.mime_content_type($sTilePath));
return $sContent;
}
public function getUrl() {
$asParams = $this->getUrlParams();
$sUrl = $this->sPattern;
foreach($asParams as $sParam=>$sValue) {
$sUrl = str_replace('{'.$sParam.'}', $sValue, $sUrl);
}
return $sUrl;
}
private function isFileAvailable($sTilePath) {
if(!file_exists($sTilePath)) return false;
else return (time() - filemtime($sTilePath) < self::DATA_RETENTION);
}
private function getFilePath() {
$asParams = $this->getUrlParams();
return self::CACHE_FOLDER.'/'.$asParams['id'].'_'.$asParams['x'].'_'.$asParams['y'].'_'.$asParams['z'].'.tile';
}
private function getUrlParams() {
return array(
'id' => $this->sId,
'x' => $this->iX,
'y' => $this->iY,
'z' => $this->iZ,
'token' => $this->sToken,
's' => empty($this->asDomains)?'':$this->asDomains[array_rand($this->asDomains)]
);
}
}
<?php
class Cacher extends PhpObject
{
const CACHE_FOLDER = 'cache/';
const DATA_RETENTION = 60 * 60 * 24 * 90; //3 months
private $sPattern;
//Params
private $sId;
private $iX;
private $iY;
private $iZ;
private $sToken;
private $asDomains;
private $sReferer;
public function __construct($sPattern, $sId='')
{
parent::__construct(__CLASS__, Settings::DEBUG);
$this->setPattern($sPattern);
$this->setId($sId);
$this->iX = 0;
$this->iY = 0;
$this->iZ = 0;
$this->sToken = '';
$this->asDomains = array();
$this->sReferer = '';
}
public function setId($sId) {
$this->sId = $sId;
}
public function setPattern($sPattern) {
$this->sPattern = $sPattern;
}
public function setToken($sToken) {
$this->sToken = $sToken;
}
public function setDomains($asDomains) {
$this->asDomains = $asDomains;
}
public function setReferer($sReferer) {
$this->sReferer = $sReferer;
}
public function setGeoPos($iX, $iY, $iZ) {
$this->iX = $iX;
$this->iY = $iY;
$this->iZ = $iZ;
}
public function pushTile($iX, $iY, $iZ) {
$this->setGeoPos($iX, $iY, $iZ);
$sTilePath = $this->getFilePath();
if(!$this->isFileAvailable($sTilePath)) {
$oCurl = curl_init();
curl_setopt($oCurl, CURLOPT_URL, $this->getUrl());
curl_setopt($oCurl, CURLOPT_HEADER, false);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($oCurl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($oCurl, CURLOPT_REFERER, $this->sReferer);
$sContent = curl_exec($oCurl);
curl_close($oCurl);
file_put_contents($sTilePath, $sContent);
}
else $sContent = file_get_contents($sTilePath);
header('Content-type: '.mime_content_type($sTilePath));
return $sContent;
}
public function getUrl() {
$asParams = $this->getUrlParams();
$sUrl = $this->sPattern;
foreach($asParams as $sParam=>$sValue) {
$sUrl = str_replace('{'.$sParam.'}', $sValue, $sUrl);
}
return $sUrl;
}
private function isFileAvailable($sTilePath) {
if(!file_exists($sTilePath)) return false;
else return (time() - filemtime($sTilePath) < self::DATA_RETENTION);
}
private function getFilePath() {
$asParams = $this->getUrlParams();
return self::CACHE_FOLDER.'/'.$asParams['id'].'_'.$asParams['x'].'_'.$asParams['y'].'_'.$asParams['z'].'.tile';
}
private function getUrlParams() {
return array(
'id' => $this->sId,
'x' => $this->iX,
'y' => $this->iY,
'z' => $this->iZ,
'token' => $this->sToken,
's' => empty($this->asDomains)?'':$this->asDomains[array_rand($this->asDomains)]
);
}
}

111
inc/project.php Normal file
View File

@@ -0,0 +1,111 @@
<?php
class Project extends PhpObject {
//Spot Mode
const MODE_PREVIZ = 'P';
const MODE_BLOG = 'B';
const MODE_HISTO = 'H';
const MODES = array('previz'=>self::MODE_PREVIZ, 'blog'=>self::MODE_BLOG, 'histo'=>self::MODE_HISTO);
//Folders
const GEO_FOLDER = 'geo/';
//DB Tables
const PROJ_TABLE = 'projects';
/**
* Database Handle
* @var Db
*/
private $oDb;
private $iProjectId;
private $sMode;
private $sName;
private $sCodeName;
private $asActive;
private $sGeo;
public function __construct(Db &$oDb) {
parent::__construct(__CLASS__, Settings::DEBUG);
$this->oDb = $oDb;
}
public function setProjectId($iProjectId=0) {
if($iProjectId > 0) {
$this->iProjectId = $iProjectId;
}
else {
/**
* Project 1 [-----------------]
* Project 2 [---------------------------]
* Project 3 [-----------]
* Selected Project [-------Project 1-------][------------Project 2-------------][---------------Project 3------------------
* Mode --P--][--------B--------][--P--][-----------B---------------][---P---][-----B-----][---------H----------
*/
$sQuery = "SELECT MAX(id_project) FROM projects WHERE active_to = (SELECT MIN(active_to) FROM projects WHERE active_to > NOW() OR active_to = (SELECT MAX(active_to) FROM projects))";
$asResult = $this->oDb->getArrayQuery($sQuery, true);
$this->iProjectId = array_shift($asResult);
}
$this->setProjectInfo();
}
public function getMode() {
return $this->sMode;
}
public function getProjectId() {
return $this->iProjectId;
}
public function getProjectCodeName() {
return $this->sCodeName;
}
public function getActivePeriod($sFromTo='') {
return ($sFromTo=='')?$this->asActive:$this->asActive[$sFromTo];
}
public function getProjects($iProjectId=0) {
$asInfo = array(
'select'=> array(
Db::getId(self::PROJ_TABLE)." AS id",
'codename',
'name',
'active_from',
'active_to',
"IF(NOW() BETWEEN active_from AND active_to, 1, IF(NOW() < active_from, 0, 2)) AS mode",
'geofile',
'timezone'
),
'from' => self::PROJ_TABLE
);
if($iProjectId > 0) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId);
$asProjects = $this->oDb->selectRows($asInfo, 'codename');
foreach($asProjects as &$asProject) {
switch($asProject['mode']) {
case 0: $asProject['mode'] = self::MODE_PREVIZ; break;
case 1: $asProject['mode'] = self::MODE_BLOG; break;
case 2: $asProject['mode'] = self::MODE_HISTO; break;
}
$sGeoFilePath = self::GEO_FOLDER.$asProject['geofile'];
$asProject['geofile'] = file_exists($sGeoFilePath)?$asProject['geofile'].'?'.date("YmdHis", filemtime($sGeoFilePath)):$asProject['geofile'];
}
return $asProjects;
}
private function setProjectInfo() {
$asResult = $this->getProjects($this->iProjectId);
$asProject = reset($asResult);
$this->sMode = $asProject['mode'];
$this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']);
$this->sCodeName = key($asResult);
$this->sName = $asProject['name'];
$this->sGeo = array('file'=>$asProject['geofile'], 'timezone'=>$asProject['timezone']);
}
}

View File

@@ -2,10 +2,6 @@
class Spot extends Main
{
//Spot Mode
const MODE_HISTO = 'histo';
const MODE_BLOG = 'blog';
//Spot feed
const FEED_HOOK = 'https://api.findmespot.com/spot-main-web/consumer/rest-api/2.0/public/feed/';
const FEED_TYPE_XML = '/message.xml';
@@ -25,19 +21,27 @@ class Spot extends Main
const FEED_CHUNK_SIZE = 15;
/**
* Active Project
* @var Project
*/
private $oProject;
public function __construct($oClassManagement, $sProcessPage)
{
parent::__construct($oClassManagement, $sProcessPage);
$oClassManagement->incClass('cacher', true);
$asClasses = array(
array('name'=>'project', 'project'=>true),
array('name'=>'cacher', 'project'=>true)
);
parent::__construct($oClassManagement, $sProcessPage, $asClasses);
$this->oProject = new Project($this->oDb);
}
protected function install()
{
//Install DB
$this->oDb->install();
//Add feed
//$this->oDb->insertRow(self::FEED_TABLE, array('ref_feed_id'=>Settings::FEED_ID));
}
protected function getSqlOptions()
@@ -46,36 +50,44 @@ class Spot extends Main
(
'tables' => array
(
self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp', 'content', 'battery_state'),
self::FEED_TABLE => array('ref_feed_id', Db::getId(self::SPOT_TABLE), 'name', 'description', 'status'),
self::SPOT_TABLE => array('ref_spot_id', 'name', 'model'),
self::POST_TABLE => array('name', 'content')
self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp', 'content', 'battery_state'),
self::FEED_TABLE => array('ref_feed_id', Db::getId(self::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'),
self::SPOT_TABLE => array('ref_spot_id', 'name', 'model'),
Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to', 'geofile', 'timezone'),
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'timestamp')
),
'types' => array
(
'ref_msg_id' => "INT",
'type' => "VARCHAR(20)",
'latitude' => "DECIMAL(7,5)",
'longitude' => "DECIMAL(8,5)",
'timestamp' => "DATETIME",
'unix_timestamp'=> "INT",
'content' => "LONGTEXT",
'battery_state' => "VARCHAR(10)",
'ref_spot_id' => "VARCHAR(10)",
'name' => "VARCHAR(100)",
'model' => "VARCHAR(20)",
'ref_feed_id' => "VARCHAR(40)",
'description' => "VARCHAR(100)",
'status' => "VARCHAR(10)",
'ref_msg_id' => "INT",
'type' => "VARCHAR(20)",
'latitude' => "DECIMAL(7,5)",
'longitude' => "DECIMAL(8,5)",
'timestamp' => "DATETIME",
'unix_timestamp'=> "INT",
'content' => "LONGTEXT",
'battery_state' => "VARCHAR(10)",
'ref_spot_id' => "VARCHAR(10)",
'name' => "VARCHAR(100)",
'codename' => "VARCHAR(100)",
'model' => "VARCHAR(20)",
'ref_feed_id' => "VARCHAR(40)",
'description' => "VARCHAR(100)",
'status' => "VARCHAR(10)",
'active_from' => "DATETIME",
'active_to' => "DATETIME",
'geofile' => "VARCHAR(50)",
'timezone' => "VARCHAR(100)",
'last_update' => "DATETIME"
),
'constraints' => array
(
self::MSG_TABLE => "UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)",
self::FEED_TABLE => "UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)",
self::SPOT_TABLE => "UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)",
self::MSG_TABLE => "INDEX(`ref_msg_id`)",
self::FEED_TABLE => "INDEX(`ref_feed_id`)",
self::SPOT_TABLE => "INDEX(`ref_spot_id`)",
self::MSG_TABLE => "UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)",
self::FEED_TABLE => "UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)",
self::SPOT_TABLE => "UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)",
self::MSG_TABLE => "INDEX(`ref_msg_id`)",
self::FEED_TABLE => "INDEX(`ref_feed_id`)",
self::SPOT_TABLE => "INDEX(`ref_spot_id`)",
Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)",
),
'cascading_delete' => array
(
@@ -89,10 +101,14 @@ class Spot extends Main
return parent::getMainPage(
array(
'vars' => array(
'feed_id' => Settings::FEED_ID,
'chunk_size'=> self::FEED_CHUNK_SIZE,
'mode' => Settings::MODE,
'mapbox_key'=> Settings::MAPBOX_KEY
'chunk_size' => self::FEED_CHUNK_SIZE,
'mapbox_key' => Settings::MAPBOX_KEY,
'default_project_codename' => $this->oProject->getProjectCodeName(),
'projects' => $this->oProject->getProjects()
),
'consts' => array(
'modes' => Project::MODES,
'geo_folder'=> Project::GEO_FOLDER
)
),
'index',
@@ -100,16 +116,22 @@ class Spot extends Main
);
}
/* Managing projects */
public function setProjectId($iProjectId=0) {
$this->oProject->setProjectId($iProjectId);
}
/* Getting & Storing messages */
private function getFeed($sRefFeedId=Settings::FEED_ID)
private function getFeed($sRefFeedId)
{
$sUrl = self::FEED_HOOK.$sRefFeedId.self::FEED_TYPE_JSON;
$sContent = file_get_contents($sUrl);
return json_decode($sContent, true);
}
private function updateFeed($sRefFeedId=Settings::FEED_ID)
private function updateFeed($sRefFeedId)
{
$asData = $this->getFeed($sRefFeedId);
$asMsgs = $asData['response']['feedMessageResponse']['messages'];
@@ -122,14 +144,15 @@ class Spot extends Main
$asSpotInfo = array('ref_spot_id'=>$asFirstMsg['messengerId'], 'name'=>$asFirstMsg['messengerName'], 'model'=>$asFirstMsg['modelId']);
$iSpotId = $this->oDb->insertUpdateRow(self::SPOT_TABLE, $asSpotInfo, array('ref_spot_id'));
//Update Feed Info
//Update Feed Info and last update date
$asFeedInfo = array(
'ref_feed_id' => $sRefFeedId,
Db::getId(self::SPOT_TABLE) => $iSpotId,
'name' => $asFeed['name'],
'description' => $asFeed['description'],
'status' => $asFeed['status'],
'led' => date(Db::MYSQL_TIMESTAMP) //update with current time
'ref_feed_id' => $sRefFeedId,
Db::getId(self::SPOT_TABLE) => $iSpotId,
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'name' => $asFeed['name'],
'description' => $asFeed['description'],
'status' => $asFeed['status'],
'last_update' => date(Db::TIMESTAMP_FORMAT)
);
$iFeedId = $this->oDb->insertUpdateRow(self::FEED_TABLE, $asFeedInfo, array('ref_feed_id'));
@@ -142,7 +165,7 @@ class Spot extends Main
'type' => $asMsg['messageType'],
'latitude' => $asMsg['latitude'],
'longitude' => $asMsg['longitude'],
'timestamp' => date(Db::MYSQL_TIMESTAMP, strtotime($asMsg['dateTime'])), //Stored in Local Time
'timestamp' => date(Db::TIMESTAMP_FORMAT, strtotime($asMsg['dateTime'])), //Stored in Local Time
'unix_timestamp' => $asMsg['unixTime'], //Stored in UNIX time
'content' => $asMsg['messageContent'],
'battery_state' => $asMsg['batteryState']
@@ -152,7 +175,7 @@ class Spot extends Main
}
}
public function getMessages($sRefFeedId=Settings::FEED_ID)
public function getMessages()
{
/* Adding another point to test
$test = $this->oDb->selectRow(self::MSG_TABLE, 1);
@@ -162,12 +185,7 @@ class Spot extends Main
$test['longitude'] = '172.1936';
$this->oDb->insertUpdateRow(self::MSG_TABLE, $test, array('ref_msg_id')); */
//Check last message & update feed if necessary (max once a day)
$sLastMsg = $this->oDb->selectValue(self::FEED_TABLE, 'led', array('ref_feed_id'=>$sRefFeedId));
if(Settings::MODE!=self::MODE_HISTO && mb_substr($sLastMsg, 0, 10) != date('Y-m-d')) $this->updateFeed($sRefFeedId);
//Extract messages
$asMessages = array_values($this->getSpotMessages());
$asMessages = $this->getSpotMessages();
$bSuccess = !empty($asMessages);
$sDesc = $bSuccess?'':self::NO_DATA;
@@ -202,22 +220,46 @@ class Spot extends Main
private function getSpotMessages()
{
$asInfo = array('from'=>self::MSG_TABLE, 'orderBy'=>array('timestamp'=>'ASC'));
if(Settings::MODE==self::MODE_HISTO) {
$asInfo['constraint'] = array('timestamp'=>Settings::HISTO_SPAN);
$asInfo['constOpe'] = array('timestamp'=>"BETWEEN");
//Get Feed IDs
$asFeeds = $this->oDb->selectRows(array(
'select' => array(Db::getId(self::FEED_TABLE), 'ref_feed_id', 'last_update'),
'from' => self::FEED_TABLE,
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()))
);
//Update on Blog Mode and not updated already today
$asAllFeedMessages = array();
foreach($asFeeds as $asFeed) {
//Feed updated once a day in Blog Mode
if($this->oProject->getMode() == Project::MODE_BLOG && mb_substr($asFeed['last_update'], 0, 10) != date('Y-m-d')) {
$this->updateFeed($asFeed['ref_feed_id']);
}
$asInfo = array(
'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp'),
'from' => self::MSG_TABLE,
'constraint'=> array(
Db::getId(Spot::FEED_TABLE) => $asFeed[Db::getId(self::FEED_TABLE)],
'timestamp' => $this->oProject->getActivePeriod()),
'constOpe' => array(
Db::getId(Spot::FEED_TABLE) => "=",
'timestamp' => "BETWEEN"),
'orderBy' => array('timestamp'=>'ASC'));
$asMessages = $this->oDb->selectRows($asInfo);
foreach($asMessages as $asMessage)
{
$asMessage['unix_timestamp'] = (int) $asMessage['unix_timestamp'];
$asMessage['latitude'] = floatval($asMessage['latitude']);
$asMessage['longitude'] = floatval($asMessage['longitude']);
$asMessage['relative_time'] = Toolbox::getDateTimeDesc($asMessage['unix_timestamp']);
$asMessage['formatted_time'] = date(self::FORMAT_TIME, $asMessage['unix_timestamp']);
$asAllFeedMessages[] = $asMessage;
}
}
$asMessages = $this->oDb->selectRows($asInfo);
foreach($asMessages as $iKey=>$asMessage)
{
$asMessages[$iKey]['unix_timestamp'] = (int) $asMessages[$iKey]['unix_timestamp'];
$asMessages[$iKey]['latitude'] = floatval($asMessages[$iKey]['latitude']);
$asMessages[$iKey]['longitude'] = floatval($asMessages[$iKey]['longitude']);
$asMessages[$iKey]['relative_time'] = Toolbox::getDateTimeDesc($asMessages[$iKey]['unix_timestamp']);
$asMessages[$iKey]['formatted_time'] = date(self::FORMAT_TIME, $asMessages[$iKey]['unix_timestamp']);
}
return $asMessages;
return $asAllFeedMessages;
}
private function getPictures()
@@ -234,9 +276,8 @@ class Spot extends Main
//Get/Create thumbnail
$sThumbnailPath = self::getPicThumbnail($sPicPath);
//Filter on valid time interval (Histo mode only)
if( Settings::MODE != self::MODE_HISTO ||
$iPicTimeStamp >= strtotime(Settings::HISTO_SPAN['from']) && $iPicTimeStamp <= strtotime(Settings::HISTO_SPAN['to'])) {
//Filter on valid time interval
if($iPicTimeStamp >= strtotime($this->oProject->getActivePeriod('from')) && $iPicTimeStamp <= strtotime($this->oProject->getActivePeriod('to'))) {
$asPics[] = array(
'path' => $sPicPath,
@@ -253,15 +294,19 @@ class Spot extends Main
private function getPosts()
{
$asInfo = array('from'=>self::POST_TABLE);
if(Settings::MODE==self::MODE_HISTO) {
$asInfo['constraint'] = array('led'=>Settings::HISTO_SPAN);
$asInfo['constOpe'] = array('led'=>"BETWEEN");
$asInfo = array(
'from' => self::POST_TABLE,
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()),
'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=")
);
if($this->oProject->getMode()==Project::MODE_HISTO) {
$asInfo['constraint']['timestamp'] = $this->oProject->getActivePeriod();
$asInfo['constOpe']['timestamp'] = "BETWEEN";
}
$asPosts = $this->oDb->selectRows($asInfo);
foreach($asPosts as &$asPost) {
$iUnixTimeStamp = strtotime($asPost['led']);
$iUnixTimeStamp = strtotime($asPost['timestamp']);
$asPost['unix_timestamp'] = $iUnixTimeStamp;
$asPost['relative_time'] = Toolbox::getDateTimeDesc($iUnixTimeStamp);
$asPost['formatted_time'] = date(self::FORMAT_TIME, $iUnixTimeStamp);
@@ -320,7 +365,12 @@ class Spot extends Main
public function addPost($sName, $sPost)
{
$asData = array('name'=>mb_strtolower(trim($sName)), 'content'=>trim($sPost));
$asData = array(
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'name' => mb_strtolower(trim($sName)),
'content' => trim($sPost),
'timestamp' => date(Db::TIMESTAMP_FORMAT)
);
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
return self::getJsonResult(($iPostId > 0), '');
}
@@ -337,6 +387,7 @@ class Spot extends Main
{
if(isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] == $this->asContext['serv_name']) {
$asDomains = array();
$sReferer = 'http://spot.lutran.fr';
switch($sMapId) {
case 'mapbox.satellite':
case 'mapbox.streets':
@@ -348,10 +399,19 @@ class Spot extends Main
$sToken = Settings::LINZ_KEY;
$asDomains = array('a', 'b', 'c', 'd');
break;
case 'ign.es':
$sPattern = 'http://www.ign.es/wmts/mapa-raster?request=getTile&format=image/png&layer=MTN&TileMatrixSet=GoogleMapsCompatible&TileMatrix={z}&TileCol={x}&TileRow={y}';
break;
case 'ign.fr':
$sPattern = 'https://wxs.ign.fr/{token}/geoportail/wmts?LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}';
$sToken = Settings::IGN_FR_KEY;
$sReferer = 'https://www.visugpx.com/yfJDwfuTlf';
break;
}
$oCacher = new Cacher($sPattern, $sMapId);
$oCacher->setToken($sToken);
$oCacher->setDomains($asDomains);
$oCacher->setReferer($sReferer);
return $oCacher->pushTile($iX, $iY, $iZ);
}

View File

@@ -22,12 +22,14 @@ $sName = isset($_GET['name'])?$_GET['name']:'';
$sContent = isset($_GET['content'])?$_GET['content']:'';
$iChunk = isset($_GET['chunk'])?$_GET['chunk']:0;
$sId = isset($_REQUEST['id'])?$_REQUEST['id']:'';
$iProjectId = isset($_REQUEST['project_id'])?$_REQUEST['project_id']:'';
$iX = isset($_GET['x'])?$_GET['x']:0;
$iY = isset($_GET['y'])?$_GET['y']:0;
$iZ = isset($_GET['z'])?$_GET['z']:0;
//Initiate class
$oSpot = new Spot($oClassManagement, __FILE__);
$oSpot->setProjectId($iProjectId);
$sResult = '';
if($sAction!='')
@@ -49,6 +51,9 @@ if($sAction!='')
case 'tile':
$sResult = $oSpot->getTile($sId, $iX, $iY, $iZ);
break;
/*case 'sql':
$sResult = $oSpot->getDbBuildScript();
break;*/
default:
$sResult = Spot::getJsonResult(false, Spot::NOT_FOUND);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,376 +0,0 @@
<div id="messages">
<div id="map">
<div class="loader fa fa-map"></div>
</div>
<div id="legend">
<div class="line te_araroa">Te Araroa</div>
<div class="line routeburn">Routeburn Track</div>
<div class="line hitchhiking">Hors rando</div>
</div>
<div id="feed">
<div id="posts">
<div id="poster"></div>
<div id="posts_list"></div>
<div id="loading"></div>
</div>
</div>
</div>
<script type="text/javascript" src="script/lightbox.min.js"></script>
<script type="text/javascript">
oSpot.pageInit = function(asHash)
{
self.tmp('$Map', $('#map'));
//Add Loading
var asLoading = {
type: 'loading',
formatted_time: '',
relative_time: '',
displayed_id: 'Chargement...'
};
getPost(asLoading).appendTo($('#loading'));
self.get('messages', function(oMessages){
//Build Feed
self.tmp('updatable', true);
self.tmp('out-of-data', 'boolean');
updateFeed(true);
self.tmp('simple-bar', new SimpleBar($('#posts')[0]));
self.tmp('simple-bar').getScrollElement().addEventListener('scroll', onFeedScroll);
self.tmp('infowindow', 'boolean');
self.tmp('map_offset', -0.3);
self.tmp('tile_api', '?a=tile&id={id}&z={z}&x={x}&y={y}');
//Centering map
var agCenter = {lat:0, lng:0};
var iZoom = 0;
if(self.vars('mode')=='blog')
{
//on last message
var oLastMsg = oMessages[oMessages.length-1];
agCenter.lat = oLastMsg.latitude;
agCenter.lng = oLastMsg.longitude;
iZoom = 12;
}
else
{
var iMapPadding = 0.05;
var iMinLat, iMaxLat, iMinLng, iMaxLng;
iMinLat = iMinLng = 180;
iMaxLat = iMaxLng = -180;
$.each(oMessages, function(iKey, oMsg){
iMinLat = Math.min(iMinLat, oMsg.latitude);
iMaxLat = Math.max(iMaxLat, oMsg.latitude);
iMinLng = Math.min(iMinLng, oMsg.longitude);
iMaxLng = Math.max(iMaxLng, oMsg.longitude);
});
//Get Marker bounds
var oSouthWest = L.latLng(iMinLat, iMinLng);
var oNorthEast = L.latLng(iMaxLat, iMaxLng);
oMarkerBounds = new L.LatLngBounds(oSouthWest, oNorthEast);
agCenter = oMarkerBounds.getCenter();
//Calculate adequate zoom (map.fitBounds is dezooming too much)
var oMapDim = {
height: self.tmp('$Map').height()*(1 - iMapPadding),
width: self.tmp('$Map').width()*(1 + self.tmp('map_offset') - iMapPadding)
};
iZoom = getBoundsZoomLevel(oMarkerBounds, oMapDim);
}
//Tile layers
var oMapBoxSat = L.tileLayer(self.tmp('tile_api'), {id: 'mapbox.satellite', minZoom: 0, maxZoom: 19}),
//oOpenTopoMap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {id: 'OpenTopoMap', minZoom: 2, maxZoom: 19});
//oMapBoxStreet = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token='+self.vars('mapbox_key'), {id: 'mapbox.streets'}),
oLinz = L.tileLayer(self.tmp('tile_api'), {id: 'linz', maxZoom: 17, continuousWorld: true, attribution: 'Sourced from LINZ. CC BY 4.0'});
//oGoogleSatellite = L.tileLayer('https://mt.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {id: 'GoogleSatellite', minZoom: 1, maxZoom: 22});
//Map
var oMap = L.map(self.tmp('$Map')[0], {
center: agCenter,
zoom: iZoom,
layers: [oMapBoxSat],
attributionControl: false,
zoomControl: false
});
//Controls
L.control.layers({'Satellite': oMapBoxSat, 'LINZ': oLinz}, null, {position: 'topleft'}).addTo(oMap);
//Te Araroa track
$.ajax({
dataType: 'json',
url: 'kml/TeAraroaTrail_simplified.geojson',
mimeType: 'application/json',
success: function(aoTracks) {
//Get track colors
var asColors = {};
$('#legend').find('.line').each(function(iKey, oLegend){
var $Legend = $(oLegend);
asColors[$Legend.attr('class').replace('line', '').trim()] = $Legend.css('border-top-color');
});
//Assign track color & popup
L.geoJson(aoTracks, {
style: function(oTrack) {return {color: asColors[oTrack.properties.type], weight: 4, opacity: 1};}
})
.bindPopup(function(oLayer) {
var asProperties = oLayer.feature.properties;
var $Tooltip = $('<div>', {'class':'track_tooltip'});
$('<p>', {'class':'name'}).text(asProperties.Name).appendTo($Tooltip);
if(asProperties.Name != asProperties.description) $('<p>', {'class':'description'}).text(asProperties.description).appendTo($Tooltip);
return $Tooltip[0];
})
.addTo(oMap);
}
});
//Building messages
$.each(oMessages, function(iKey, oMsg){
//Marker
var oMarker = L.marker(L.latLng(oMsg.latitude, oMsg.longitude), {
id: oMsg.id_message,
riseOnHover: true,
icon: L.icon({
iconUrl: (iKey%2==0)?'images/footprint_alt.png':'images/footprint.png',
iconSize: [32, 37],
iconAnchor: [16, 37]
})
}).addTo(oMap);
//Marker events
oMarker.on({
mouseover: function(){
this.openPopup();
},
/*mouseout: function(){
},*/
click: function(oPoint){
self.tmp('map').setOffsetView(self.tmp('map_offset'), oPoint.latlng, 15);
}
});
//Tooltip
$Tooltip = $('<div>', {'class':'info-window'})
.append($('<h1>').append('Message '+oMsg.type+' #'+oMsg.id_message))
.append($('<p>', {'class':'time'}).addIcon('fa-clock-o').append(oMsg.formatted_time+' ('+oMsg.relative_time+')'))
.append($('<p>', {'class':'coordinates'}).addIcon('fa-compass').append('Lat : '+oMsg.latitude+', Lng : '+oMsg.longitude));
//Tooltip pictures
if(oMsg.pics) {
var $Pics = $('<div>', {'class':'pics'});
$.each(oMsg.pics, function(iKey, asPic){
$Pics.append($('<a>', {href: asPic.path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time})
.append($('<img>', {'src': asPic.thumb_path})));
});
$Tooltip
.append($('<p>').addIcon('fa-image').append('Photos'))
.append($Pics);
}
oMarker.bindPopup($Tooltip[0], {
maxWidth: 1000,
keepInView: true,
closeOnClick: true,
offset: new L.Point(0, -30)
});
});
//Recenter map to be at the center of 70% (map_offset) of the page, 30% being used by posts
if(self.vars('mode')!='blog') oMap.setOffsetView(self.tmp('map_offset'));
//Legend
var oLegend = L.control({position: 'bottomright'});
oLegend.onAdd = function(oMap) {return L.DomUtil.get('legend');};
oLegend.addTo(oMap);
self.tmp('map', oMap);
});
//Post
if(self.vars('mode')=='histo') $('#poster').hide();
else {
var asPoster = {
type: 'poster',
formatted_time: '',
relative_time: 'Nouveau message'
};
getPost(asPoster).appendTo($('#poster'));
$('#name').defaultVal('Nom...');
$('#post').defaultVal('Ton message...');
$('#submit').click(function(){
if($('#poster').checkForm())
{
self.get(
'add_post',
function()
{
$('#name').val('');
$('#post').val('');
updateFeed(true);
},
{name:$('#name').val(), content:$('#post').val()}
);
}
});
}
};
function getBoundsZoomLevel(bounds, mapDim) {
var WORLD_DIM = { height: 256, width: 256 };
var ZOOM_MAX = 21;
function latRad(lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, worldPx, fraction) {
return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat) - latRad(sw.lat)) / Math.PI;
var lngDiff = ne.lng - sw.lng;
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
L.Map.include({
setOffsetView: function (iOffsetRatioX, oCenter, iZoomLevel) {
var oCenter = (typeof oCenter == 'object')?$.extend({}, oCenter):this.getCenter();
iZoomLevel = iZoomLevel || this.getZoom();
var oBounds = this.getBounds();
var iOffsetX = (oBounds.getEast() - oBounds.getWest()) * iOffsetRatioX / ( 2 * Math.pow(2, iZoomLevel - this.getZoom()));
oCenter.lng = oCenter.lng - iOffsetX;
this.setView(oCenter, iZoomLevel);
}
});
function onFeedScroll(){
var $Box = $(this);
var $BoxContent = $Box.find('.simplebar-content');
if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) updateFeed();
}
function updateFeed(bFirstChunk)
{
bFirstChunk = bFirstChunk || false;
if(self.tmp('updatable')) {
if(!self.tmp('out-of-data') || bFirstChunk) {
var $Posts = $('#posts_list');
if(bFirstChunk===true) {
$Posts.empty();
self.tmp('news_chunk', 0);
}
self.tmp('updatable', false);
$('#loading').fadeIn('fast');
self.get('feed', function(asData) {
$.each(asData, function(iKey, asPost){
getPost(asPost).appendTo($Posts);
});
//oSimpleBar.recalculate();
self.tmp('news_chunk', self.tmp('news_chunk') + 1);
self.tmp('out-of-data', Object.keys(asData).length != self.vars('chunk_size'));
self.tmp('updatable', true);
$('#loading').fadeOut('fast');
}, {
'chunk': self.tmp('news_chunk')
});
}
}
else if(bFirstChunk) { //Delaying important data load
if(typeof oUpdateTimer != 'undefined') clearTimeout(oUpdateTimer);
oUpdateTimer = setTimeout(function(){updateFeed(true);}, 200);
}
}
function getPost(asPost) {
var $Post = $('<div>', {'class':'post '+asPost.type});
var sRelTime = (self.vars('mode')=='histo')?asPost.formatted_time.substr(0, 10):asPost.relative_time;
var sAbsTime = asPost.formatted_time;
var $Body = {};
switch(asPost.type)
{
case 'message':
$Body = $('<div>')
.append($('<p>').addIcon('fa-compass', true).append('Latitude '+asPost.latitude+', Longitude '+asPost.longitude))
.append($('<p>').addIcon('fa-clock-o', true).append(sAbsTime))
.append(
$('<img>', {'class':'staticmap', title: 'Click pour zoomer', src: getStaticMapUrl(asPost.latitude, asPost.longitude)})
.data('lat', asPost.latitude)
.data('lng', asPost.longitude)
.click(function(){
var $This = $(this);
var oCenter = L.latLng(parseFloat($This.data('lat')), parseFloat($This.data('lng')));
self.tmp('map').setOffsetView(self.tmp('map_offset'), oCenter, 13);
})
);
sClass = 'compass';
break;
case 'picture':
var $Image = $('<img>', {'src': asPost.thumb_path/*, 'style':'transform:rotate('+asPost.rotate+'deg);'*/});
$Body = $('<a>', {href: asPost.path, 'data-lightbox': self.consts.title, 'data-title': sAbsTime}).append($Image);
sClass = 'image';
break;
case 'post':
$Body = $('<div>')
.append($('<p>', {'class':'message'}).text(asPost.content))
.append($('<p>', {'class':'signature'}).text('-- '+asPost.formatted_name));
sClass = 'comment';
break;
case 'poster':
$Body = $('<p>', {'class':'message'})
.append($('<input>', {type:'text', id:'post', name:'post'}))
.append($('<input>', {type:'text', id:'name', name:'name'}))
.append($('<button>', {type:'button', id:'submit', name:'submit'}).addIcon('fa-send'));
sClass = 'comment';
break;
case 'loading':
$Body = $('<p>', {'class':'spinner'}).addIcon('fa-spin fa-spinner');
sClass = 'tasks';
break;
}
$Post
.append($('<div>', {'class':'header'})
.append($('<span>', {'class':'index'}).addIcon('fa-'+sClass))
.append($('<span>', {'class':'time', 'title':sAbsTime}).text(sRelTime)))
.append($('<div>', {'class':'body'}).append($Body));
if(asPost.displayed_id) $Post.find('.index').append(' '+asPost.displayed_id);
return $Post;
}
function getStaticMapUrl(oCenterLat, oCenterLng) {
var asParams = [
'https://api.mapbox.com/v4/mapbox.satellite', //Domain
'url-http%3A%2F%2Fspot.lutran.fr%2Fimages%2Ffootprint.png('+oCenterLng+','+oCenterLat+')', //Marker
oCenterLng+','+oCenterLat+',13', //Center + zoom
'400x300.png?access_token='+self.vars('mapbox_key') //Image size + access token
];
return asParams.join('/');
}
</script>

385
masks/project.html Executable file
View File

@@ -0,0 +1,385 @@
<div id="messages">
<div id="map">
<div class="loader fa fa-map"></div>
</div>
<div id="legend">
<div class="line main">Trajet principal</div>
<div class="line off-track">Variante</div>
<div class="line hitchhiking">Hors rando</div>
</div>
<div id="feed">
<div id="posts">
<div id="poster"></div>
<div id="posts_list"></div>
<div id="loading"></div>
</div>
</div>
</div>
<script type="text/javascript" src="script/lightbox.min.js"></script>
<script type="text/javascript">
self.onSamePageMove = function(asHash){
self.tmp('map').remove();
self.tmp('$Map').empty();
self.pageInit(asHash);
return false;
};
oSpot.pageInit = function(asHash)
{
//Set active project
if(asHash.items.length==0) {
self.setHash(asHash.page, [self.vars('default_project_codename')]);
return;
}
self.vars('project', self.vars(['projects', asHash.items[0]]));
self.tmp('$Map', $('#map'));
self.tmp('tracktype-colors', 'object');
//Assign Track Type Colors
$('#legend').find('.line').each(function(iKey, oLegend){
var $Legend = $(oLegend);
var sTrackType = $Legend.attr('class').replace('line', '').trim();
self.tmp(['tracktype-colors', sTrackType], $Legend.css('border-top-color'));
});
//Add "Loading" Post
var asLoading = {
type: 'loading',
formatted_time: '',
relative_time: '',
displayed_id: 'Chargement...'
};
getPost(asLoading).appendTo($('#loading'));
//Spot Messages
$.when(
self.get(
'messages',
function(){},
{project_id: self.vars(['project', 'id'])},
function(e){console.log(e);}
),
$.ajax({
dataType: 'json',
url: self.consts.geo_folder+self.vars(['project', 'geofile']),
mimeType: 'application/json'
})
).done(function(oMessages, aoTracks) {
buildSpotMessages(oMessages[0]['data'], aoTracks[0]);
});
//Posts
updateFeed(true);
initPosts();
};
function buildSpotMessages(oMessages, aoTracks){
//Build Feed
self.tmp('updatable', true);
self.tmp('out-of-data', 'boolean');
self.tmp('simple-bar', new SimpleBar($('#posts')[0]));
self.tmp('simple-bar').getScrollElement().addEventListener('scroll', onFeedScroll);
self.tmp('infowindow', 'boolean');
self.tmp('feed_width', $('#feed').outerWidth(true));
self.tmp('map_offset', -1 * self.tmp('feed_width') / $('body').outerWidth(true));
self.tmp('map_padding', 0.05);
self.tmp('tile_api', '?a=tile&id={id}&z={z}&x={x}&y={y}');
//Tile layers
var oMapBoxSat = L.tileLayer(self.tmp('tile_api'), {id: 'mapbox.satellite', minZoom: 0, maxZoom: 19}),
//oOpenTopoMap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {id: 'OpenTopoMap', minZoom: 2, maxZoom: 19});
//oMapBoxStreet = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token='+self.vars('mapbox_key'), {id: 'mapbox.streets'}),
oIgnSpain = L.tileLayer(self.tmp('tile_api'), {id: 'ign.es', minZoom: 1, maxZoom: 20}),
oIgnFrance = L.tileLayer(self.tmp('tile_api'), {id: 'ign.fr', minZoom: 0, maxZoom: 18, tileSize: 256}),
oLinz = L.tileLayer(self.tmp('tile_api'), {id: 'linz', maxZoom: 17, continuousWorld: true, attribution: 'Sourced from LINZ. CC BY 4.0'});
//oGoogleSatellite = L.tileLayer('https://mt.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {id: 'GoogleSatellite', minZoom: 1, maxZoom: 22});
//Map
var oMap = L.map(self.tmp('$Map')[0], {
//center: agCenter,
//zoom: iZoom,
layers: [oMapBoxSat],
attributionControl: false,
zoomControl: false
});
//Tracks, colors & popup
var oTracks = L.geoJson(aoTracks, {
style: function(oTrack) {return {color: self.tmp(['tracktype-colors', oTrack.properties.type]), weight: 4, opacity: 1};}
})
.bindPopup(function(oLayer) {
var asProperties = oLayer.feature.properties;
var $Tooltip = $('<div>', {'class':'track_tooltip'});
$('<p>', {'class':'name'}).text(asProperties.name).appendTo($Tooltip);
if(asProperties.Name != asProperties.description) $('<p>', {'class':'description'}).text(asProperties.description).appendTo($Tooltip);
return $Tooltip[0];
})
.addTo(oMap);
//Centering map
if(self.vars(['project', 'mode'])==self.consts.modes.blog)
{
//Zoom on last message
var oLastMsg = oMessages[oMessages.length-1];
oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15);
//Recenter map to be at the center of 70% (map_offset) of the page, 30% being used by posts
oMap.setOffsetView(self.tmp('map_offset'));
}
else oMap.fitBounds(oTracks.getBounds(), {paddingTopLeft: L.point(5, 42), paddingBottomRight: L.point(self.tmp('feed_width')+5, 5)});
//Controls
L.control.layers({
'Satellite': oMapBoxSat,
'IGN (France)': oIgnFrance,
'IGN (Espagne)': oIgnSpain,
'LINZ (Nouvelle-Zélande)': oLinz
}, null, {position: 'topleft'}).addTo(oMap);
//Building messages
$.each(oMessages, function(iKey, oMsg){
//Marker
var oMarker = L.marker(L.latLng(oMsg.latitude, oMsg.longitude), {
id: oMsg.id_message,
riseOnHover: true,
icon: L.icon({
iconUrl: (iKey%2==0)?'images/footprint_alt.png':'images/footprint.png',
iconSize: [32, 37],
iconAnchor: [16, 37]
})
}).addTo(oMap);
//Marker events
oMarker.on({
mouseover: function(){
this.openPopup();
},
/*mouseout: function(){
},*/
click: function(oPoint){
self.tmp('map').setOffsetView(self.tmp('map_offset'), oPoint.latlng, 15);
}
});
//Tooltip
$Tooltip = $('<div>', {'class':'info-window'})
.append($('<h1>').append('Message '+oMsg.type+' #'+oMsg.id_message))
.append($('<p>', {'class':'time'}).addIcon('fa-clock-o').append(oMsg.formatted_time+' ('+oMsg.relative_time+')'))
.append($('<p>', {'class':'coordinates'}).addIcon('fa-compass').append('Lat : '+oMsg.latitude+', Lng : '+oMsg.longitude));
//Tooltip pictures
if(oMsg.pics) {
var $Pics = $('<div>', {'class':'pics'});
$.each(oMsg.pics, function(iKey, asPic){
$Pics.append($('<a>', {href: asPic.path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time})
.append($('<img>', {'src': asPic.thumb_path})));
});
$Tooltip
.append($('<p>').addIcon('fa-image').append('Photos'))
.append($Pics);
}
oMarker.bindPopup($Tooltip[0], {
maxWidth: 1000,
keepInView: true,
closeOnClick: true,
offset: new L.Point(0, -30)
});
});
//Legend
var oLegend = L.control({position: 'bottomright'});
oLegend.onAdd = function(oMap) {return L.DomUtil.get('legend');};
oLegend.addTo(oMap);
self.tmp('map', oMap);
}
function initPosts() {
if(self.vars(['project', 'mode'])==self.consts.modes.histo) $('#poster').hide();
else {
var asPoster = {
type: 'poster',
formatted_time: '',
relative_time: 'Nouveau message'
};
getPost(asPoster).appendTo($('#poster'));
//$('#name').defaultVal('Nom...');
//$('#post').defaultVal('Ton message...');
$('#submit').click(function(){
if($('#poster').checkForm())
{
self.get(
'add_post',
function()
{
$('#name').val('');
$('#post').val('');
updateFeed(true);
},
{
project_id: self.vars(['project', 'id']),
name: $('#name').val(),
content: $('#post').val()
}
);
}
});
}
}
function getBoundsZoomLevel(bounds, mapDim) {
var WORLD_DIM = { height: 256, width: 256 };
var ZOOM_MAX = 21;
function latRad(lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, worldPx, fraction) {
return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat) - latRad(sw.lat)) / Math.PI;
var lngDiff = ne.lng - sw.lng;
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
L.Map.include({
setOffsetView: function (iOffsetRatioX, oCenter, iZoomLevel) {
var oCenter = (typeof oCenter == 'object')?$.extend({}, oCenter):this.getCenter();
iZoomLevel = iZoomLevel || this.getZoom();
var oBounds = this.getBounds();
var iOffsetX = (oBounds.getEast() - oBounds.getWest()) * iOffsetRatioX / ( 2 * Math.pow(2, iZoomLevel - this.getZoom()));
oCenter.lng = oCenter.lng - iOffsetX;
this.setView(oCenter, iZoomLevel);
}
});
function onFeedScroll(){
var $Box = $(this);
var $BoxContent = $Box.find('.simplebar-content');
if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) updateFeed();
}
function updateFeed(bFirstChunk)
{
bFirstChunk = bFirstChunk || false;
if(self.tmp('updatable')) {
if(!self.tmp('out-of-data') || bFirstChunk) {
var $Posts = $('#posts_list');
if(bFirstChunk===true) {
$Posts.empty();
self.tmp('news_chunk', 0);
}
self.tmp('updatable', false);
$('#loading').fadeIn('fast');
self.get('feed', function(asData) {
$('#loading').hide();
$.each(asData, function(iKey, asPost){
getPost(asPost).appendTo($Posts);
});
self.tmp('news_chunk', self.tmp('news_chunk') + 1);
self.tmp('out-of-data', Object.keys(asData).length != self.vars('chunk_size'));
self.tmp('updatable', true);
}, {
'project_id': self.vars(['project', 'id']),
'chunk': self.tmp('news_chunk')
});
}
}
else if(bFirstChunk) { //Delaying important data load
if(typeof oUpdateTimer != 'undefined') clearTimeout(oUpdateTimer);
oUpdateTimer = setTimeout(function(){updateFeed(true);}, 200);
}
}
function getPost(asPost) {
var $Post = $('<div>', {'class':'post '+asPost.type});
var sRelTime = (self.vars(['project', 'mode'])==self.consts.modes.histo)?asPost.formatted_time.substr(0, 10):asPost.relative_time;
var sAbsTime = asPost.formatted_time;
var $Body = {};
switch(asPost.type)
{
case 'message':
$Body = $('<div>')
.append($('<p>').addIcon('fa-compass', true).append('Latitude '+asPost.latitude+', Longitude '+asPost.longitude))
.append($('<p>').addIcon('fa-clock-o', true).append(sAbsTime))
.append(
$('<img>', {'class':'staticmap', title: 'Click pour zoomer', src: getStaticMapUrl(asPost.latitude, asPost.longitude)})
.data('lat', asPost.latitude)
.data('lng', asPost.longitude)
.click(function(){
var $This = $(this);
var oCenter = L.latLng(parseFloat($This.data('lat')), parseFloat($This.data('lng')));
self.tmp('map').setOffsetView(self.tmp('map_offset'), oCenter, 13);
})
);
sClass = 'compass';
break;
case 'picture':
var $Image = $('<img>', {'src': asPost.thumb_path/*, 'style':'transform:rotate('+asPost.rotate+'deg);'*/});
$Body = $('<a>', {href: asPost.path, 'data-lightbox': self.consts.title, 'data-title': sAbsTime}).append($Image);
sClass = 'image';
break;
case 'post':
$Body = $('<div>')
.append($('<p>', {'class':'message'}).text(asPost.content))
.append($('<p>', {'class':'signature'}).text('-- '+asPost.formatted_name));
sClass = 'comment';
break;
case 'poster':
$Body = $('<p>', {'class':'message'})
.append($('<input>', {type:'text', id:'post', name:'post', placeholder:'Message'}))
.append($('<input>', {type:'text', id:'name', name:'name', placeholder:'Nom'}))
.append($('<button>', {type:'button', id:'submit', name:'submit'}).addIcon('fa-send'));
sClass = 'comment';
break;
case 'loading':
$Body = $('<p>', {'class':'spinner'}).addIcon('fa-spin fa-spinner');
sClass = 'tasks';
break;
}
$Post
.append($('<div>', {'class':'header'})
.append($('<span>', {'class':'index'}).addIcon('fa-'+sClass))
.append($('<span>', {'class':'time', 'title':sAbsTime}).text(sRelTime)))
.append($('<div>', {'class':'body'}).append($Body));
if(asPost.displayed_id) $Post.find('.index').append(' '+asPost.displayed_id);
return $Post;
}
function getStaticMapUrl(oCenterLat, oCenterLng) {
var asParams = [
'https://api.mapbox.com/v4/mapbox.satellite', //Domain
'url-http%3A%2F%2Fspot.lutran.fr%2Fimages%2Ffootprint.png('+oCenterLng+','+oCenterLat+')', //Marker
oCenterLng+','+oCenterLat+',13', //Center + zoom
'400x300.png?access_token='+self.vars('mapbox_key') //Image size + access token
];
return asParams.join('/');
}
</script>

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ function Spot(asGlobals)
this.consts = asGlobals.consts;
this.consts.hash_sep = '-';
this.consts.title = 'Te Araroa';
this.consts.default_page = 'messages';
this.consts.default_page = 'project';
/* Initialization */
@@ -63,11 +63,12 @@ function Spot(asGlobals)
this.get = function(sAction, fOnSuccess, oVars, fOnError, bProcessIcon)
{
if(!oVars) oVars = {};
fOnError = fOnError || function(textStatus, errorThrown) {console.log(textStatus+' '+errorThrown);};
bProcessIcon = bProcessIcon || false;
//if(bProcessIcon) self.addBufferIcon();
oVars['a'] = sAction;
$.ajax(
return $.ajax(
{
url: self.consts.process_page,
data: oVars,
@@ -85,8 +86,7 @@ function Spot(asGlobals)
.fail(function(jqXHR, textStatus, errorThrown)
{
//if(bProcessIcon) self.resetIcon();
if(!fOnError) console.log(textStatus+' '+errorThrown);
else fOnError(textStatus);
fOnError(textStatus, errorThrown);
});
};

View File

@@ -10,11 +10,7 @@ class Settings
const TEXT_ENC = 'UTF-8';
const TIMEZONE = 'Europe/Paris';
const MAPBOX_KEY = '';
const IGN_FR_KEY = '';
const LINZ_KEY = '';
const MODE = Spot::MODE_BLOG; //Spot::MODE_HISTO/MODE_BLOG
const HISTO_SPAN = array('from'=>'2015-12-29 00:00:00', 'to'=>'2016-02-23 23:59:59');
const FEED_ID = ''; //Spot Feed ID
const DEBUG = true;
}
?>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,34 +6,37 @@
@import 'leaflet';
@import 'common';
#map {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 100%;
background: #EEE;
#messages {
.loader {
#map {
position: absolute;
font-size: 3em;
width: 1em;
height: 1em;
top: calc(50% - 0.5em);
left: calc(50% - 0.5em);
color: #666;
@extend .flicker;
}
.track_tooltip {
p {
margin: 0;
&.name {
font-weight: bold;
}
&.description {
font-style: italic;
left: 0;
top: 0;
bottom: 0;
width: 100%;
background: #EEE;
.loader {
position: absolute;
font-size: 3em;
width: 1em;
height: 1em;
top: calc(50% - 0.5em);
left: calc(50% - 0.5em);
color: #666;
@extend .flicker;
}
.track_tooltip {
p {
margin: 0;
&.name {
font-weight: bold;
}
&.description {
font-style: italic;
}
}
}
}
@@ -57,11 +60,10 @@
font-size: 1em;
color: #222;
&.te_araroa {
&.main {
border-color: #00ff78;
}
&.routeburn {
&.off-track {
border-color: #0000ff;
}
&.hitchhiking {
@@ -73,9 +75,10 @@
#feed {
position: absolute;
right: 1em;
top: 0em;
bottom: 0em;
right: 0;
top: 0;
bottom: 0;
margin-right: 1em;
width: calc(30% - 1em);
z-index: 999;

8
todo Normal file
View File

@@ -0,0 +1,8 @@
To Do List
----------
- Elevation chart
- Radio button projects
- Hover on track segments
- Table for images
- Static map in backend
- Device/Spot Class