diff --git a/README.md b/README.md index 57cc910..fb1d9d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # APRS Track Direct -APRS Track Direct is a collection of tools that can be used to run an APRS website. You can use data from APRS-IS, CWOP-IS, OGN, HABHUB, CBAPRS or any other source that uses the APRS specification. +APRS Track Direct is a collection of tools that can be used to run an APRS website. You can use data from APRS-IS, CWOP-IS, OGN or any other source that uses the APRS specification. Tools included are an APRS data collector, a websocket server, a javascript library (websocket client and more), a heatmap generator and a website example (which can of course be used as is). @@ -61,7 +61,7 @@ sudo python2 setup.py install ``` ### Set up aprsc -You should not to connect to a public APRS server (APRS-IS, CWOP-IS or OGN server). The collector will use a full feed connection and each websocket client will use a filtered feed connection. To not cause extra load on public servers it is better to run your own aprsc server and let your collector and all websocket connections connect to that instead (will result in only one full feed connection to a public APRS server). +You should not to connect your collector and websocket server directly to a public APRS server (APRS-IS, CWOP-IS or OGN server). The collector will use a full feed connection and each websocket client will use a filtered feed connection (through the websocket server). To not cause extra load on public servers it is better to run your own aprsc server and let your collector and all websocket connections connect to that instead (will result in only one full feed connection to a public APRS server). Note that it seems like aprsc needs to run on a server with a public ip, otherwise uplink won't work. @@ -114,7 +114,7 @@ ALTER ROLE {USER} WITH SUPERUSER; GRANT ALL PRIVILEGES ON DATABASE "trackdirect" to {USER}; ``` -Remember to add password to password-file: +Might be good to add password to password-file: ``` vi ~/.pgpass ``` diff --git a/config/trackdirect.ini b/config/trackdirect.ini index 62d2cfe..6a2d71f 100644 --- a/config/trackdirect.ini +++ b/config/trackdirect.ini @@ -12,11 +12,15 @@ password="foobar" port="5432" ;; Settings for the remover script -days_to_save_position_data="31" -days_to_save_station_data="90" +days_to_save_position_data="10" +days_to_save_station_data="30" days_to_save_weather_data="10" days_to_save_telemetry_data="10" +;; If this setting is enabled, OGN stations that we are not allowed to reveal the identity of will be given a random name similar to "UNKNOWN123" +;; If disabled we will drop all packets regarding stations that we should not reveal the identity of. +save_ogn_stations_with_missing_identity="0" + [websocket_server] @@ -29,10 +33,10 @@ port="9000" ;; Websocket server log output error_log="~/trackdirect/server/log/wsserver_aprs.log" -;; Frequency limit -;; Packets received more frequently than the configured frequency limit will be dropped -;; Frequency limit is specified in seconds, and 0 means that the limit is disabled. -;; This setting is very useful when frequency is very high (when receiving data from the OGN network this needs to be at least 15s) +;; Packets received more frequently than the configured frequency limit will be dropped (limit is specified in seconds) +;; This frequency limit is only refering to pakets that is received in real time from the filtered feed used by the websocket server +;; This frequency limit may be a bit more forgiving than the frequence limit on the collector. +;; When receiving data from the OGN network this needs to be about 15s or more. frequency_limit="0" ;; First APRS IS server for the websocket server to connect to. @@ -40,6 +44,7 @@ frequency_limit="0" aprs_host1="127.0.0.1" aprs_port1="14580" +;; Important that you set the correct source, otherwise it might be handled incorrect ;; - Source Id 1: APRS-IS ;; - Source Id 2: CWOP ;; - Source Id 3: CBAPRS @@ -54,18 +59,18 @@ aprs_source_id1="1" ;aprs_source_id2="2" ;; Allow time travel -;; Use this settings to disable data requests with a time interval -;; Useful when it is not allowed to show data older than 24h (like when data comes from the OGN network) -;; Note that you need to configure the remover script to delete data after 24h as well (if the source require you to do so) +;; Use this settings to disable/enable data requests with a time interval (this must be disabled for the OGN network) allow_time_travel="1" -;; Max default time in seconds (how old packets that will be included in the response) +;; Max default time in minutes (how old packets that will be included in the response) +;; This setting should be no more than 1440 for for the OGN network. max_default_time="1440" -;; Max time in seconds when filtering (how old packets that will be included in the response) -max_filter_time="14400" +;; Max time in minutes when filtering (how old packets that will be included in the response) +;; This setting should be no more than 1440 for for the OGN network. +max_filter_time="1440" -;; Time in seconds until idle client is disconnected +;; Time in minutes until idle client is disconnected max_client_idle_time="60" ;; Max age in seconds for real time packets waiting to be sent to client (dropping packets if limit is excceded) @@ -78,6 +83,7 @@ host="aprsc" port_full="10152" port_filtered="14580" +;; Important that you set the correct source, otherwise it might be handled incorrect ;; - Source Id 1: APRS-IS ;; - Source Id 2: CWOP ;; - Source Id 3: CBAPRS @@ -92,10 +98,8 @@ passcode="-1" ;; Database inserts is done in batches numbers_in_batch="50" -;; Frequency limit -;; Packets received more frequently than the configured frequency limit will not be shown on map. -;; Frequency limit is specified per station in seconds, and 0 means that the limit is disabled. -;; This setting is very useful when frequency is very high (when receiving data from the OGN network this needs to be at least 20s). +;; Packets received more frequently than the configured frequency limit will not be shown on map (limit is specified in seconds) +;; When receiving data from the OGN network this needs to be 20s or more. ;; If setting save_fast_packets to "0", packets that is received to frequently will not be save (useful for OGN, but not for APRS-IS). frequency_limit="5" save_fast_packets="1" diff --git a/htdocs/includes/common.php b/htdocs/includes/common.php index b8627bb..8f04af3 100644 --- a/htdocs/includes/common.php +++ b/htdocs/includes/common.php @@ -1744,4 +1744,35 @@ function getSymbolDescription($symbolTable, $symbol, $includeUndefinedOverlay) } } } +} + + +/** + * Returnes true if the time travel feature works + * + * @return boolean + */ +function isTimeTravelAllowed() { + $isTimeTravelAllowed = false; + $config = parse_ini_file(ROOT . '/../config/trackdirect.ini', true); + + if (isset($config['websocket_server'])) { + if (isset($config['websocket_server']['allow_time_travel'])) { + if ($config['websocket_server']['allow_time_travel'] == '1') { + $isTimeTravelAllowed = true; + } + } + + if (isset($config['websocket_server']['aprs_source_id1']) && $config['websocket_server']['aprs_source_id1'] == 5) { + // Data source is OGN, disable time travel (server will block it anyway) + $isTimeTravelAllowed = false; + } + + if (isset($config['websocket_server']['aprs_source_id2']) && $config['websocket_server']['aprs_source_id2'] == 5) { + // Data source is OGN, disable time travel (server will block it anyway) + $isTimeTravelAllowed = false; + } + } + + return $isTimeTravelAllowed; } \ No newline at end of file diff --git a/htdocs/includes/repositories/packetpathrepository.class.php b/htdocs/includes/repositories/packetpathrepository.class.php index 1cb5d02..cadeae5 100644 --- a/htdocs/includes/repositories/packetpathrepository.class.php +++ b/htdocs/includes/repositories/packetpathrepository.class.php @@ -95,4 +95,36 @@ class PacketPathRepository extends ModelRepository $stmt = $pdo->prepareAndExec($sql, $args); return $stmt->fetchAll(PDO::FETCH_ASSOC); } + + /** + * Get latest data list by receiving station id + * + * @param int $stationId + * @param int $hours + * @param int $limit + * @return array + */ + public function getLatestDataListByReceivingStationId($stationId, $hours, $limit) + { + if (!isInt($stationId) || !isInt($hours)) { + return []; + } + $minTimestamp = time() - (60*60*$hours); + + $sql = 'select pp.* + from packet_path pp + where pp.station_id = ? + and pp.timestamp >= ? + and pp.number = 0 + and pp.sending_latitude is not null + and pp.sending_longitude is not null + order by pp.timestamp + limit ?'; + + $arg = [$stationId, $minTimestamp, $limit]; + + $pdo = PDOConnection::getInstance(); + $stmt = $pdo->prepareAndExec($sql, $arg); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } } diff --git a/htdocs/includes/repositories/packetrepository.class.php b/htdocs/includes/repositories/packetrepository.class.php index a91511e..d61909d 100644 --- a/htdocs/includes/repositories/packetrepository.class.php +++ b/htdocs/includes/repositories/packetrepository.class.php @@ -148,15 +148,14 @@ class PacketRepository extends ModelRepository * Get latest packet data list by station id (useful for creating a chart) * * @param int $stationId - * @param int $startTimestamp - * @param int $endTimestamp + * @param int $numberOfHours * @param array $columns * @return Array */ - public function getLatestDataListByStationId($stationId, $startTimestamp, $endTimestamp, $columns) + public function getLatestDataListByStationId($stationId, $numberOfHours, $columns) { $result = Array(); - if (!isInt($stationId) || !isInt($startTimestamp) || !isInt($endTimestamp)) { + if (!isInt($stationId) || !isInt($numberOfHours)) { return $result; } @@ -165,27 +164,13 @@ class PacketRepository extends ModelRepository $columns[] = 'timestamp'; } - if ($endTimestamp < time()-60) { - // Fetching old values - $sql = 'select ' . implode(',', $columns) . ', position_timestamp from packet - where station_id = ? - and timestamp >= ? - and (timestamp <= ? or (position_timestamp is not null and position_timestamp <= ?)) - and (speed is not null or altitude is not null) - and map_id in (1,12,5,7,9) - order by timestamp'; - $arg = [$stationId, $startTimestamp, $endTimestamp, $endTimestamp]; - } else { - // Fetching recent values - $sql = 'select ' . implode(',', $columns) . ', position_timestamp from packet - where station_id = ? - and timestamp >= ? - and timestamp <= ? - and (speed is not null or altitude is not null) - and map_id in (1,12,5,7,9) - order by timestamp'; - $arg = [$stationId, $startTimestamp, $endTimestamp]; - } + $sql = 'select ' . implode(',', $columns) . ', position_timestamp from packet + where station_id = ? + and timestamp >= ? + and (speed is not null or altitude is not null) + and map_id in (1,12,5,7,9) + order by timestamp'; + $arg = [$stationId, time() - $numberOfHours*60*60]; $pdo = PDOConnection::getInstance(); $stmt = $pdo->prepareAndExec($sql, $arg); diff --git a/htdocs/public/about.php b/htdocs/public/about.php new file mode 100644 index 0000000..a7a119d --- /dev/null +++ b/htdocs/public/about.php @@ -0,0 +1,140 @@ + + + + + About + + + + + + + + + + + diff --git a/htdocs/public/css/main.css b/htdocs/public/css/main.css index d7fa822..16fa67a 100755 --- a/htdocs/public/css/main.css +++ b/htdocs/public/css/main.css @@ -11,7 +11,7 @@ body { height: 100%; font-family: "Sans-serif", "Helvetica" !important; - font-size: 14px; + font-size: 12px; background: #fff; } @@ -93,6 +93,10 @@ a { background-image: url(/images/checked.png); } +.dropdown-content-checkbox-hidden { + display: none !important; +} + /* Hide the link that should open and close the topnav on small screens */ .topnav .icon { display: none; @@ -264,7 +268,7 @@ a { overflow: hidden; } -#modal-station-info-iframe, #modal-station-search-iframe { +#modal-station-info-iframe, #modal-station-search-iframe, #modal-about-iframe { width: 100%; height: 100%; border: none; diff --git a/htdocs/public/data/coverage.php b/htdocs/public/data/coverage.php new file mode 100644 index 0000000..c1ea3b8 --- /dev/null +++ b/htdocs/public/data/coverage.php @@ -0,0 +1,24 @@ +getObjectById($_GET['id'] ?? null); +if ($station->isExistingObject()) { + $response['station_id'] = $station->id; + $response['coverage'] = []; + + $numberOfHours = 10*24; // latest 10 days should be enough + $limit = 10000; // Limit number of packets to reduce load on server (and browser) + $packetPaths = PacketPathRepository::getInstance()->getLatestDataListByReceivingStationId($_GET['id'] ?? null, $numberOfHours, $limit); + foreach ($packetPaths as $path) { + $row = []; + $row['latitude'] = $path['sending_latitude']; + $row['longitude'] = $path['sending_longitude']; + $row['distance'] = $path['distance']; + $response['coverage'][] = $row; + } +} + +header('Content-type: application/json'); +echo json_encode($response); diff --git a/htdocs/public/kml.php b/htdocs/public/data/kml.php similarity index 95% rename from htdocs/public/kml.php rename to htdocs/public/data/kml.php index 5f815e4..7038a33 100644 --- a/htdocs/public/kml.php +++ b/htdocs/public/data/kml.php @@ -1,9 +1,9 @@ getObjectById($_GET['sid']); +if (isset($_GET['id']) && isInt($_GET['id'])) { + $station = StationRepository::getInstance()->getObjectById($_GET['id']); } else { $station = new Station(null); } diff --git a/htdocs/public/data/trail.php b/htdocs/public/data/trail.php new file mode 100644 index 0000000..1229b1e --- /dev/null +++ b/htdocs/public/data/trail.php @@ -0,0 +1,55 @@ +getObjectById($_GET['id'] ?? null); +if ($station->isExistingObject()) { + $numberOfHours = $_GET['hours'] ?? 1; +error_log($numberOfHours); + $columns = ['timestamp']; + $type = 'speed'; + if ($_GET['type'] == 'speed') { + $type = 'speed'; + if ($_GET['imperialUnits'] ?? '0' == '1') { + $response[] = array('Time', 'Speed (mph)'); + } else { + $response[] = array('Time', 'Speed (kmh)'); + } + } else { + $type = 'altitude'; + if ($_GET['imperialUnits'] ?? '0' == '1') { + $response[] = array('Time', 'Altitude (ft)'); + } else { + $response[] = array('Time', 'Altitude (m)'); + } + } + $columns[] = $type; + + $packets = PacketRepository::getInstance()->getLatestDataListByStationId($_GET['id'] ?? null, $numberOfHours, $columns); + foreach($packets as $packet) { + $value = floatval($packet[$type]); + if ($_GET['imperialUnits'] ?? '0' == '1') { + if ($type == 'speed') { + $value = convertKilometerToMile($value); + } else if ($type == 'altitude') { + $value = convertMeterToFeet($value); + } + } + + if ($type == 'speed' && count($response) > 1) { + if (isset($response[count($response) - 1])) { + $prevTimestamp = $response[count($response) - 1][0]; + if ($prevTimestamp < ($timestamp - 60*60)) { + // Previous value is old, make sure we have a break in graph + $response[] = array($prevTimestamp + 1, null); + } + } + } + + $response[] = [$packet['timestamp'], $value]; + } +} + +header('Content-type: application/json'); +echo json_encode($response); diff --git a/htdocs/public/index.php b/htdocs/public/index.php index 2333f6b..202f748 100755 --- a/htdocs/public/index.php +++ b/htdocs/public/index.php @@ -20,11 +20,13 @@ + - + + @@ -52,12 +54,11 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/htdocs/public/station/weather.php b/htdocs/public/station/weather.php index d52090c..715fd81 100644 --- a/htdocs/public/station/weather.php +++ b/htdocs/public/station/weather.php @@ -68,6 +68,7 @@