'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( 'host_url' => Settings::HOST_SERVER, '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 = Settings::HOST_SERVER; 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-'.urlencode(Settings::HOST_SERVER).'%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; } } ?>