Steve White ae9ece5266 *** Initial Commit of Files for APRS.TO Backend Administration Panel ***
This code is non-functional at this point.
2025-02-02 15:53:34 -05:00

309 lines
10 KiB
PHP

<?php
class Kml {
private $_class;
private static $_singletonInstance = null;
public function __construct($class = 'Kml')
{
$this->_class = $class;
}
public static function getInstance()
{
if (self::$_singletonInstance === null) {
self::$_singletonInstance = new Kml();
}
return self::$_singletonInstance;
}
public function getKmlDomDocument($station, $packets, $startTimestamp, $endTimestamp, $color)
{
$dom = new DOMDocument('1.0', 'UTF-8');
$node = $dom->createElementNS('http://earth.google.com/kml/2.2', 'kml');
$parNode = $dom->appendChild($node);
$dnode = $dom->createElement('Document');
$docNode = $parNode->appendChild($dnode);
if (!empty($packets)) {
$lastPacket = $packets[count($packets) - 1];
$makrerStyleNode = $this->getMarkerStyleNode($dom, $lastPacket, $color);
$docNode->appendChild($makrerStyleNode);
$folderNode = $dom->createElement('Folder');
$docNode->appendChild($folderNode);
$nameNode = $dom->createElement('name', $station->name
. ' (' . $startTimestamp . ' - ' . $endTimestamp . ')');
$folderNode->appendChild($nameNode);
$stationNode = $this->getStationNode($dom, $station, $packets);
$folderNode->appendChild($stationNode);
}
return $dom;
}
public function formatKmlContent($kmlContent, $tab="\t", $tabCount = 1)
{
$kmlContent = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $kmlContent);
$token = strtok($kmlContent, "\n");
$result = '';
$pad = 0;
$matches = array();
while ($token !== false)
{
$token = trim($token);
if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) $indent=0;
elseif (preg_match('/^<\/\w/', $token, $matches))
{
$pad--;
if($indent>0) $indent=0;
}
elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches))
{
$indent=1;
}
else $indent = 0;
$line = str_pad($token, strlen($token)+($pad*$tabCount), $tab, STR_PAD_LEFT);
$result .= $line."\n";
$token = strtok("\n");
$pad += $indent;
}
return $result;
}
private function getMarkerStyleNode($dom, $packet, $color)
{
$styleNode = $dom->createElement('Style');
$styleNode->setAttribute('id', 'markerStyle' . $packet->stationId);
$iconstyleNode = $dom->createElement('IconStyle');
$iconNode = $dom->createElement('Icon');
$href = $dom->createElement('href', $this->getWebsiteURL() . $packet->getIconFilePath(null, null, true));
$iconNode->appendChild($href);
$iconstyleNode->appendChild($iconNode);
$styleNode->appendChild($iconstyleNode);
$linestyleNode = $dom->createElement('LineStyle');
if (!is_null($color) && preg_match('/^[A-F0-9]{8}$/', $color)) {
$colorNode = $dom->createElement('color', $color);
$linestyleNode->appendChild($colorNode);
} else {
$colorNode = $dom->createElement('color', 'ff55ff55');
$linestyleNode->appendChild($colorNode);
}
$widthNode = $dom->createElement('width', '5');
$linestyleNode->appendChild($widthNode);
$styleNode->appendChild($linestyleNode);
return $styleNode;
}
private function getStationNode($dom, $station, $packets) {
$lastPacket = $packets[count($packets) - 1];
$placeNode = $dom->createElement('Placemark');
$placeNode->setAttribute('id', 'station-' . $station->getId());
$styleUrl = $dom->createElement('styleUrl', '#markerStyle' . $station->getId());
$placeNode->appendChild($styleUrl);
$nameNode = $dom->createElement('name', htmlspecialchars($station->name));
$placeNode->appendChild($nameNode);
$description = $this->getPacketNodeDescription($station, $packets);
if ($description !== null) {
$node = $dom->createElement('description', ($description));
$placeNode->appendChild($node);
}
$multiGeometryNode = $this->getMultiGeometryNode($dom, $station, $packets);
$placeNode->appendChild($multiGeometryNode);
return $placeNode;
}
private function getMultiGeometryNode($dom, $station, $packets) {
$lastPacket = $packets[count($packets) - 1];
$multiGeometryNode = $dom->createElement('MultiGeometry');
$altitudeMode = 'clampToGround';
if ($this->isFlyingObject($station, $packets)) {
$altitudeMode = 'absolute';
}
$pointNode = $this->getPacketPointNode($dom, $lastPacket, $altitudeMode);
$multiGeometryNode->appendChild($pointNode);
if (count($packets) > 1) {
$tailLineNode = $this->getTailLineNode($dom, $packets, $altitudeMode);
$multiGeometryNode->appendChild($tailLineNode);
}
return $multiGeometryNode;
}
private function getPacketPointNode($dom, $packet, $altitudeMode)
{
$pointNode = $dom->createElement('Point');
// Creates a coordinates element and gives it the value of the lng and lat columns from the results.
if ($packet->altitude !== null) {
$coorStr = round($packet->longitude, 5) . ',' . round($packet->latitude, 5) . ',' . round($packet->altitude, 2);
} else {
$coorStr = round($packet->longitude, 5) . ',' . round($packet->latitude, 5);
}
$altitudeModeNode = $dom->createElement('altitudeMode', $altitudeMode);
$pointNode->appendChild($altitudeModeNode);
$coorNode = $dom->createElement('coordinates', $coorStr);
$pointNode->appendChild($coorNode);
return $pointNode;
}
private function getTailLineNode($dom, $packets, $altitudeMode)
{
$lineStringNode = $dom->createElement('LineString');
$lineCoorStr = '';
foreach ($packets as $packet) {
if ($packet->altitude !== null) {
$lineCoorStr .= ' ' . round($packet->longitude, 5) . ',' . round($packet->latitude, 5) . ',' . round($packet->altitude, 2);
} else {
$lineCoorStr .= ' ' . round($packet->longitude, 5) . ',' . round($packet->latitude, 5);
}
}
$altitudeModeNode = $dom->createElement('altitudeMode', $altitudeMode);
$lineStringNode->appendChild($altitudeModeNode);
$tessellateNode = $dom->createElement('tessellate', '1');
$lineStringNode->appendChild($tessellateNode);
$extrudeNode = $dom->createElement('extrude', '1');
$lineStringNode->appendChild($extrudeNode);
$lineCoorNode = $dom->createElement('coordinates', $lineCoorStr);
$lineStringNode->appendChild($lineCoorNode);
return $lineStringNode;
}
private function getPacketNodeDescription($station, $packets)
{
$description = array();
$description[] = '<a href="' . $this->getWebsiteURL() . '?sid=' . $station->getId() . '" target="_blank">Track ' . $station->name . '</a>';
if (!empty($packets)) {
$firstPacket = $packets[0];
$lastPacket = $packets[count($packets) - 1];
$lastPacketTime = new DateTime();
$lastPacketTime->setTimestamp($lastPacket->timestamp);
$description[] = '';
if (count($packets) == 1) {
$description[] = '<span style="color: grey;">' . $lastPacketTime->format('Y-m-d H:i:s e') . '</span>';
} else {
$firstPacketTime = new DateTime();
$firstPacketTime->setTimestamp($firstPacket->timestamp);
$description[] = '<span style="color: grey;">' . $firstPacketTime->format('Y-m-d H:i:s e') . ' -<br>' . $lastPacketTime->format('Y-m-d H:i:s e') . '</span>';
}
if ($lastPacket->comment != null) {
$description[] = '';
$description[] = '<span style="font-style: italic; color: #754D08;">' . htmlspecialchars($lastPacket->comment) . '</span>';
}
if ($lastPacket->speed !== null) {
$description[] = '';
$description[] = 'Latest speed: <span style="color: blue;">' . $lastPacket->speed . 'kmh' . '</span>';
if ($lastPacket->altitude !== null) {
$description[] = 'Latest altitude: <span style="color: blue;">' . $lastPacket->altitude . 'm ' . '</span>';
}
if ($lastPacket->course !== null) {
$description[] = 'Latest course: <span style="color: blue;">' . $lastPacket->course . '°' . '</span>';
}
}
}
return '<div style="margin-right: 45px;">' . implode('<br/>', $description) . '</div>';
}
private function isClientRequestingToOften()
{
$currentMinuteTs = (time() - (time() % 60));
$limitedNumberOfRequestsPerMinute = 60;
$logFile = 'tmp/export_kml_ip_' . $_SERVER['REMOTE_ADDR'] . '_' . $currentMinuteTs;
// Create dir if missing
if (!file_exists('tmp')) {
mkdir('tmp');
}
// Delete old log-files
$files = glob('tmp/*');
foreach ($files as $file) {
if (is_file($file) && !stristr($file, '_' . $currentMinuteTs)) {
unlink($file);
}
}
// Log request
$log = fopen($logFile, "a");
fwrite($log,'0');
fclose($log);
// Check limit
if (filesize($logFile) > $limitedNumberOfRequestsPerMinute) {
return true;
}
return false;
}
private function isFlyingObject($station, $packets)
{
if (count($packets) <= 1) {
// A station with only one packet is probably stationary
return false;
}
if ($station->sourceId == 4) {
// All gliderradar stations with a tail is probably flying
return true;
}
foreach ($packets as $packet) {
if ($packet->altitude !== null && $packet->altitude > 1000) {
// If altitude is above 1000m it is probably flying
return true;
}
}
return false;
}
private function getWebsiteURL()
{
return sprintf(
"%s://%s",
isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https' : 'http',
$_SERVER['SERVER_NAME']
);
}
}