diff --git a/htdocs/includes/models/model.class.php b/htdocs/includes/models/model.class.php
new file mode 100644
index 0000000..4c3b9de
--- /dev/null
+++ b/htdocs/includes/models/model.class.php
@@ -0,0 +1,116 @@
+_id = $id;
+ }
+
+ /**
+ * Returns id of the object
+ *
+ * @return int
+ */
+ public function getId()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * Returns true if object exists in database
+ *
+ * @return boolean
+ */
+ public function isExistingObject()
+ {
+ if ($this->_id != null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Makes it possible to get $object->field
+ *
+ * @param string $key
+ */
+ public function __get($key)
+ {
+ $key = $this->_camelize($key);
+
+ if (isset($this->_values[$key])) {
+ return $this->_values[$key];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Makes it possible to set $object->field
+ *
+ * @param string $key
+ * @param mixed
+ */
+ public function __set($key, $value)
+ {
+ $this->_values[$this->_camelize($key)] = $value;
+ }
+
+
+ /**
+ * Makes it possible to check if $object->field is set
+ *
+ * @param string $key
+ */
+ public function __isset($key)
+ {
+ if (isset($this->_values[$key])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Makes it possible to unset $object->field
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ if (isset($this->_values[$key])) {
+ unset($this->_values[$key]);
+ }
+ }
+
+ /**
+ * Update object from array, usually a db record
+ * column-names with underscores will be converted to camelcase
+ *
+ * @param array $array
+ */
+ public function updateObjectFromArray($array)
+ {
+ foreach ($array as $column => $value) {
+ $this->_values[$this->_camelize($column)] = $value;
+ }
+ }
+
+ /**
+ * Convert underscore separated variables to camelcaps
+ * (for some reason I prefer underscore in db-columns but I prefer camelCaps in code...)
+ *
+ * @param string $input
+ * @return string
+ */
+ private function _camelize($input)
+ {
+ return lcfirst(str_replace('_', '', ucwords($input, '_')));
+ }
+}
diff --git a/htdocs/includes/models/packet.class.php b/htdocs/includes/models/packet.class.php
new file mode 100644
index 0000000..3d14372
--- /dev/null
+++ b/htdocs/includes/models/packet.class.php
@@ -0,0 +1,375 @@
+symbol) >= 1 && strlen($this->symbolTable) >= 1) {
+ $symbolAsciiValue = ord(substr($this->symbol, 0, 1));
+ $symbolTableAsciiValue = ord(substr($this->symbolTable, 0, 1));
+ } else {
+ // Default values
+ $symbolAsciiValue = 125;
+ $symbolTableAsciiValue = 47;
+ }
+
+ $scaleStrValue = '';
+ if ($scaleWidth !== null && $scaleHeight !== null) {
+ $scaleStrValue = '-scale' . $scaleWidth . 'x' . $scaleHeight;
+ }
+
+ return '/symbols/symbol-' . $symbolAsciiValue . '-' . $symbolTableAsciiValue . $scaleStrValue . '.png';
+ }
+
+ /**
+ * Get PGH range in meters
+ *
+ * @return float
+ */
+ public function getPHGRange()
+ {
+ if ($this->getPhg() != null) {
+ $p = $this->getPhgPower();
+ $h = $this->getPhgHaat(false);
+ $g = $this->getPhgGain();
+
+ $gain = pow(10, ($g/10)); //converts from DB to decimal
+ $range = sqrt(2 * $h * sqrt(($p / 10) * ($gain / 2)));
+ return $range / 0.000621371192; // convert to m and return
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH description
+ *
+ * @return String
+ */
+ public function getPHGDescription()
+ {
+ if ($this->getPhg() != null) {
+ $power = $this->getPhgPower();
+ $haat = $this->getPhgHaat();
+ $gain = $this->getPhgGain();
+ $direction = $this->getPhgDirection();
+ $range = $this->getPHGRange();
+
+ $description = '';
+ if ($power !== null) {
+ $description .= 'Power ' . $power . ' W';
+ }
+
+ if ($haat !== null) {
+
+ if (strlen($description) > 0) {
+ $description .= ', ';
+ }
+
+ if (isImperialUnitUser()) {
+ $description .= 'Height ' . round(convertMeterToFeet($haat), 0) . ' ft';
+ } else {
+ $description .= 'Height ' . $haat . ' m';
+ }
+ }
+
+ if ($gain !== null && $direction !== null) {
+ if (strlen($description) > 0) {
+ $description .= ', ';
+ }
+ $description .= 'Gain ' . $gain . ' dB ' . $direction;
+ }
+
+ return $description;
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH string
+ *
+ * @return string
+ */
+ public function getPhg()
+ {
+ if ($this->phg != null) {
+ if ($this->phg == 0) {
+ return null; // 0000 is considered not to be used (power == 0!)
+ } else if ($this->phg < 10) {
+ return '000' + strval($this->phg);
+ } else if ($this->phg < 100) {
+ return '00' + strval($this->phg);
+ } else if ($this->phg < 1000) {
+ return '0' + strval($this->phg);
+ } else {
+ return strval($this->phg);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH power
+ *
+ * @return int
+ */
+ public function getPhgPower()
+ {
+ if ($this->getPhg() != null) {
+ return pow(intval(substr($this->getPhg(), 0, 1)), 2);
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH hight (above averange terrain)
+ *
+ * @param boolean $inMeters
+ * @return int
+ */
+ public function getPhgHaat($inMeters = true)
+ {
+ if ($this->getPhg() != null) {
+ $value = intval(substr($this->getPhg(), 1, 1));
+
+ $haat = 0;
+ if ($value != 0) {
+ $haat = 10 * pow(2, $value);
+ }
+
+ if ($inMeters) {
+ return intval(round($haat * 0.3048));
+ } else {
+ return $haat;
+ }
+
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH Gain
+ *
+ * @return int
+ */
+ public function getPhgGain()
+ {
+ if ($this->getPhg() != null) {
+ return intval(substr($this->getPhg(), 2, 1));
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH Direction
+ *
+ * @return String
+ */
+ public function getPhgDirection()
+ {
+ if ($this->getPhg() != null) {
+
+ switch (substr($this->getPhg(), 3, 1)) {
+ case 0:
+ return 'omni';
+ break;
+ case 1:
+ return 'North East';
+ break;
+ case 2:
+ return 'East';
+ break;
+ case 3:
+ return 'South East';
+ break;
+ case 4:
+ return 'South';
+ break;
+ case 5:
+ return 'South West';
+ break;
+ case 6:
+ return 'West';
+ break;
+ case 7:
+ return 'North West';
+ break;
+ case 8:
+ return 'North';
+ break;
+ case 9:
+ return null;
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get PGH Direction Degree
+ *
+ * @return int
+ */
+ public function getPhgDirectionDegree()
+ {
+ if ($this->getPhg() != null) {
+
+ switch (substr($this->getPhg(), 3, 1)) {
+ case 0:
+ return null;
+ break;
+ case 1:
+ return 45;
+ break;
+ case 2:
+ return 90;
+ break;
+ case 3:
+ return 135;
+ break;
+ case 4:
+ return 180;
+ break;
+ case 5:
+ return 225;
+ break;
+ case 6:
+ return 270;
+ break;
+ case 7:
+ return 315;
+ break;
+ case 8:
+ return 360;
+ break;
+ case 9:
+ return null;
+ break;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get RNG
+ *
+ * @return float
+ */
+ public function getRng()
+ {
+ if ($this->rng != null) {
+ return $this->rng;
+ }
+ return null;
+ }
+
+ /**
+ * Get packet type description
+ *
+ * @return Station
+ */
+ public function getPacketTypeName()
+ {
+ switch ($this->packetTypeId) {
+ case 1:
+ return 'Position';
+ break;
+ case 2:
+ return 'Direction';
+ break;
+ case 3:
+ return 'Weather';
+ break;
+ case 4:
+ return 'Object';
+ break;
+ case 5:
+ return 'Item';
+ break;
+ case 6:
+ return 'Telemetry';
+ break;
+ case 7:
+ return 'Message';
+ break;
+ case 8:
+ return 'Query';
+ break;
+ case 9:
+ return 'Response';
+ break;
+ case 10:
+ return 'Status';
+ break;
+ case 11:
+ return 'Other';
+ break;
+ default:
+ return 'Unknown';
+ break;
+ }
+ }
+
+ /**
+ * Get releted packet weather
+ * @return PacketWeather
+ */
+ public function getPacketWeather() {
+ return PacketWeatherRepository::getInstance()->getObjectByPacketId($this->id, $this->timestamp);
+ }
+
+ /**
+ * Get releted packet telemetry
+ * @return PacketTelemetry
+ */
+ public function getPacketTelemetry() {
+ return PacketTelemetryRepository::getInstance()->getObjectByPacketId($this->id, $this->timestamp);
+ }
+
+ /**
+ * Returns OGN part of packet
+ *
+ * @return PacketOgn
+ */
+ public function getPacketOgn()
+ {
+ static $cache = array();
+ $key = $this->id;
+ if (!isset($cache[$key])) {
+ if ($this->sourceId == 5) {
+ $cache[$key] = PacketOgnRepository::getInstance()->getObjectByPacketId($this->id);
+ } else {
+ $cache[$key] = new PacketOgn(null);
+ }
+ }
+ return $cache[$key];
+ }
+
+ /**
+ * Get Station
+ * @return Station
+ */
+ public function getStationObject() {
+ return StationRepository::getInstance()->getObjectById($this->stationId);
+ }
+
+ /**
+ * Get Sender
+ * @return Sender
+ */
+ public function getSenderObject() {
+ return SenderRepository::getInstance()->getObjectById($this->senderId);
+ }
+}
diff --git a/htdocs/includes/models/packetogn.class.php b/htdocs/includes/models/packetogn.class.php
new file mode 100644
index 0000000..5150d13
--- /dev/null
+++ b/htdocs/includes/models/packetogn.class.php
@@ -0,0 +1,10 @@
+_stationTelemetryParam = null;
+ $this->_stationTelemetryEqns = null;
+ $this->_stationTelemetryBits = null;
+ $this->_stationTelemetryUnit = null;
+ }
+
+ /**
+ * Get value parameter name
+ *
+ * @param int $valNumber
+ * @return string
+ */
+ public function getValueParameterName($valNumber)
+ {
+ if ($this->_stationTelemetryParam === null && $this->stationTelemetryParamId !== null) {
+ $this->_stationTelemetryParam = StationTelemetryParamRepository::getInstance()->getObjectById($this->stationTelemetryParamId);
+ }
+
+ if ($this->_stationTelemetryParam !== null
+ && $this->_stationTelemetryParam->isExistingObject()
+ && isset($this->_stationTelemetryParam->{'p'.$valNumber})
+ && $this->_stationTelemetryParam->{'p'.$valNumber} != ''
+ ) {
+
+ return $this->_stationTelemetryParam->{'p'.$valNumber};
+ } else {
+ return "Value$valNumber";
+ }
+ }
+
+ /**
+ * Get bit parameter name
+ *
+ * @param int $valNumber
+ * @return string
+ */
+ public function getBitParameterName($valNumber)
+ {
+ if ($this->_stationTelemetryParam === null && $this->stationTelemetryParamId !== null) {
+ $this->_stationTelemetryParam = StationTelemetryParamRepository::getInstance()->getObjectById($this->stationTelemetryParamId);
+ }
+
+ if ($this->_stationTelemetryParam !== null && $this->_stationTelemetryParam->isExistingObject() && $this->_stationTelemetryParam->{'b'.$valNumber} != '') {
+ return $this->_stationTelemetryParam->{'b'.$valNumber};
+ } else {
+ return "Bit$valNumber";
+ }
+ }
+
+ /**
+ * Get value eqns
+ *
+ * @param int $valNumber
+ * @return numeric
+ */
+ public function getEqnsValue($valNumber)
+ {
+ if ($this->_stationTelemetryEqns === null && $this->stationTelemetryEqnsId !== null) {
+ $this->_stationTelemetryEqns = StationTelemetryEqnsRepository::getInstance()->getObjectById($this->stationTelemetryEqnsId);
+ }
+
+ if ($this->_stationTelemetryEqns !== null && $this->_stationTelemetryEqns->isExistingObject()) {
+ $a = $this->_stationTelemetryEqns->{'a'.$valNumber};
+ $b = $this->_stationTelemetryEqns->{'b'.$valNumber};
+ $c = $this->_stationTelemetryEqns->{'c'.$valNumber};
+
+ if ($a === null && $b === null) {
+ // User has sent us a faulty eqns, just return raw value
+ return Array(0, 1, 0);
+ } else {
+ if ($a === null) {
+ $a = 0;
+ }
+ if ($b === null) {
+ $b = 0;
+ }
+ if ($c === null) {
+ $c = 0;
+ }
+ return Array($a, $b, $c);
+ }
+
+ } else {
+ // We have no eqns, just return raw value
+ return Array(0, 1, 0);
+ }
+ }
+
+ /**
+ * Check if value is set value
+ *
+ * @param int $valNumber
+ * @return bool
+ */
+ public function isValueSet($valNumber)
+ {
+ $val = $this->{'val'.$valNumber};
+ if ($val !== null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get value
+ *
+ * @param int $valNumber
+ * @return numeric
+ */
+ public function getValue($valNumber)
+ {
+ $val = $this->{'val'.$valNumber};
+
+ $eqns = $this->getEqnsValue($valNumber);
+ $a = $eqns[0];
+ $b = $eqns[1];
+ $c = $eqns[2];
+
+ if ($a == null && $b == null) {
+ // User has sent us a faulty eqns, just return raw value
+ return $val;
+
+ } else {
+ $result = 0;
+
+ if ($a != null) {
+ $result += $a * $val * $val;
+ }
+
+ if ($b != null) {
+ $result += $b * $val;
+ }
+
+ if ($c != null) {
+ $result += $c;
+ }
+
+ return $result;
+ }
+ }
+
+ /**
+ * Get value unit
+ *
+ * @param int $valNumber
+ * @return string
+ */
+ public function getValueUnit($valNumber)
+ {
+ $unit = ''; // default
+ if ($this->_stationTelemetryUnit === null && $this->stationTelemetryUnitId !== null) {
+ $this->_stationTelemetryUnit = StationTelemetryUnitRepository::getInstance()->getObjectById($this->stationTelemetryUnitId);
+ }
+
+ if ($this->_stationTelemetryUnit !== null && $this->_stationTelemetryUnit->isExistingObject()) {
+ $unit = ' ' . $this->_stationTelemetryUnit->{'u'.$valNumber};
+ }
+
+ return $unit;
+ }
+
+ /**
+ * Get bit sense
+ *
+ * @param int $valNumber
+ * @return int
+ */
+ public function getBitSense($valNumber)
+ {
+ if ($this->_stationTelemetryBits === null && $this->stationTelemetryBitsId !== null) {
+ $this->_stationTelemetryBits = StationTelemetryBitsRepository::getInstance()->getObjectById($this->stationTelemetryBitsId);
+ }
+
+ $sense = 1; // default to 1
+ if ($this->_stationTelemetryBits !== null && $this->_stationTelemetryBits->isExistingObject()) {
+ $sense = substr($this->_stationTelemetryBits->bits, $valNumber-1, 1);
+ }
+
+ return $sense;
+ }
+
+ /**
+ * Get bit
+ *
+ * @param int $valNumber
+ * @return int
+ */
+ public function getBit($valNumber)
+ {
+ if ($this->_stationTelemetryBits === null && $this->stationTelemetryBitsId !== null) {
+ $this->_stationTelemetryBits = StationTelemetryBitsRepository::getInstance()->getObjectById($this->stationTelemetryBitsId);
+ }
+
+ $sense = $this->getBitSense($valNumber);
+
+ $bit = substr($this->bits, $valNumber-1, 1);
+ if (($sense == 1 && $bit == 1) || ($sense == 0 && $bit == 0)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Get bit label
+ *
+ * @param int $valNumber
+ * @return int
+ */
+ public function getBitLabel($valNumber)
+ {
+ if ($this->_stationTelemetryUnit === null && $this->stationTelemetryUnitId !== null) {
+ $this->_stationTelemetryUnit = StationTelemetryUnitRepository::getInstance()->getObjectById($this->stationTelemetryUnitId);
+ }
+
+ $label = 'On'; // default
+ if ($this->_stationTelemetryUnit !== null && $this->_stationTelemetryUnit->isExistingObject()) {
+ $label = $this->_stationTelemetryUnit->{'l'.$valNumber};
+ }
+
+ return $label;
+ }
+}
diff --git a/htdocs/includes/models/packetweather.class.php b/htdocs/includes/models/packetweather.class.php
new file mode 100644
index 0000000..134b1be
--- /dev/null
+++ b/htdocs/includes/models/packetweather.class.php
@@ -0,0 +1,72 @@
+getRainSummaryList($showRain1h, $showRain24h, $showRainSinceMidnight);
+
+ if (empty($result)) {
+ if ($this->rain_1h === null && $this->rain_24h === null && $this->rain_since_midnight === null) {
+ return 'No rain measurements received';
+ } else {
+ return 'No other rain measurements received';
+ }
+ } else {
+ return implode('
', $result);
+ }
+ }
+
+ /**
+ * Returnes a rain summary array
+ *
+ * @param boolean $showRain1h
+ * @param boolean $showRain24h
+ * @param boolean $showRainSinceMidnight
+ * @return string
+ */
+ public function getRainSummaryList($showRain1h = true, $showRain24h = true, $showRainSinceMidnight = true)
+ {
+ $result = [];
+
+ if ($showRain1h && $this->rain_1h !== null) {
+ if (isImperialUnitUser()) {
+ $result[] = "Rain latest hour: " . round(convertMmToInch($this->rain_1h), 2) . " in";
+ } else {
+ $result[] = "Rain latest hour: " . round($this->rain_1h, 2) . " mm";
+ }
+ }
+
+ if ($showRain24h && $this->rain_24h !== null) {
+ if (isImperialUnitUser()) {
+ $result[] = "Rain latest 24h hours: " . round(convertMmToInch($this->rain_24h), 2) . " in";
+ } else {
+ $result[] = "Rain latest 24h hours: " . round($this->rain_24h, 2) . " mm";
+ }
+ }
+
+ if ($showRainSinceMidnight && $this->rain_since_midnight !== null) {
+ if (isImperialUnitUser()) {
+ $result[] = "Rain since midnight: " . round(convertMmToInch($this->rain_since_midnight), 2) . " in";
+ } else {
+ $result[] = "Rain since midnight: " . round($this->rain_since_midnight, 2) . " mm";
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/htdocs/includes/models/sender.class.php b/htdocs/includes/models/sender.class.php
new file mode 100644
index 0000000..4f61f6a
--- /dev/null
+++ b/htdocs/includes/models/sender.class.php
@@ -0,0 +1,19 @@
+getObjectByNameAndSenderId($this->name, $this->id);
+ }
+}
diff --git a/htdocs/includes/models/station.class.php b/htdocs/includes/models/station.class.php
new file mode 100644
index 0000000..b3a91cb
--- /dev/null
+++ b/htdocs/includes/models/station.class.php
@@ -0,0 +1,322 @@
+sourceId == 1 && $this->stationTypeId == 1) {
+ $pos = strpos($this->name, '-');
+ if ($pos !== false) {
+ $callsign = substr($this->name, 0, $pos);
+ } else {
+ $callsign = $this->name;
+ }
+
+ if (strlen($callsign) >= 3 && preg_match('~[0-9]~', substr($callsign, 1, strlen($callsign)-1))) {
+ // At least 3 letters and a digit somewhere in the middle
+ return $callsign;
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns OGN sender address
+ *
+ * @return string
+ */
+ public function getOgnSenderAddress()
+ {
+ if (isset($this->latestOgnSenderAddress) && $this->latestOgnSenderAddress != '') {
+ return $this->latestOgnSenderAddress;
+ }
+ return null;
+ }
+
+ /**
+ * Returns OGN device
+ *
+ * @return Model
+ */
+ public function getOgnDevice()
+ {
+ static $cache = array();
+ $key = $this->id;
+ if (!isset($cache[$key])) {
+ if (isset($this->latestOgnSenderAddress) && $this->latestOgnSenderAddress != '') {
+ $cache[$key] = ModelRepository::getInstance()->getObjectFromSql('select * from ogn_device where device_id = ?', [$this->latestOgnSenderAddress]);
+ } else {
+ $cache[$key] = null;
+ }
+ }
+ return $cache[$key];
+ }
+
+ /**
+ * Returns OGN device DB aircraft type name
+ *
+ * @return string
+ */
+ public function getOgnDdbAircraftTypeName()
+ {
+ $ognDevice = $this->getOgnDevice();
+ if ($ognDevice) {
+ switch ($ognDevice->ddbAircraftType) {
+ case 1:
+ return 'Glider/Motoglider';
+ case 2:
+ return 'Plane';
+ case 3:
+ return 'Ultralight';
+ case 4:
+ return 'Helicopter';
+ case 5:
+ return 'Drone/UAV';
+ case 6:
+ return 'Other';
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns OGN aircraft type name
+ *
+ * @return string
+ */
+ public function getOgnAircraftTypeName()
+ {
+ if (isset($this->latestOgnAircraftTypeId) && $this->latestOgnAircraftTypeId != '') {
+ switch ($this->latestOgnAircraftTypeId) {
+ case 1:
+ return 'Glider';
+ case 2:
+ return 'Tow Plane';
+ case 3:
+ return 'Helicopter';
+ case 4:
+ return 'Parachute';
+ case 5:
+ return 'Drop Plane';
+ case 6:
+ return 'Hang Glider';
+ case 7:
+ return 'Para Glider';
+ case 8:
+ return 'Powered Aircraft';
+ case 9:
+ return 'Jet Aircraft';
+ case 10:
+ return 'UFO';
+ case 11:
+ return 'Balloon';
+ case 12:
+ return 'Airship';
+ case 13:
+ return 'UAV';
+ case 14:
+ return '';
+ case 15:
+ return 'Static Object';
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns source description
+ *
+ * @return string
+ */
+ public function getSourceDescription()
+ {
+ if (isset($this->sourceId) && $this->sourceId != '') {
+ if ($this->sourceId == 1) {
+ return 'APRS-IS';
+ } elseif ($this->sourceId == 2) {
+ return 'CWOP (Citizen Weather Observer Program)';
+ } elseif ($this->sourceId == 5) {
+ return 'OGN (Open Glider Network)';
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get distance between specified lat/lng and the station latest position (confirmed position if exists)
+ *
+ * @param float $lat
+ * @param float $lng
+ * @return int;
+ */
+ public function getDistance($lat, $lng)
+ {
+ static $cache = array();
+ $key = $this->id . ':' . $lat . ':' . $lng;
+ if (!isset($cache[$key])) {
+
+ $latestLatitude = null;
+ $latestLongitude = null;
+ if ($this->latestConfirmedLongitude !== null && $this->latestConfirmedLatitude !== null) {
+ $latestLatitude = $this->latestConfirmedLatitude;
+ $latestLongitude = $this->latestConfirmedLongitude;
+ } else if ($this->latestLongitude !== null && $this->latestLatitude !== null) {
+ $latestLatitude = $this->latestLatitude;
+ $latestLongitude = $this->latestLongitude;
+ }
+
+ if ($lat !== null && $lng !== null && $latestLatitude !== null && $latestLongitude !== null) {
+ $theta = $lng - $latestLongitude;
+ $dist = sin(deg2rad($lat)) * sin(deg2rad($latestLatitude)) + cos(deg2rad($lat)) * cos(deg2rad($latestLatitude)) * cos(deg2rad($theta));
+ $dist = acos($dist);
+ $dist = rad2deg($dist);
+ $miles = $dist * 60 * 1.1515;
+ $cache[$key] = round($miles * 1609.344, 0);
+ } else {
+ $cache[$key] = null;
+ }
+ }
+ return $cache[$key];
+ }
+
+ /**
+ * Returnes icon http path
+ *
+ * @param int $scaleWidth
+ * @param int $scaleHeight
+ * @return string
+ */
+ public function getIconFilePath($scaleWidth = null, $scaleHeight = null)
+ {
+ if (strlen($this->latestConfirmedSymbol) >= 1 && strlen($this->latestConfirmedSymbolTable) >= 1) {
+ $symbolAsciiValue = ord(substr($this->latestConfirmedSymbol, 0, 1));
+ $symbolTableAsciiValue = ord(substr($this->latestConfirmedSymbolTable, 0, 1));
+ } else {
+ // Default values
+ $symbolAsciiValue = 125;
+ $symbolTableAsciiValue = 47;
+ }
+
+ $scaleStrValue = '';
+ if ($scaleWidth !== null && $scaleHeight !== null) {
+ $scaleStrValue = '-scale' . $scaleWidth . 'x' . $scaleHeight;
+ }
+
+ return '/symbols/symbol-' . $symbolAsciiValue . '-' . $symbolTableAsciiValue . $scaleStrValue . '.png';
+ }
+
+ /**
+ * Get array of the latest used symbols for this station
+ *
+ * @param int $scaleWidth
+ * @param int $scaleHeight
+ * @return array
+ */
+ public function getLatestIconFilePaths($scaleWidth = 24, $scaleHeight = 24)
+ {
+ $result = Array();
+ if ($this->latestConfirmedPacketTimestamp > (time() - (60*60*24))) {
+ // Latest packet is not that old, go on
+
+ $scaleStrValue = '';
+ if ($scaleWidth !== null && $scaleHeight !== null) {
+ $scaleStrValue = '-scale' . $scaleWidth . 'x' . $scaleHeight;
+ }
+
+ $sql = 'select symbol, symbol_table from packet where station_id = ? and timestamp > ? group by symbol, symbol_table';
+ $stmt = PDOConnection::getInstance()->prepareAndExec($sql, [$this->id, $this->latestConfirmedPacketTimestamp - (60*60*24)]);
+
+ $records = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($records as $record) {
+
+ if (strlen($record['symbol']) >= 1 && strlen($record['symbol_table']) >= 1) {
+ $key = $record['symbol'] . ':' . $record['symbol_table'];
+
+ $symbolAsciiValue = ord(substr($record['symbol'], 0, 1));
+ $symbolTableAsciiValue = ord(substr($record['symbol_table'], 0, 1));
+ $result[$key] = '/symbols/symbol-' . $symbolAsciiValue . '-' . $symbolTableAsciiValue . $scaleStrValue . '.png';
+ }
+ }
+ }
+
+ return array_values($result);
+ }
+
+ /**
+ * Get packet frequency in number of seconds for the latest 10 packets
+ *
+ * @param string $date
+ * @param int &$numberOfPackets
+ * @return int
+ */
+ public function getPacketFrequency($date = null, &$numberOfPackets)
+ {
+ $pdo = PDOConnection::getInstance();
+ if ($this->latestPacketTimestamp !== null) {
+ $timestamp = $this->latestPacketTimestamp;
+ } else {
+ return null;
+ }
+
+ // Find start timestamp
+ $sql = 'select timestamp ts from packet where station_id = ? and map_id in (1,2,5,7,9) and timestamp < ? order by timestamp desc limit 1';
+ $stmt = $pdo->prepareAndExec($sql, [$this->id, $timestamp - 1]);
+
+ $record = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!empty($record) && $record['ts'] != null && $record['ts'] < $timestamp) {
+ $startTimestamp = $timestamp - (($timestamp - $record['ts'])*10);
+ if ($timestamp - $startTimestamp < 600) {
+ $startTimestamp = $timestamp - 600;
+ }
+ } else {
+ if ($date === null) {
+ // Try to find frequency for the date before
+ $date = strftime('%Y-%m-%d', $timestamp - 86400);
+ return $this->getPacketFrequency($date, $numberOfPackets);
+ } else {
+ // Give up
+ return null;
+ }
+ }
+
+ $sql = 'select (max(timestamp) - min(timestamp)) / count(*) freq, count(*) c from packet where station_id = ? and map_id in (1,2,5,7,9) and timestamp >= ?';
+ $stmt = $pdo->prepareAndExec($sql, [$this->id, $startTimestamp]);
+ $record = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!empty($record) && $record['freq'] > 0) {
+ $numberOfPackets = $record['c'];
+ return $record['freq'];
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * Returnes symbol description
+ * @param boolean $includeUndefinedOverlay
+ * @return string
+ */
+ public function getLatestSymbolDescription($includeUndefinedOverlay = true)
+ {
+ $symbol = null;
+ $symbolTable = null;
+
+ if (strlen($this->latestConfirmedSymbol) >= 1 && strlen($this->latestConfirmedSymbolTable) >= 1) {
+ $symbol = $this->latestConfirmedSymbol;
+ $symbolTable = $this->latestConfirmedSymbolTable;
+ }
+
+ return getSymbolDescription($symbolTable, $symbol, $includeUndefinedOverlay);
+ }
+}
diff --git a/htdocs/includes/models/stationtelemetrybits.class.php b/htdocs/includes/models/stationtelemetrybits.class.php
new file mode 100644
index 0000000..68078b7
--- /dev/null
+++ b/htdocs/includes/models/stationtelemetrybits.class.php
@@ -0,0 +1,9 @@
+