Files
spot/inc/spot.php

466 lines
15 KiB
PHP
Executable File

<?php
class Spot extends Main
{
//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';
const FEED_TYPE_JSON = '/message.json';
//Database
const MSG_TABLE = 'messages';
const FEED_TABLE = 'feeds';
const SPOT_TABLE = 'spots';
const POST_TABLE = 'posts';
const FORMAT_TIME = 'd/m/Y à H:i';
const FEED_CHUNK_SIZE = 15;
/**
* Active Project
* @var Project
*/
private $oProject;
/**
* Picture Class
* @var Picture
*/
private $oPicture;
public function __construct($oClassManagement, $sProcessPage)
{
$asClasses = array(
array('name'=>'project', 'project'=>true),
array('name'=>'picture', 'project'=>true),
array('name'=>'cacher', 'project'=>true)
);
parent::__construct($oClassManagement, $sProcessPage, $asClasses);
$this->oProject = new Project($this->oDb);
$this->oPicture = new Picture($this->oDb, $this->oProject);
}
protected function install()
{
//Install DB
$this->oDb->install();
}
protected function getSqlOptions()
{
return array
(
'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), 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'),
Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'timestamp', 'rotate')
),
'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)",
'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",
'filename' => "VARCHAR(100) NOT NULL",
'taken_on' => "DATETIME",
'rotate' => "SMALLINT"
),
'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`)",
Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)",
Picture::PIC_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)"
),
'cascading_delete' => array
(
self::SPOT_TABLE=>array(self::MSG_TABLE)
)
);
}
public function getMainPage($asGlobalVars = array(), $sMainPage = 'index', $asMainPageTags=array())
{
return parent::getMainPage(
array(
'vars' => array(
'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
)
),
'index',
array(
'filepath_css' => self::addTimestampToFilePath('style/spot.css'),
'filepath_js_leaflet' => self::addTimestampToFilePath('script/leaflet.min.js'),
'filepath_js_jquery' => self::addTimestampToFilePath('script/jquery.min.js'),
'filepath_js_jquery_mods' => self::addTimestampToFilePath('script/jquery.mods.js'),
'filepath_js_spot' => self::addTimestampToFilePath('script/spot.js')
)
);
}
/* Managing projects */
public function setProjectId($iProjectId=0) {
$this->oProject->setProjectId($iProjectId);
}
/* Getting & Storing messages */
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)
{
$asData = $this->getFeed($sRefFeedId);
$asMsgs = $asData['response']['feedMessageResponse']['messages'];
$asFeed = $asData['response']['feedMessageResponse']['feed'];
if(!empty($asMsgs))
{
//Update Spot Info
$asFirstMsg = array_values($asMsgs)[0];
$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 and last update date
$asFeedInfo = array(
'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'));
//Update Messages
foreach($asMsgs as $asMsg)
{
$asMsg = array(
'ref_msg_id' => $asMsg['id'],
Db::getId(self::FEED_TABLE) => $iFeedId,
'type' => $asMsg['messageType'],
'latitude' => $asMsg['latitude'],
'longitude' => $asMsg['longitude'],
'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']
);
$this->oDb->insertUpdateRow(self::MSG_TABLE, $asMsg, array('ref_msg_id'));
}
}
}
public function getMarkers()
{
/* Adding another point to test
$test = $this->oDb->selectRow(self::MSG_TABLE, 1);
unset($test['id_message']);
$test['ref_msg_id'] = $test['ref_msg_id'] + 1;
$test['latitude'] = '-41.4395';
$test['longitude'] = '172.1936';
$this->oDb->insertUpdateRow(self::MSG_TABLE, $test, array('ref_msg_id')); */
$asMessages = $this->getSpotMessages();
$bSuccess = !empty($asMessages);
$sDesc = $bSuccess?'':self::NO_DATA;
//Add pictures
if($bSuccess) {
$asPics = $this->getPictures('taken_on');
//Sort messages and pictures chronologically
uasort($asPics, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];});
uasort($asMessages, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];});
//Assign pictures to closest message
$iIndex = 0;
$iMaxIndex = count($asMessages) - 1;
foreach($asPics as $asPic) {
while($iIndex <= $iMaxIndex && $asPic['unix_timestamp'] > $asMessages[$iIndex]['unix_timestamp']) {
$iIndex++;
}
if($iIndex == 0) $iMsgIndex = $iIndex;
elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
else {
$iHalfWayPoint = ($asMessages[$iIndex]['unix_timestamp'] - $asMessages[$iIndex - 1]['unix_timestamp'])/2;
$iMsgIndex = ($asPic['unix_timestamp'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
}
$asMessages[$iMsgIndex]['pics'][] = $asPic;
}
}
return self::getJsonResult($bSuccess, $sDesc, $asMessages);
}
private function getSpotMessages()
{
//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['latitude'] = floatval($asMessage['latitude']);
$asMessage['longitude'] = floatval($asMessage['longitude']);
$asMessage['lat_dms'] = self::DecToDMS($asMessage['latitude'], 'lat');
$asMessage['lon_dms'] = self::DecToDMS($asMessage['longitude'], 'lon');
$this->addTimeStamp($asMessage, $asMessage['unix_timestamp']);
$asAllFeedMessages[] = $asMessage;
}
}
return $asAllFeedMessages;
}
/**
* Get valid pictures based on $sTimeRefField:
* - taken_on: Date/time on which the picture was taken (local time)
* - added_on: Date/time on which the picture was uploaded (site's time: Settings::TIMEZONE)
* @param String $sTimeRefField Field to calculate relative times
* @return Array Pictures info
*/
private function getPictures($sTimeRefField)
{
$asPics = $this->oPicture->getPicsInfo();
$asValidPics = array();
foreach($asPics as &$asPic) {
$sTimeRef = $asPic[$sTimeRefField];
if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) {
$asPic['taken_on_formatted'] = date(self::FORMAT_TIME, strtotime($asPic['taken_on']));
$this->addTimeStamp($asPic, strtotime($sTimeRef));
$asValidPics[] = $asPic;
}
}
return $asValidPics;
}
private function getPosts()
{
$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['timestamp']);
$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
$this->addTimeStamp($asPost, $iUnixTimeStamp);
}
return $asPosts;
}
private static function getJsonId($iTimeStamp, $sPriority='0', $iDbId=0)
{
return ($iTimeStamp * -1).'.'.$sPriority.$iDbId;
}
private function addTimeStamp(&$asData, $iTime) {
$asData['unix_timestamp'] = (int) $iTime;
$asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, 'fr');
$asData['formatted_time'] = date(self::FORMAT_TIME, $iTime);
}
public function getNewsFeed($iChunk=0)
{
$asFeed = array();
//Messages
$asMessages = $this->getSpotMessages();
foreach($asMessages as $iIndex=>$asMessage)
{
$iId = self::getJsonId($asMessage['unix_timestamp'], '0', $asMessage[Db::getId(self::MSG_TABLE)]);
$asFeed[$iId] = $asMessage;
$asFeed[$iId]['type'] = 'message';
$asFeed[$iId]['displayed_id'] = 'N°'.$iIndex;
}
//Pictures
$asPics = $this->getPictures('added_on');
foreach($asPics as $iKey=>$asPic)
{
$iId = self::getJsonId($asPic['unix_timestamp'], '1', $iKey);
$asFeed[$iId] = $asPic;
$asFeed[$iId]['type'] = 'picture';
}
//Post
$asPosts = $this->getPosts();
foreach($asPosts as $asPost)
{
$iId = self::getJsonId($asPost['unix_timestamp'], '2', $asPost[Db::getId(self::POST_TABLE)]);
$asFeed[$iId] = $asPost;
$asFeed[$iId]['type'] = 'post';
}
//Sort by key
ksort($asFeed);
//Split chunks
$asFeed = array_slice($asFeed, $iChunk*self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE);
return self::getJsonResult(true, '', $asFeed);
}
public function syncPics() {
return $this->oPicture->syncFileFolder();
}
public function addPost($sName, $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), '');
}
public function upload()
{
$this->oClassManagement->incClass('uploader', true);
$oUploader = new Uploader($this->oPicture);
return self::getJsonResult(true, '');
}
public function getTile($sMapId, $iX, $iY, $iZ)
{
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':
$sPattern = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.png?access_token={token}';
$sToken = Settings::MAPBOX_KEY;
break;
case 'linz':
$sPattern = 'http://tiles-{s}.data-cdn.linz.govt.nz/services;key={token}/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png';
$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;
case 'static':
$sPattern = 'https://api.mapbox.com/v4/mapbox.satellite/url-http%3A%2F%2Fspot.lutran.fr%2Fimages%2Ffootprint.png({x},{y})/{x},{y},{z}/400x300.png?access_token={token}';
$sToken = Settings::MAPBOX_KEY;
break;
}
$oCacher = new Cacher($sPattern, $sMapId);
$oCacher->setToken($sToken);
$oCacher->setDomains($asDomains);
$oCacher->setReferer($sReferer);
return $oCacher->pushTile($iX, $iY, $iZ);
}
else {
header('HTTP/1.1 403 Forbidden');
exit;
}
}
public static function DecToDMS($dValue, $sType='lat') {
if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S';
else $sDirection = ($dValue >= 0)?'E':'W';
$dAbsValue = abs($dValue);
//Degrees
$iDegree = floor($dAbsValue);
$dLeft = $dAbsValue - $iDegree;
//Minutes
$iMinute = floor($dLeft * 60);
$dLeft -= $iMinute / 60;
//Seconds
$fSecond = round($dLeft * 3600, 1);
return $iDegree.'°'.$iMinute.'\''.$fSecond.'"'.$sDirection;
}
}
?>