402 lines
13 KiB
PHP
Executable File
402 lines
13 KiB
PHP
Executable File
<?php
|
|
|
|
/* Timezones
|
|
* - Feeds (table `feeds`):
|
|
* - last_update: timestamp in site time (see Settings::TIMEZONE)
|
|
* - Spot Messages (table `messages`):
|
|
* - unix_time: UNIX (int) in UTC
|
|
* - site_time: timestamp in site time (see Settings::TIMEZONE)
|
|
* - iso_time: raw ISO 8601 in local timezone
|
|
* - Pictures (table `pictures`):
|
|
* - posted_on: timestamp in site time (see Settings::TIMEZONE)
|
|
* - taken_on: timestamp in site time (see Settings::TIMEZONE)
|
|
* - Posts (table `posts`):
|
|
* - site_time: timestamp in site time (see Settings::TIMEZONE)
|
|
*/
|
|
|
|
class Spot extends Main
|
|
{
|
|
//Database
|
|
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'=>'feed', 'project'=>true),
|
|
array('name'=>'project', 'project'=>true),
|
|
array('name'=>'picture', 'project'=>true),
|
|
array('name'=>'cacher', 'project'=>true),
|
|
array('name'=>'converter', '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
|
|
(
|
|
Feed::MSG_TABLE => array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'unix_time', 'content', 'battery_state'),
|
|
Feed::FEED_TABLE => array('ref_feed_id', Db::getId(Feed::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'),
|
|
Feed::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', 'site_time'),
|
|
Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'posted_on', 'rotate')
|
|
),
|
|
'types' => array
|
|
(
|
|
'ref_msg_id' => "INT",
|
|
'type' => "VARCHAR(20)",
|
|
'latitude' => "DECIMAL(7,5)",
|
|
'longitude' => "DECIMAL(8,5)",
|
|
'iso_time' => "VARCHAR(24)",
|
|
'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time
|
|
'unix_time' => "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' => "TIMESTAMP DEFAULT 0",
|
|
'active_to' => "TIMESTAMP DEFAULT 0",
|
|
'geofile' => "VARCHAR(50)",
|
|
'timezone' => "VARCHAR(100)",
|
|
'last_update' => "TIMESTAMP DEFAULT 0",
|
|
'filename' => "VARCHAR(100) NOT NULL",
|
|
'taken_on' => "TIMESTAMP DEFAULT 0",
|
|
'posted_on' => "TIMESTAMP DEFAULT 0",
|
|
'rotate' => "SMALLINT"
|
|
),
|
|
'constraints' => array
|
|
(
|
|
Feed::MSG_TABLE => "UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)",
|
|
Feed::FEED_TABLE => "UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)",
|
|
Feed::SPOT_TABLE => "UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)",
|
|
Feed::MSG_TABLE => "INDEX(`ref_msg_id`)",
|
|
Feed::FEED_TABLE => "INDEX(`ref_feed_id`)",
|
|
Feed::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
|
|
(
|
|
Feed::SPOT_TABLE=>array(Feed::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,
|
|
'site_timezone' => Settings::TIMEZONE,
|
|
'site_timezone_desc' => self::getTimeZoneDesc(Settings::TIMEZONE)
|
|
)
|
|
),
|
|
'index',
|
|
array(
|
|
'filepath_css' => self::addTimestampToFilePath('style/spot.css'),
|
|
'filepath_js_d3' => self::addTimestampToFilePath('script/d3.min.js'),
|
|
'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);
|
|
}
|
|
|
|
public function getMarkers()
|
|
{
|
|
$asMessages = $this->getSpotMessages();
|
|
$bSuccess = !empty($asMessages);
|
|
$sDesc = $bSuccess?'':self::NO_DATA;
|
|
|
|
//Add pictures
|
|
if($bSuccess) {
|
|
$asPics = $this->getPictures('taken_on');
|
|
|
|
//Assign pictures to closest message
|
|
$iIndex = 0;
|
|
$iMaxIndex = count($asMessages) - 1;
|
|
foreach($asPics as $asPic) {
|
|
while($iIndex <= $iMaxIndex && $asPic['unix_time'] > $asMessages[$iIndex]['unix_time']) {
|
|
$iIndex++;
|
|
}
|
|
if($iIndex == 0) $iMsgIndex = $iIndex;
|
|
elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
|
|
else {
|
|
$iHalfWayPoint = ($asMessages[$iIndex - 1]['unix_time'] + $asMessages[$iIndex]['unix_time'])/2;
|
|
$iMsgIndex = ($asPic['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
|
|
}
|
|
|
|
$asMessages[$iMsgIndex]['pics'][] = $asPic;
|
|
}
|
|
}
|
|
|
|
return self::getJsonResult($bSuccess, $sDesc, $asMessages);
|
|
}
|
|
|
|
private function getSpotMessages()
|
|
{
|
|
//Get messages from all feeds belonging to the project
|
|
$asFeeds = $this->oProject->getFeedIds();
|
|
foreach($asFeeds as $iFeedId) {
|
|
$oFeed = new Feed($this->oDb, $iFeedId);
|
|
$oFeed->checkUpdateFeed($this->oProject->getMode());
|
|
|
|
$asMessages = $oFeed->getMessages($this->oProject->getActivePeriod());
|
|
foreach($asMessages as $iIndex=>&$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');
|
|
$asMessage['displayed_id'] = 'N°'.($iIndex + 1);
|
|
|
|
$this->addTimeStamp($asMessage, $asMessage['unix_time']);
|
|
}
|
|
}
|
|
usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
|
|
|
return $asMessages;
|
|
}
|
|
|
|
/**
|
|
* Get valid pictures based on $sTimeRefField (both are on site time):
|
|
* - taken_on: Date/time on which the picture was taken
|
|
* - posted_on: Date/time on which the picture was uploaded
|
|
* @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 $iIndex=>$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']));
|
|
$asPic['displayed_id'] = 'N°'.($iIndex + 1);
|
|
|
|
$this->addTimeStamp($asPic, strtotime($sTimeRef));
|
|
$asValidPics[] = $asPic;
|
|
}
|
|
}
|
|
usort($asValidPics, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
|
|
|
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']['site_time'] = $this->oProject->getActivePeriod();
|
|
$asInfo['constOpe']['site_time'] = "BETWEEN";
|
|
}
|
|
$asPosts = $this->oDb->selectRows($asInfo);
|
|
|
|
foreach($asPosts as &$asPost) {
|
|
$iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE)
|
|
$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
|
|
|
|
$this->addTimeStamp($asPost, $iUnixTimeStamp);
|
|
}
|
|
usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
|
|
|
return $asPosts;
|
|
}
|
|
|
|
private function addTimeStamp(&$asData, $iTime) {
|
|
$asData['unix_time'] = (int) $iTime;
|
|
$asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, 'fr');
|
|
$asData['formatted_time'] = date(self::FORMAT_TIME, $iTime);
|
|
}
|
|
|
|
public function getNewsFeed($iChunk=0)
|
|
{
|
|
$bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO);
|
|
$asFeeds = array();
|
|
$asFeedTypes = array(
|
|
'message' => array(
|
|
'table' => Feed::MSG_TABLE,
|
|
'feed' => $this->getSpotMessages(),
|
|
'priority' => 0
|
|
),
|
|
'picture' => array(
|
|
'table' => Picture::PIC_TABLE,
|
|
'feed' => $this->getPictures('posted_on'),
|
|
'priority' => 1
|
|
),
|
|
'post' => array(
|
|
'table' => self::POST_TABLE,
|
|
'feed' => $this->getPosts(),
|
|
'priority' => 2
|
|
)
|
|
);
|
|
foreach($asFeedTypes as $sFeedType=>$asFeedTypeInfo) {
|
|
foreach($asFeedTypeInfo['feed'] as $asFeed) {
|
|
$sPriority = $asFeedTypeInfo['priority'];
|
|
if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority'];
|
|
|
|
$iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])];
|
|
$asFeeds[$iId] = $asFeed;
|
|
$asFeeds[$iId]['type'] = $sFeedType;
|
|
}
|
|
}
|
|
|
|
//Sort by key
|
|
if($bHistoMode) krsort($asFeeds);
|
|
else ksort($asFeeds);
|
|
|
|
//Split chunks
|
|
$asFeeds = array_slice($asFeeds, $iChunk*self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE);
|
|
|
|
return self::getJsonResult(true, '', $asFeeds);
|
|
}
|
|
|
|
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),
|
|
'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE)
|
|
);
|
|
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
|
|
return self::getJsonResult(($iPostId > 0), '');
|
|
}
|
|
|
|
public function upload()
|
|
{
|
|
$this->oClassManagement->incClass('uploader', true);
|
|
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'] || Settings::DEBUG) {
|
|
$asDomains = array();
|
|
$sReferer = 'https://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 'opentopomap':
|
|
$sPattern = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png';
|
|
$asDomains = array('a', 'b', 'c');
|
|
break;
|
|
case 'static':
|
|
$sPattern = 'https://api.mapbox.com/v4/mapbox.satellite/url-https%3A%2F%2Fspot.lutran.fr%2Fimages%2Ffootprint_mapbox.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;
|
|
}
|
|
|
|
public static function getTimeZoneDesc($sTimeZone) {
|
|
$sCity = substr(strrchr($sTimeZone, '/'), 1);
|
|
return 'heure d'.(in_array(strtolower(substr($sCity, 0, 1)), array('a', 'e', 'i', 'o', 'u', 'h'))?'\'':'e ').$sCity;
|
|
}
|
|
}
|
|
|
|
?>
|