├── README.md ├── images ├── putty1.png ├── putty2.png ├── wintask1.png └── wintask2.png ├── sql ├── flights.sql ├── mlat_cleanup.sql └── track_mlat_lookup.sql ├── webserver ├── ADSB.png ├── MLAT.png ├── Mode-S.png ├── config-example.php ├── destination.png ├── fasource.gif ├── flightimport.php ├── flights.php ├── functions.php ├── getTrackMlat.php ├── jquery.timeago.js ├── map.php ├── origin.png ├── search.php └── style.css └── windows ├── db_query.bat ├── db_query.vbs └── dbquerycommands.txt /README.md: -------------------------------------------------------------------------------- 1 | # VRS-flights-db 2 | Code to export Virtual Radar Server flight records and track logs to a MySQL database. This is the code used to power http://flights.hillhome.org. 3 | Also includes example pages written in PHP to display flight info. 4 | 5 | ##Prerequisites 6 | - VRS installed and running 7 | - VRS database writer plugin configured and enabled 8 | - A web server running PHP and MySQL. These instructions assume you will be using a Linux host, although you can use any OS. 9 | - PuTTY (including pageant and pscp) installed on your VRS machine 10 | - You will need to configure Pageant for key-based authentication to your web server using instructions such as http://johannesbrodwall.com/2011/06/15/howto-use-pageant-and-putty/ 11 | - [phpMyAdmin](https://www.phpmyadmin.net) is very helpful for viewing your MySQL tables to ensure records are being populated correctly. 12 | 13 | ##Instructions 14 | Note: These instructions are not exhaustive. You will need to be handy with Windows, Linux, MySQL, etc. If you encounter issues, please log them here and I will update the documentation. 15 | 16 | ### Database schema 17 | You will need to create a database and two tables on your MySQL database host. 18 | 19 | ``` 20 | mysql -u root 21 | $create database adsb; 22 | grant usage on *.* to vrsdbwriter@localhost identified by 'somepasswordhere'; 23 | grant all privileges on adsb.* to vrsdbwriter@localhost; 24 | ``` 25 | 26 | Now import the three .sql files in this repository: 27 | ``` 28 | use adsb; 29 | source path/to/flights.sql 30 | source path/to/track_mlat_lookup.sql 31 | source path/to/mlat_cleanup.sql 32 | ``` 33 | 34 | ###Install sqlite for Windows 35 | You will need to install the sqlite3.exe binary on your VRS host. 36 | - Grab the sqlite-tools-win32-x86 zip file from https://www.sqlite.org/download.html 37 | - Unzip and place sqlite3.exe in c:\sqlite 38 | 39 | ###Windows scripts 40 | Place the files from the windows directory of this repository in your c:\sqlite directory on the VRS host: 41 | - db_query.bat 42 | - db_query.vbs 43 | - dbquerycommands.txt 44 | 45 | Double check all the paths in these files, as your setup may differ. 46 | 47 | ###PHP scripts 48 | Place all the files from the webserver directory of this repository onto your web server in a flights directory under the web server's document root (example: /srv/www/htdocs/flights) 49 | 50 | You will need to edit the config-example.php file and fill in your database connection information and your VRS hostname and port, plus username and password if your setup is password protected. *Then rename the file to config.php.* 51 | 52 | Now login to your web server and edit the crontab as follows: 53 | ``` 54 | crontab -e 55 | ``` 56 | Enter the following line into the file - this will run the getTrackMlat.php file every minute. 57 | ``` 58 | */1 * * * * /usr/bin/php /srv/www/htdocs/flights/getTrackMlat.php >/dev/null 59 | ``` 60 | Save the file, and the new crontab will be installed. After a few minutes, you should observe rows being added to the track_mlat_lookup table in your database. 61 | 62 | ###PuTTY Setup on Windows 63 | First, configure Pageant for password-less authentication to your web server as discussed in the prereqs section. 64 | Next, create a PuTTY profile for your web server that will execute the flightimport.php file, as depicted in the following two images. Make sure you use the correct path to the php binary on your web server. 65 | ![alt tag](https://raw.github.com/ProHill/VRS-flights-db/master/images/putty1.png) 66 | ![alt tag](https://raw.github.com/ProHill/VRS-flights-db/master/images/putty2.png) 67 | 68 | Save the profile with the name flightimport. 69 | 70 | You will now need to use Windows Task Scheduler on your VRS host to run db_query.vbs every 5 minutes, as depicted in the following images. 71 | 72 | ![alt tag](https://raw.github.com/ProHill/VRS-flights-db/master/images/wintask1.png) 73 | ![alt tag](https://raw.github.com/ProHill/VRS-flights-db/master/images/wintask2.png) 74 | 75 | That should complete the setup. New flight records will be added to the flights table every 5 minutes, and the track log and MLAT flag will be merged in from the track_mlat_lookup table as part of the import process. 76 | 77 | ##Displaying Flight Data 78 | (Work in Progress) 79 | 80 | ###flights.php 81 | Displays a flight log with clickable links to the map page 82 | 83 | ###map.php 84 | Displays the route and full track log 85 | - Usage: http://webserver/flights/map.php?id=123456 86 | - Where 123456 is the flight's ID number from the database 87 | 88 | ###search.php 89 | Search the database based on flight/aircraft details 90 | -------------------------------------------------------------------------------- /images/putty1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/images/putty1.png -------------------------------------------------------------------------------- /images/putty2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/images/putty2.png -------------------------------------------------------------------------------- /images/wintask1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/images/wintask1.png -------------------------------------------------------------------------------- /images/wintask2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/images/wintask2.png -------------------------------------------------------------------------------- /sql/flights.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `flightstable` ( 2 | `ID` int(11) NOT NULL, 3 | `ModeS` varchar(6) CHARACTER SET utf8 NOT NULL, 4 | `Country` varchar(24) CHARACTER SET utf8 DEFAULT NULL, 5 | `Registration` varchar(20) CHARACTER SET utf8 DEFAULT NULL, 6 | `Operator` varchar(100) CHARACTER SET utf8 DEFAULT NULL, 7 | `Callsign` varchar(20) CHARACTER SET utf8 DEFAULT NULL, 8 | `ModelCode` varchar(10) CHARACTER SET utf8 DEFAULT NULL, 9 | `AircraftModel` varchar(40) CHARACTER SET utf8 DEFAULT NULL, 10 | `OperatorCode` varchar(20) CHARACTER SET utf8 DEFAULT NULL, 11 | `FirstSeen` datetime NOT NULL, 12 | `FirstLatitude` double DEFAULT NULL, 13 | `FirstLongitude` double DEFAULT NULL, 14 | `FirstAltitude` int(11) DEFAULT NULL, 15 | `LastSeen` datetime NOT NULL, 16 | `LastLatitude` double DEFAULT NULL, 17 | `LastLongitude` double DEFAULT NULL, 18 | `LastAltitude` int(11) DEFAULT NULL, 19 | `NumPositionReports` int(11) DEFAULT NULL, 20 | `FromICAO` char(4) CHARACTER SET utf8 DEFAULT NULL, 21 | `FromIATA` char(3) CHARACTER SET utf8 DEFAULT NULL, 22 | `FromName` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 23 | `FromLat` double DEFAULT NULL, 24 | `FromLong` double DEFAULT NULL, 25 | `FromLocation` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 26 | `FromCountry` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 27 | `ToICAO` char(4) CHARACTER SET utf8 DEFAULT NULL, 28 | `ToIATA` char(3) CHARACTER SET utf8 DEFAULT NULL, 29 | `ToName` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 30 | `ToLat` double DEFAULT NULL, 31 | `ToLong` double DEFAULT NULL, 32 | `ToLocation` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 33 | `ToCountry` varchar(80) CHARACTER SET utf8 DEFAULT NULL, 34 | `Interesting` tinyint(1) NOT NULL DEFAULT '0', 35 | `Mlat` tinyint(1) NOT NULL DEFAULT '0', 36 | `Track` varchar(60000) DEFAULT NULL, 37 | `Note` varchar(100) DEFAULT NULL 38 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 39 | 40 | -- 41 | -- Indexes for dumped tables 42 | -- 43 | 44 | -- 45 | -- Indexes for table `flightsos` 46 | -- 47 | ALTER TABLE `flightstable` 48 | ADD PRIMARY KEY (`ID`), 49 | ADD KEY `LastSeen` (`LastSeen`), 50 | ADD KEY `ModeS` (`ModeS`), 51 | ADD KEY `Registration` (`Registration`), 52 | ADD KEY `Operator` (`Operator`), 53 | ADD KEY `Callsign` (`Callsign`), 54 | ADD KEY `AircraftModel` (`AircraftModel`), 55 | ADD KEY `Interesting_Index` (`ModeS`,`Callsign`,`Interesting`,`LastSeen`) USING BTREE, 56 | ADD KEY `ModeS_LastSeen` (`ModeS`,`LastSeen`), 57 | ADD KEY `Interesting_ModeS` (`Interesting`,`ModeS`); 58 | -------------------------------------------------------------------------------- /sql/mlat_cleanup.sql: -------------------------------------------------------------------------------- 1 | CREATE EVENT `Delete old MLAT lookup rows` ON SCHEDULE EVERY 1 HOUR STARTS NOW() ON COMPLETION PRESERVE ENABLE DO DELETE FROM `track_mlat_lookup` WHERE Timestamp < (NOW() - INTERVAL 24 HOUR); 2 | -------------------------------------------------------------------------------- /sql/track_mlat_lookup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `track_mlat_lookup` ( 2 | `Id` int(11) NOT NULL, 3 | `Icao` varchar(6) NOT NULL, 4 | `Callsign` varchar(20) DEFAULT NULL, 5 | `Registration` varchar(20) DEFAULT NULL, 6 | `Mlat` tinyint(1) NOT NULL, 7 | `Track` varchar(60000) DEFAULT NULL, 8 | `MultiTrack` tinyint(1) DEFAULT '0', 9 | `Timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 10 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 11 | 12 | -- 13 | -- Indexes for dumped tables 14 | -- 15 | 16 | -- 17 | -- Indexes for table `mlat_lookupos` 18 | -- 19 | ALTER TABLE `track_mlat_lookup` 20 | ADD PRIMARY KEY (`Id`), 21 | ADD KEY `Icao_Timestamp` (`Icao`,`Timestamp`); -------------------------------------------------------------------------------- /webserver/ADSB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/ADSB.png -------------------------------------------------------------------------------- /webserver/MLAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/MLAT.png -------------------------------------------------------------------------------- /webserver/Mode-S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/Mode-S.png -------------------------------------------------------------------------------- /webserver/config-example.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webserver/destination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/destination.png -------------------------------------------------------------------------------- /webserver/fasource.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/fasource.gif -------------------------------------------------------------------------------- /webserver/flightimport.php: -------------------------------------------------------------------------------- 1 | true, 15 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 16 | ) 17 | ); 18 | } catch (PDOException $e) { 19 | die("database connection failed: ".$e->getMessage()); 20 | } 21 | $affectedRows = $pdo->exec(" 22 | LOAD DATA LOCAL INFILE ".$pdo->quote($csvfile)." IGNORE INTO TABLE $flightsdatabasetable 23 | FIELDS TERMINATED BY ".$pdo->quote($fieldseparator)." 24 | ESCAPED BY '' 25 | LINES TERMINATED BY ".$pdo->quote($lineseparator)); 26 | 27 | $date = date('m/d/Y h:i:s a'); 28 | echo "Loaded a total of $affectedRows records from this csv file at $date\n"; 29 | 30 | $stmt = "SELECT * FROM $flightsdatabasetable ORDER BY LastSeen DESC LIMIT " . $affectedRows; 31 | 32 | $conn = new mysqli($databasehost, $databaseusername, $databasepassword, $databasename); 33 | 34 | if ($conn->connect_error) { 35 | die("Connection failed: " . $conn->connect_error); 36 | } 37 | 38 | $result = $conn->query($stmt); 39 | 40 | //ob_start(); 41 | if ($result->num_rows > 0) { 42 | while($row = $result->fetch_assoc()) { 43 | $ModeS = $row["ModeS"]; 44 | 45 | // Check if the flight's positions are MLAT-derived 46 | // Also grab the flight's full track log, if there is one 47 | $mlat_stmt = 'SELECT Icao, Mlat, Track from ' . $lookup_table . ' WHERE Icao = "' . $ModeS . '" AND Timestamp >= NOW() - INTERVAL 2 HOUR ORDER BY Timestamp DESC LIMIT 1'; 48 | $mlat_result = $conn->query($mlat_stmt); 49 | if ($mlat_result->num_rows > 0) { 50 | while ($mlat_row = $mlat_result->fetch_assoc()) { 51 | 52 | // Override MLAT flag to true if NumPositionReports is 0 but there is a track 53 | if (($row["NumPositionReports"] == 0) && (!empty($mlat_row["Track"]))) { 54 | $mlat_row["Mlat"] = 1; 55 | } 56 | // Add logic here to deal with multiple tracks in the Track field in the mlat_lookup table 57 | $Trackarray = explode('|', $mlat_row["Track"]); 58 | if (count($Trackarray) > 1) { 59 | $mlat_row["Track"] = str_replace("]|[", ",", $mlat_row["Track"]); 60 | } 61 | $mlat_update_stmt = 'UPDATE ' . $flightsdatabasetable . ' SET Mlat = ' . $mlat_row["Mlat"] . ', Track = "' . $mlat_row["Track"] . '" WHERE ModeS = "' . $mlat_row["Icao"] . '" AND LastSeen >= NOW() - INTERVAL 1 HOUR'; 62 | $mlat_update_result = $conn->query($mlat_update_stmt); 63 | } 64 | } 65 | } 66 | } 67 | //ob_end_clean(); 68 | $conn->close(); 69 | 70 | ?> -------------------------------------------------------------------------------- /webserver/flights.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 | Latest Flights 30 | 31 | 32 |
'; 33 | 34 | echo '

Flight Log

35 |
'; 36 | 37 | //check if the starting row variable was passed in the URL or not 38 | if (!isset($_GET['limit'])) { 39 | if (isset($_GET['recordcount'])) { 40 | $limit = "0," . $_GET['recordcount']; 41 | } 42 | else { 43 | //we give the value of the starting row 0 because nothing was found in URL 44 | $limit = "0,100"; 45 | } 46 | } 47 | else { 48 | $limit = $_GET['limit']; 49 | } 50 | 51 | $conn = new mysqli($databasehost, $databaseusername, $databasepassword, $databasename); 52 | 53 | if ($conn->connect_error) { 54 | die("Connection failed: " . $conn->connect_error); 55 | } 56 | 57 | echo '

'; 58 | list($offsetzero, $recordcount) = explode(",", $limit); 59 | $offset=$offsetzero+1; 60 | 61 | // Display navigation links 62 | // Only print a previous link if a Next was clicked 63 | $prev = $offsetzero - $recordcount; 64 | echo '"; 69 | 70 | $stmt = "SELECT ID, ModeS, Country, Registration, AircraftModel, ModelCode, Operator, OperatorCode, Callsign, FromIATA, FromICAO, FromName, FromLocation, FromCountry, ToIATA, ToICAO, ToName, ToLocation, ToCountry, NumPositionReports, Mlat, DATE_FORMAT(`LastSeen`, '%Y-%m-%dT%H:%i:%s') as LastSeenAgo FROM $flightsdatabasetable ORDER by LastSeen DESC LIMIT $limit"; 71 | 72 | $result = $conn->query($stmt); 73 | echo '
Flights ' . $offset . ' to ' . ($recordcount+$offsetzero) . 74 | '
75 |
76 |
78 | 79 |
'; 80 | 81 | if ($result->num_rows > 0) { 82 | echo "
"; 83 | 84 | // *** Call function to write the flight table *** 85 | writeFlightTable($result); 86 | echo "
"; 87 | // Display navigation links 88 | echo '"; 95 | 96 | } else { 97 | echo "0 results"; 98 | } 99 | $conn->close(); 100 | echo '
'; 101 | echo ''; 102 | ?> -------------------------------------------------------------------------------- /webserver/functions.php: -------------------------------------------------------------------------------- 1 | "; 4 | echo "Flight IDMode S CodeRegistration AircraftAirline/OperatorCallsign RouteLast Seen"; 5 | echo ""; 6 | while($row = $result->fetch_assoc()) { 7 | echo ''; 8 | echo '' . $row["ID"] . ''; 9 | 10 | echo '' . $row["ModeS"] . ''; 11 | if ($row["Mlat"] == 1) { 12 | // MLAT icon 13 | echo 'MLAT'; 14 | } 15 | elseif ($row["NumPositionReports"] >= 1) { 16 | // ADS-B icon 17 | echo 'ADS-B'; 18 | } 19 | else { 20 | // Mode-S icon 21 | echo 'ModeS'; 22 | } 23 | 24 | echo ''; 25 | if ($row["Registration"] != "") { 26 | echo ''.$row["Registration"].' '; 27 | } 28 | 29 | echo "".$row["AircraftModel"]; 30 | if ($row["ModelCode"] != "") { 31 | echo " (".$row["ModelCode"].")" . ""; 32 | } 33 | else { 34 | echo ""; 35 | } 36 | echo htmlspecialchars($row["Operator"]); 37 | 38 | echo ""; 39 | 40 | if ($row["Callsign"] != "") { 41 | echo ''.$row["Callsign"].' '; 42 | } 43 | 44 | // Route 45 | if ($row["FromIATA"] != "") { 46 | echo '' . $row["FromIATA"] . ' to ' . $row["ToIATA"] . ''; 47 | } 48 | else echo ""; 49 | echo "".$row["LastSeenAgo"].""; 50 | } 51 | echo ""; 52 | 53 | } // End writeFlightTable function 54 | ?> -------------------------------------------------------------------------------- /webserver/getTrackMlat.php: -------------------------------------------------------------------------------- 1 | true, 6 | CURLOPT_FOLLOWLOCATION => TRUE, 7 | CURLOPT_VERBOSE => FALSE, 8 | CURLOPT_ENCODING => "gzip", 9 | CURLOPT_URL => "http://$vrshostname:$vrsport/VirtualRadar/AircraftList.json?trFmt=f&refreshTrails=1" 10 | ]; 11 | curl_setopt_array($ch, $options); 12 | 13 | if ( $vrsusername != "" && $vrspassword != "") { 14 | curl_setopt($ch, CURLOPT_USERPWD, "$vrsusername:$vrspassword"); 15 | } 16 | 17 | $data = curl_exec($ch); 18 | 19 | $data = json_decode($data); 20 | 21 | try { 22 | $conn = new PDO("mysql:host=$databasehost;dbname=$databasename", $databaseusername, $databasepassword); 23 | // set the PDO error mode to exception 24 | $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 25 | 26 | $sql = "INSERT INTO $lookup_table (Id, Icao, Callsign, Registration, Mlat, Track, MultiTrack) VALUES (:Id, :Icao, :Callsign, :Registration, :Mlat, :Track, :MultiTrack) 27 | ON DUPLICATE KEY UPDATE Id = :Id, Icao = :Icao, Callsign = :Callsign, Registration = :Registration, Mlat = :Mlat, Track = :Track, MultiTrack = :MultiTrack"; 28 | 29 | // Look for previous tracks in the past few minutes to address aircraft that drop out for a few minutes 30 | $tracklookupsql = "SELECT Track from $lookup_table where Id = :Id AND Timestamp >= NOW() - INTERVAL 23 MINUTE ORDER BY Timestamp DESC LIMIT 1"; 31 | 32 | // Prepare statement 33 | $stmt = $conn->prepare($sql); 34 | $tracklookupstmt = $conn->prepare($tracklookupsql); 35 | 36 | $Id = null; 37 | $Icao = null; 38 | $Callsign = null; 39 | $Registration = null; 40 | $Mlat = null; 41 | $Track = null; 42 | $MultiTrack = null; 43 | 44 | $stmt->bindParam(':Id', $Id, PDO::PARAM_INT); 45 | $stmt->bindParam(':Icao', $Icao, PDO::PARAM_STR); 46 | $stmt->bindParam(':Callsign', $Callsign, PDO::PARAM_STR); 47 | $stmt->bindParam(':Registration', $Registration, PDO::PARAM_STR); 48 | $stmt->bindParam(':Mlat', $Mlat, PDO::PARAM_BOOL); 49 | $stmt->bindParam(':Track', $Track, PDO::PARAM_STR); 50 | $stmt->bindParam(':MultiTrack', $MultiTrack, PDO::PARAM_STR); 51 | $tracklookupstmt->bindParam(':Id', $Id, PDO::PARAM_INT); 52 | 53 | //$aircraftIDs = array(); 54 | foreach($data->acList as $aircraft) { 55 | if ($aircraft->Id == 0) { 56 | continue; 57 | } 58 | $MultiTrack = 0; 59 | $Id = $aircraft->Id; 60 | //$aircraftIDs[] = $Id; 61 | $Icao = $aircraft->Icao; 62 | if (isset($aircraft->Call)) { 63 | $Callsign = $aircraft->Call; 64 | } 65 | else { 66 | $Callsign = null; 67 | } 68 | if (isset($aircraft->Reg)) { 69 | $Registration = $aircraft->Reg; 70 | } 71 | else { 72 | $Registration = null; 73 | } 74 | if (isset($aircraft->Mlat)) { 75 | $Mlat = $aircraft->Mlat; 76 | } 77 | else { 78 | $Mlat = false; 79 | } 80 | if (isset($aircraft->Cot)) { 81 | $Track = json_encode($aircraft->Cot); 82 | // Put previous track lookup here 83 | $tracklookupresult = $tracklookupstmt->execute(); 84 | $row = $tracklookupstmt->fetch(); 85 | //echo $row[0]; 86 | $storedTrack = $row[0]; 87 | $Trackarray = explode('|', $storedTrack); 88 | 89 | if ((count($Trackarray) == 1) && $storedTrack != "") { 90 | //echo "Only 1 track" . PHP_EOL; 91 | if (substr($storedTrack, 1, 18) == substr($Track, 1, 18)) { 92 | // Same track, just overwrite it as before 93 | //echo "Same track - overwrite"; 94 | } 95 | else { 96 | // New track, need to append it 97 | //echo "New track - append" . PHP_EOL; 98 | $Track = $storedTrack . "|" . $Track; 99 | $MultiTrack = 1; 100 | } 101 | } 102 | elseif ((count($Trackarray) == 1) && $storedTrack == "") { 103 | if (substr($storedTrack, 1, 18) == substr($Track, 1, 18)) { 104 | // Same track, just overwrite it as before 105 | // echo "Brand new track" . PHP_EOL; 106 | } 107 | } 108 | 109 | elseif (count($Trackarray) > 1) { 110 | $lastindex = count($Trackarray) - 1; 111 | if (substr($Trackarray[$lastindex], 1, 18) == substr($Track, 1, 18)) { 112 | //echo "Last Track matches - need to overwrite it" . PHP_EOL; 113 | $Trackarray[$lastindex] = $Track; 114 | $Track = implode("|", $Trackarray); 115 | $MultiTrack = 1; 116 | } 117 | else { 118 | //echo "New track, need to append another one" . PHP_EOL; 119 | array_push($Trackarray, $Track); 120 | $Track = implode("|", $Trackarray); 121 | $MultiTrack = 1; 122 | } 123 | } 124 | } 125 | else { 126 | $Track = null; 127 | } 128 | // execute the query 129 | //$stmt->debugDumpParams(); 130 | $stmt->execute(); 131 | } 132 | 133 | // UPDATE succeeded 134 | echo "Records added successfully\n"; 135 | } 136 | catch(PDOException $e) 137 | { 138 | echo $sql . "
" . $e->getMessage(); 139 | } 140 | 141 | $conn = null; 142 | ?> 143 | -------------------------------------------------------------------------------- /webserver/jquery.timeago.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timeago is a jQuery plugin that makes it easy to support automatically 3 | * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). 4 | * 5 | * @name timeago 6 | * @version 1.4.1 7 | * @requires jQuery v1.2.3+ 8 | * @author Ryan McGeary 9 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 10 | * 11 | * For usage and examples, visit: 12 | * http://timeago.yarp.com/ 13 | * 14 | * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) 15 | */ 16 | 17 | (function (factory) { 18 | if (typeof define === 'function' && define.amd) { 19 | // AMD. Register as an anonymous module. 20 | define(['jquery'], factory); 21 | } else { 22 | // Browser globals 23 | factory(jQuery); 24 | } 25 | }(function ($) { 26 | $.timeago = function(timestamp) { 27 | if (timestamp instanceof Date) { 28 | return inWords(timestamp); 29 | } else if (typeof timestamp === "string") { 30 | return inWords($.timeago.parse(timestamp)); 31 | } else if (typeof timestamp === "number") { 32 | return inWords(new Date(timestamp)); 33 | } else { 34 | return inWords($.timeago.datetime(timestamp)); 35 | } 36 | }; 37 | var $t = $.timeago; 38 | 39 | $.extend($.timeago, { 40 | settings: { 41 | refreshMillis: 60000, 42 | allowPast: true, 43 | allowFuture: false, 44 | localeTitle: false, 45 | cutoff: 0, 46 | strings: { 47 | prefixAgo: null, 48 | prefixFromNow: null, 49 | suffixAgo: "ago", 50 | suffixFromNow: "from now", 51 | inPast: 'any moment now', 52 | seconds: "less than a minute", 53 | minute: "about a minute", 54 | minutes: "%d minutes", 55 | hour: "about an hour", 56 | hours: "about %d hours", 57 | day: "a day", 58 | days: "%d days", 59 | month: "about a month", 60 | months: "%d months", 61 | year: "about a year", 62 | years: "%d years", 63 | wordSeparator: " ", 64 | numbers: [] 65 | } 66 | }, 67 | 68 | inWords: function(distanceMillis) { 69 | if(!this.settings.allowPast && ! this.settings.allowFuture) { 70 | throw 'timeago allowPast and allowFuture settings can not both be set to false.'; 71 | } 72 | 73 | var $l = this.settings.strings; 74 | var prefix = $l.prefixAgo; 75 | var suffix = $l.suffixAgo; 76 | if (this.settings.allowFuture) { 77 | if (distanceMillis < 0) { 78 | prefix = $l.prefixFromNow; 79 | suffix = $l.suffixFromNow; 80 | } 81 | } 82 | 83 | if(!this.settings.allowPast && distanceMillis >= 0) { 84 | return this.settings.strings.inPast; 85 | } 86 | 87 | var seconds = Math.abs(distanceMillis) / 1000; 88 | var minutes = seconds / 60; 89 | var hours = minutes / 60; 90 | var days = hours / 24; 91 | var years = days / 365; 92 | 93 | function substitute(stringOrFunction, number) { 94 | var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; 95 | var value = ($l.numbers && $l.numbers[number]) || number; 96 | return string.replace(/%d/i, value); 97 | } 98 | 99 | var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || 100 | seconds < 90 && substitute($l.minute, 1) || 101 | minutes < 45 && substitute($l.minutes, Math.round(minutes)) || 102 | minutes < 90 && substitute($l.hour, 1) || 103 | hours < 24 && substitute($l.hours, Math.round(hours)) || 104 | hours < 42 && substitute($l.day, 1) || 105 | days < 30 && substitute($l.days, Math.round(days)) || 106 | days < 45 && substitute($l.month, 1) || 107 | days < 365 && substitute($l.months, Math.round(days / 30)) || 108 | years < 1.5 && substitute($l.year, 1) || 109 | substitute($l.years, Math.round(years)); 110 | 111 | var separator = $l.wordSeparator || ""; 112 | if ($l.wordSeparator === undefined) { separator = " "; } 113 | return $.trim([prefix, words, suffix].join(separator)); 114 | }, 115 | 116 | parse: function(iso8601) { 117 | var s = $.trim(iso8601); 118 | s = s.replace(/\.\d+/,""); // remove milliseconds 119 | s = s.replace(/-/,"/").replace(/-/,"/"); 120 | s = s.replace(/T/," ").replace(/Z/," UTC"); 121 | s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 122 | s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 123 | return new Date(s); 124 | }, 125 | datetime: function(elem) { 126 | var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); 127 | return $t.parse(iso8601); 128 | }, 129 | isTime: function(elem) { 130 | // jQuery's `is()` doesn't play well with HTML5 in IE 131 | return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); 132 | } 133 | }); 134 | 135 | // functions that can be called via $(el).timeago('action') 136 | // init is default when no action is given 137 | // functions are called with context of a single element 138 | var functions = { 139 | init: function(){ 140 | var refresh_el = $.proxy(refresh, this); 141 | refresh_el(); 142 | var $s = $t.settings; 143 | if ($s.refreshMillis > 0) { 144 | this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); 145 | } 146 | }, 147 | update: function(time){ 148 | var parsedTime = $t.parse(time); 149 | $(this).data('timeago', { datetime: parsedTime }); 150 | if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); 151 | refresh.apply(this); 152 | }, 153 | updateFromDOM: function(){ 154 | $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); 155 | refresh.apply(this); 156 | }, 157 | dispose: function () { 158 | if (this._timeagoInterval) { 159 | window.clearInterval(this._timeagoInterval); 160 | this._timeagoInterval = null; 161 | } 162 | } 163 | }; 164 | 165 | $.fn.timeago = function(action, options) { 166 | var fn = action ? functions[action] : functions.init; 167 | if(!fn){ 168 | throw new Error("Unknown function name '"+ action +"' for timeago"); 169 | } 170 | // each over objects here and call the requested function 171 | this.each(function(){ 172 | fn.call(this, options); 173 | }); 174 | return this; 175 | }; 176 | 177 | function refresh() { 178 | //check if it's still visible 179 | if(!$.contains(document.documentElement,this)){ 180 | //stop if it has been removed 181 | $(this).timeago("dispose"); 182 | return this; 183 | } 184 | 185 | var data = prepareData(this); 186 | var $s = $t.settings; 187 | 188 | if (!isNaN(data.datetime)) { 189 | if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { 190 | $(this).text(inWords(data.datetime)); 191 | } 192 | } 193 | return this; 194 | } 195 | 196 | function prepareData(element) { 197 | element = $(element); 198 | if (!element.data("timeago")) { 199 | element.data("timeago", { datetime: $t.datetime(element) }); 200 | var text = $.trim(element.text()); 201 | if ($t.settings.localeTitle) { 202 | element.attr("title", element.data('timeago').datetime.toLocaleString()); 203 | } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { 204 | element.attr("title", text); 205 | } 206 | } 207 | return element.data("timeago"); 208 | } 209 | 210 | function inWords(date) { 211 | return $t.inWords(distance(date)); 212 | } 213 | 214 | function distance(date) { 215 | return (new Date().getTime() - date.getTime()); 216 | } 217 | 218 | // fix for IE6 suckage 219 | document.createElement("abbr"); 220 | document.createElement("time"); 221 | })); 222 | -------------------------------------------------------------------------------- /webserver/map.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Flight Map 12 | 13 | 14 | 239 | 240 | 241 |
'; 242 | 243 | $id = ''; 244 | $sqlwhere = ''; 245 | 246 | if (isset($_GET['id'])) { 247 | if (preg_match("/^[0-9].*/", $_GET['id'])) { 248 | $id = $_GET['id']; 249 | $sqlwhere .= 'WHERE ID =' . $id; 250 | } else { 251 | $id = 0; 252 | $sqlwhere .= 'WHERE ID = ' . $id; 253 | } 254 | } else { 255 | $id = 0; 256 | $sqlwhere .= 'WHERE ID = ' . $id; 257 | } 258 | 259 | $stmt = "SELECT * FROM " . $flightsdatabasetable . " " . $sqlwhere; 260 | 261 | $conn = new mysqli($databasehost, $databaseusername, $databasepassword, $databasename); 262 | 263 | if ($conn->connect_error) { 264 | die("Connection failed: " . $conn->connect_error); 265 | } 266 | 267 | $result = $conn->query($stmt); 268 | 269 | echo '

Flight Map

270 |
id = ' . $id . '
'; 271 | 272 | if ($result->num_rows > 0) { 273 | $row = $result->fetch_assoc(); 274 | 275 | if ($row["Track"] != null) { 276 | $trackarray = json_decode($row["Track"]); 277 | } 278 | else { 279 | $trackarray = ""; 280 | } 281 | 282 | echo ' 283 | 294 | 310 |
Route OverviewTracked Log
284 |
285 |
'; 286 | // Display message if route map isn't available 287 | if ($row["FromLat"] == 0) { 288 | echo '
Flight route could not be determined. Check FlightAware.
'; 289 | echo ''; 290 | } else { // Show the map drop-down menu 291 | echo ''; 292 | } 293 | echo '
295 |
296 |
'; 297 | 298 | // Display message if track map isn't available 299 | if (($row["FirstLatitude"] == 0) || ($row["FirstLongitude"] == 0) || ($row["LastLatitude"] == 0) || ($row["LastLongitude"] == 0)) { 300 | if ($row["Track"] == null) { 301 | echo '
Aircraft was not broadcasting its position.
'; 302 | echo ''; 303 | } else { // Show the map drop-down menu 304 | echo ''; 305 | } 306 | } else { // Show the map drop-down menu 307 | echo ''; 308 | } 309 | echo '
311 | 312 | 313 | 314 | 315 | Direct route 316 |      317 | 318 | 319 | 320 | Actual route flown (if available)
'; 321 | } 322 | 323 | else { 324 | echo "0 results"; 325 | } 326 | $conn->close(); 327 | echo ' 328 | 349 | 350 | 371 |
372 | 373 | '; 421 | ?> 422 | -------------------------------------------------------------------------------- /webserver/origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProHill/VRS-flights-db/e2967d226d0f2030bae8857e7a3c9853e39d53b0/webserver/origin.png -------------------------------------------------------------------------------- /webserver/search.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 64 | 65 | Search Flights 66 | 67 | '; 68 | 69 | //check if the starting row variable was passed in the URL or not 70 | if (!isset($_GET['limit'])) { 71 | //we give the value of the starting row to 0 because nothing was found in URL 72 | $limit = "0,25"; 73 | //otherwise we take the value from the URL 74 | } 75 | else { 76 | $limit = $_GET['limit']; 77 | } 78 | $conn = new mysqli($databasehost, $databaseusername, $databasepassword, $databasename); 79 | 80 | if ($conn->connect_error) { 81 | die("Connection failed: " . $conn->connect_error); 82 | } 83 | 84 | // Build where clause 85 | $querystring = ''; 86 | $searchquery = ''; 87 | $sqlwhere = '1 = 1 '; 88 | $q = ''; 89 | $AircraftModel = ''; 90 | $ModeS = ''; 91 | $Registration = ''; 92 | $Operator = ''; 93 | $Callsign = ''; 94 | $FromAirport = ''; 95 | $ToAirport = ''; 96 | 97 | $modes_distinct = "false"; 98 | if(isset($_GET['modes_distinct'])){ 99 | $modes_distinct = "true"; 100 | } 101 | 102 | if(isset($_GET['q'])){ 103 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['q'])){ 104 | $q=$_GET['q']; 105 | $sqlwhere .= 'AND (Operator LIKE "%' . $q . '%" OR AircraftModel LIKE "%' . $q . '%" 106 | OR Callsign LIKE "%' . $q . '%" OR Country LIKE "%' . $q . '%" OR FromIATA LIKE "%' . $q . '%" 107 | OR FromName LIKE "%' . $q . '%" OR FromLocation LIKE "%' . $q . '%" 108 | OR ToIATA LIKE "%' . $q . '%" OR ToName LIKE "%' . $q . '%" OR ToLocation LIKE "%' . $q . '%" OR ModeS LIKE "%' . $q . '%") '; 109 | } 110 | else { 111 | $q=""; 112 | } 113 | } 114 | 115 | if(isset($_GET['AircraftModel'])){ 116 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['AircraftModel'])){ 117 | $AircraftModel=$_GET['AircraftModel']; 118 | $sqlwhere .= 'AND AircraftModel LIKE "%' . $AircraftModel . '%" '; 119 | } 120 | else { 121 | $AircraftModel=""; 122 | } 123 | } 124 | 125 | if(isset($_GET['Callsign'])){ 126 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['Callsign'])){ 127 | $Callsign=$_GET['Callsign']; 128 | $sqlwhere .= 'AND Callsign LIKE "%' . $Callsign . '%" '; 129 | } 130 | else { 131 | $Callsign=""; 132 | } 133 | } 134 | 135 | if(isset($_GET['ModeS'])){ 136 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['ModeS'])){ 137 | $ModeS=$_GET['ModeS']; 138 | $sqlwhere .= 'AND ModeS = "' . $ModeS . '" '; 139 | } 140 | else { 141 | $ModeS=""; 142 | } 143 | } 144 | 145 | if(isset($_GET['Registration'])){ 146 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['Registration'])){ 147 | $Registration=$_GET['Registration']; 148 | $sqlwhere .= 'AND Registration LIKE "%' . $Registration . '%" '; 149 | } 150 | else { 151 | $Registration = ""; 152 | } 153 | } 154 | 155 | if(isset($_GET['Operator'])){ 156 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['Operator'])){ 157 | $Operator=$_GET['Operator']; 158 | $sqlwhere .= 'AND Operator LIKE "%' . $Operator . '%" '; 159 | } 160 | else { 161 | $Operator=""; 162 | } 163 | } 164 | 165 | if(isset($_GET['FromAirport'])){ 166 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['FromAirport'])){ 167 | $FromAirport=$_GET['FromAirport']; 168 | $sqlwhere .= 'AND FromIATA LIKE "%' . $FromAirport . '%" '; 169 | } 170 | else { 171 | $FromAirport=""; 172 | } 173 | } 174 | 175 | if(isset($_GET['ToAirport'])){ 176 | if(preg_match("/^[A-Za-z0-9].*/", $_GET['ToAirport'])){ 177 | $ToAirport=$_GET['ToAirport']; 178 | $sqlwhere .= 'AND ToIATA LIKE "%' . $ToAirport . '%" '; 179 | } 180 | else { 181 | $ToAirport=""; 182 | } 183 | } 184 | 185 | // There is something on the query string - build the query 186 | if ($sqlwhere != "1 = 1 ") { 187 | $searchquery = "SELECT ID, ModeS, Country, Registration, AircraftModel, ModelCode, Operator, OperatorCode, Callsign, FromIATA, FromICAO, FromName, FromLocation, FromCountry, ToIATA, ToICAO, ToName, ToLocation, ToCountry, NumPositionReports, Interesting, Mlat, 188 | DATE_FORMAT(`LastSeen`, '%Y-%m-%dT%H:%i:%s') as LastSeenAgo 189 | FROM $flightsdatabasetable WHERE "; 190 | $searchquery .= $sqlwhere; 191 | $countquery = "SELECT COUNT(*) AS flightcount FROM $flightsdatabasetable WHERE "; 192 | $countquery .= $sqlwhere; 193 | if ($modes_distinct == "true") { 194 | $searchquery .= " GROUP BY ModeS DESC "; 195 | $countquery .= " GROUP BY ModeS "; 196 | } 197 | $searchquery .= "ORDER by LastSeen DESC LIMIT $limit"; 198 | } 199 | 200 | list($offsetzero, $recordcount) = explode(",", $limit); 201 | $offset=$offsetzero+1; 202 | 203 | echo ' 204 | 205 |
'; 206 | if ($searchquery != "") { 207 | echo '

Search Results

'; 208 | } 209 | echo '
'; 210 | 211 | if ($searchquery != "") { 212 | $result = $conn->query($searchquery); 213 | $countresult = $conn->query($countquery); 214 | $countresultrow = $countresult->fetch_assoc(); 215 | $resultcount = $countresultrow["flightcount"]; 216 | 217 | // If using distinct, count the rows differently 218 | if ($modes_distinct == "true") { 219 | $resultcount = mysqli_num_rows($countresult); 220 | } 221 | 222 | // Display navigation links 223 | // Only print a previous link if a Next was clicked 224 | $prev = $offsetzero - $recordcount; 225 | echo '"; 237 | 238 | echo '
'; 239 | echo 'results = ' . $resultcount . ''; 240 | if ($q != "") { 241 | echo 'keyword = ' . $q . ''; 242 | } 243 | if ($ModeS != "") { 244 | echo 'ModeS = ' . $ModeS . ''; 245 | } 246 | if ($AircraftModel != "") { 247 | echo 'aircraft = ' . $AircraftModel . ''; 248 | } 249 | if ($Operator != "") { 250 | echo 'operator = ' . $Operator . ''; 251 | } 252 | if ($Callsign != "") { 253 | echo 'callsign = ' . $Callsign . ''; 254 | } 255 | if ($Registration != "") { 256 | echo 'registration = ' . $Registration . ''; 257 | } 258 | if ($FromAirport != "") { 259 | echo 'from = ' . $FromAirport . ''; 260 | } 261 | if ($ToAirport != "") { 262 | echo 'to = ' . $ToAirport . ''; 263 | } 264 | echo 'distinct airframes = ' . $modes_distinct . ' 265 | limit per page = ' . $recordcount . '
'; 266 | 267 | if ($result->num_rows > 0) { 268 | echo "
"; 269 | 270 | // Call function to write the flight table 271 | writeFlightTable($result, $conn); 272 | echo "
"; 273 | // Display navigation links 274 | echo '"; 282 | } 283 | else { 284 | echo "0 results"; 285 | } 286 | } 287 | echo '

Search

288 |
289 |
290 |
291 | '; 292 | if ($q != "") { 293 | echo ''; 294 | } 295 | else { 296 | echo ''; 297 | } 298 | echo '
'; 299 | 300 | // Populate AircraftModel field 301 | $ModelQuery = "select distinct AircraftModel from $flightsdatabasetable order by AircraftModel ASC"; 302 | $ModelResult = $conn->query($ModelQuery); 303 | $ModelOptions = ""; 304 | while($ModelRow = $ModelResult->fetch_assoc()) { 305 | if ($AircraftModel == $ModelRow["AircraftModel"]) { 306 | $ModelOptions .= ''; 307 | } 308 | else { 309 | $ModelOptions .=""; 310 | } 311 | } 312 | $ModelMenu = '
313 |
314 | 315 | 316 |
'; 318 | 319 | echo $ModelMenu; 320 | 321 | // ModeS, Callsign, and Registration fields 322 | echo '
323 | '; 324 | if ($ModeS != "") { 325 | echo ''; 326 | } 327 | else { 328 | echo ''; 329 | } 330 | 331 | echo '

'; 332 | if ($Callsign != "") { 333 | echo ''; 334 | } 335 | else { 336 | echo ''; 337 | } 338 | 339 | echo '

'; 340 | if ($Registration != "") { 341 | echo ''; 342 | } 343 | else { 344 | echo ''; 345 | } 346 | echo '

'; 347 | 348 | // Populate Operator field 349 | $OperatorQuery = "select distinct Operator from $flightsdatabasetable order by Operator ASC"; 350 | $OperatorResult = $conn->query($OperatorQuery); 351 | $OperatorOptions = ""; 352 | while($OperatorRow = $OperatorResult->fetch_assoc()) { 353 | if ($Operator == $OperatorRow["Operator"]) { 354 | $OperatorOptions .= ''; 355 | } 356 | else { 357 | $OperatorOptions .= ''; 358 | } 359 | } 360 | $OperatorMenu = '

361 | 362 |
363 | 364 |
'; 366 | 367 | echo $OperatorMenu; 368 | 369 | echo '
370 | '; 371 | if ($FromAirport != "") { 372 | echo '
'; 373 | } 374 | else { 375 | echo '
'; 376 | } 377 | echo '

'; 378 | if ($ToAirport != "") { 379 | echo ''; 380 | } 381 | else { 382 | echo ''; 383 | } 384 | echo '
'; 385 | 386 | echo '
387 | 388 |
389 | 390 | 392 | 393 |
394 |
'; 395 | if ($modes_distinct == "true") { 396 | echo ' '; 397 | } 398 | else { 399 | echo ' '; 400 | } 401 | echo '
402 |
'; 403 | 404 | 405 | // Search Button 406 | echo '
407 |
408 | 409 |
410 |
'; 411 | 412 | $conn->close(); 413 | echo '
'; 414 | echo ''; 415 | echo ''; 551 | echo ''; 552 | ?> 553 | -------------------------------------------------------------------------------- /webserver/style.css: -------------------------------------------------------------------------------- 1 | img { 2 | vertical-align: middle; 3 | } 4 | 5 | table tr { 6 | border-bottom: 1px solid #ccc; 7 | } 8 | 9 | table tr td{ 10 | text-align: center; 11 | padding:8px; 12 | font-size:0.9em; 13 | font-weight:400; 14 | width:10%; 15 | } 16 | 17 | table tr td img { 18 | margin: 5px 19 | } 20 | 21 | th.FlightTable { 22 | text-align: center; 23 | padding: 5px; 24 | 25 | } 26 | 27 | td.RouteOverview, td.TrackedRoute { 28 | vertical-align: top; 29 | } 30 | 31 | .mapcontainer { 32 | width: 100%; 33 | height: 480px; 34 | background-color: rgb(229, 227, 223); 35 | } 36 | 37 | .MapTitle { 38 | /* width: 49%; */ 39 | text-align: center; 40 | padding: 5px; 41 | white-space: nowrap; 42 | font-weight: 700; 43 | text-transform: capitalize; 44 | color: #666666; 45 | background-color: #99CCFF; 46 | font-family: 'Source Sans Pro', sans-serif; 47 | } 48 | 49 | #route-map, 50 | #track-map { 51 | width: 100%; 52 | height: 480px; 53 | display: inline-block; 54 | 55 | } 56 | 57 | .mapmsg{ 58 | height: 230px; 59 | margin-top: -230px; 60 | font-style: italic; 61 | position: relative; 62 | z-index: 1000; 63 | text-align: center; 64 | } 65 | 66 | #route_map_toolbar { 67 | padding: 0px; 68 | padding-left: 5px; 69 | margin-top: 0px; 70 | margin-bottom: 5px; 71 | border: 1px solid #000; 72 | width: 120px; 73 | height: 20px; 74 | vertical-align: middle; 75 | border-radius: 0px; 76 | overflow: hidden; 77 | background-color: #fff; 78 | background: #fff url("http://maps.gstatic.com/mapfiles/arrow-down.png") no-repeat 90% 50%; 79 | 80 | } 81 | 82 | #track_map_toolbar { 83 | padding: 0px; 84 | padding-left: 5px; 85 | margin-top: 0px; 86 | margin-bottom: 5px; 87 | border: 1px solid #000; 88 | width: 120px; 89 | height: 20px; 90 | vertical-align: middle; 91 | border-radius: 0px; 92 | overflow: hidden; 93 | background-color: #fff; 94 | background: #fff url("http://maps.gstatic.com/mapfiles/arrow-down.png") no-repeat 90% 50%; 95 | 96 | } 97 | 98 | .mapButton { 99 | padding: 0px; 100 | margin: 0; 101 | margin-left: 1px; 102 | margin-bottom: 2px; 103 | width: 120px; 104 | height: 20px; 105 | vertical-align: middle; 106 | border: 1px solid #000; 107 | box-shadow: none; 108 | background-color: transparent; 109 | background-image: none; 110 | -webkit-appearance: none; 111 | -moz-appearance: none; 112 | appearance: none; 113 | } 114 | 115 | select.mapButton::-ms-expand { 116 | display: none; 117 | } 118 | 119 | form select.mapButton{ 120 | padding: 0px 0px; 121 | width: 120px; 122 | margin-bottom: 3px; 123 | height: 20px; 124 | border: none; 125 | box-shadow: none; 126 | background-color: transparent; 127 | background-image: none; 128 | -webkit-appearance: none; 129 | -moz-appearance: none; 130 | appearance: none; 131 | } 132 | 133 | #main_toolbar * { 134 | vertical-align: middle; 135 | height: 20px; 136 | margin: 0px 0; 137 | margin-bottom: 3px; 138 | margin-left: 1px; 139 | outline: 0; 140 | } 141 | 142 | form{ 143 | margin-bottom:20px; 144 | padding:10px; 145 | padding-bottom:30px; 146 | } 147 | 148 | form.charts_form{ 149 | height: 20px; 150 | vertical-align: middle; 151 | padding: 0px; 152 | padding-bottom:0px; 153 | } 154 | 155 | form.limit { 156 | margin-bottom:0px; 157 | padding:0px; 158 | padding-bottom:0px; 159 | display: inline; 160 | } 161 | 162 | .info div{ 163 | display: inline-block; 164 | margin-bottom:2.0em; 165 | font-weight:400; 166 | margin-right:2.0em; 167 | } 168 | 169 | div.limit { 170 | margin-bottom:0px; 171 | padding:0px; 172 | padding-bottom:0px; 173 | display: inline; 174 | float: right; 175 | margin-right:0px; 176 | } 177 | 178 | label.limit { 179 | margin-bottom:0px; 180 | padding:0px; 181 | padding-bottom:0px; 182 | display: inline; 183 | } 184 | 185 | .pagination{ 186 | text-align: center; 187 | width:100%; 188 | } 189 | .pagination a{ 190 | display:inline-block; 191 | margin-right:15px; 192 | background-color: #fff; 193 | border: 1px solid #ccc; 194 | display: inline-block; 195 | margin-bottom: 0; 196 | font-weight: 300; 197 | text-align: center; 198 | vertical-align: middle; 199 | cursor: pointer; 200 | white-space: nowrap; 201 | padding: 6px 12px; 202 | line-height: 1.42857143; 203 | border-radius: 4px; 204 | } 205 | .pagination a:hover{ 206 | color:#fff; 207 | background-color:#737373; 208 | } 209 | select#limit { 210 | width: auto; 211 | } 212 | fieldset { 213 | padding: .35em .625em .75em; 214 | margin: 0 2px; 215 | border: 1px solid silver 216 | } 217 | form label{ 218 | display:inline-block; 219 | width:200px; 220 | margin-bottom:10px; 221 | vertical-align: top; 222 | } 223 | 224 | form input[type="text"], 225 | form textarea, 226 | form select{ 227 | width:350px; 228 | padding:5px; 229 | } 230 | 231 | .info span{ 232 | color:#595959; 233 | font-weight:100; 234 | padding:0px; 235 | margin-right:5px; 236 | text-align: left; 237 | /* font-size:0.8em; */ 238 | } 239 | form fieldset{ 240 | margin-bottom:20px; 241 | } 242 | input, button, select, textarea { 243 | font-family: inherit; 244 | font-size: inherit; 245 | line-height: inherit 246 | } 247 | button, select { 248 | text-transform: none 249 | } 250 | button, input, optgroup, select, textarea { 251 | margin: 0; 252 | font: inherit; 253 | color: inherit 254 | } 255 | -------------------------------------------------------------------------------- /windows/db_query.bat: -------------------------------------------------------------------------------- 1 | set THEDATABASE=C:\progra~2\Kinetic\BaseStation\Basestation.sqb 2 | set THECSVFILE=c:\sqlite\flights.csv 3 | if exist %THECSVFILE% del %THECSVFILE% 4 | :: allow time for the csv file to be deleted 5 | timeout /t 2 /nobreak 6 | c:\sqlite\sqlite3.exe %THEDATABASE% < "c:\sqlite\dbquerycommands.txt" 7 | ::allow time for the csv to be written to file 8 | timeout /t 2 /nobreak 9 | "C:\Program Files (x86)\putty\pscp.exe" -scp %THECSVFILE% @: -------------------------------------------------------------------------------- /windows/db_query.vbs: -------------------------------------------------------------------------------- 1 | Dim WinScriptHost 2 | Set WinScriptHost = CreateObject("WScript.Shell") 3 | WinScriptHost.Run Chr(34) & "C:\sqlite\db_query.bat" & Chr(34), 0, true 4 | WinScriptHost.Run "C:\Progra~2\putty\putty.exe -load flightimport", 0, true 5 | Set WinScriptHost = Nothing 6 | -------------------------------------------------------------------------------- /windows/dbquerycommands.txt: -------------------------------------------------------------------------------- 1 | attach database 'c:\Users\Administrator\AppData\Local\VirtualRadar\StandingData.sqb' as StandingData; 2 | .mode list 3 | .separator "|" 4 | .output 'c:\sqlite\flights.csv' 5 | select Flights.FlightID as 'ID', Aircraft.ModeS as 'ModeS', Aircraft.ModeSCountry as 'Country', Aircraft.Registration as 'Registration', Aircraft.RegisteredOwners as 'Operator', Flights.Callsign as 'Callsign', Aircraft.ICAOTypeCode as 'ModelCode', Aircraft.Type as 'AircraftModel', Aircraft.OperatorFlagCode as 'OperatorCode', strftime('%Y-%m-%d %H:%M:%S',Flights.StartTime) as 'FirstSeen', Flights.FirstLat as 'FirstLatitude', Flights.FirstLon as 'FirstLongitude', Flights.FirstAltitude as 'StartingAltitude', strftime('%Y-%m-%d %H:%M:%S',Flights.EndTime) as 'LastSeen', Flights.LastLat as 'LastLatitude', Flights.LastLon as 'LastLongitude', Flights.LastAltitude as 'EndingAltitude', Flights.NumAirPosMsgRec as 'NumPositionReports', StandingData.RouteView.FromAirportICAO as 'FromICAO', StandingData.RouteView.FromAirportIATA as 'FromIATA', StandingData.RouteView.FromAirportName as 'FromName', StandingData.RouteView.FromAirportLatitude as 'FromLat', StandingData.RouteView.FromAirportLongitude as 'FromLong', StandingData.RouteView.FromAirportLocation as 'FromLocation', StandingData.RouteView.FromAirportCountry as 'FromCountry', StandingData.RouteView.ToAirportICAO as 'ToICAO', StandingData.RouteView.ToAirportIATA as 'ToIATA', StandingData.RouteView.ToAirportName as 'ToName', StandingData.RouteView.ToAirportLatitude as 'ToLat', StandingData.RouteView.ToAirportLongitude as 'ToLong', StandingData.RouteView.ToAirportLocation as 'ToLocation', StandingData.RouteView.ToAirportCountry as 'ToCountry', Aircraft.Interested as 'Interesting' from Aircraft INNER JOIN Flights ON (Aircraft.AircraftID=Flights.AircraftID) LEFT JOIN StandingData.RouteView ON (Flights.Callsign=StandingData.RouteView.Callsign) where Flights.EndTime >= strftime('%Y-%m-%d %H:%M','now','-2 hour','localtime') order by Flights.Endtime desc; 6 | .output stdout 7 | detach database StandingData; --------------------------------------------------------------------------------