diff --git a/htdocs/gfx/adsb-72.png b/htdocs/gfx/adsb-72.png new file mode 100644 index 00000000..ddb8c621 Binary files /dev/null and b/htdocs/gfx/adsb-72.png differ diff --git a/htdocs/lib/GoogleMaps.js b/htdocs/lib/GoogleMaps.js index 92b27dd1..291625e3 100644 --- a/htdocs/lib/GoogleMaps.js +++ b/htdocs/lib/GoogleMaps.js @@ -78,6 +78,25 @@ GAprsMarker.prototype.place = function() { } }; +// +// GoogleMaps-Specific AircraftMarker +// + +function GAircraftMarker() { $.extend(this, new AircraftMarker()); } +GAircraftMarker.prototype = new GMarker(); + +GAircraftMarker.prototype.place = function() { + // Project location and place symbol + var div = this.div; + if (div) { + var point = this.getProjection().fromLatLngToDivPixel(this.position); + if (point) { + div.style.left = point.x - 36 + 'px'; + div.style.top = point.y - 36 + 'px'; + } + } +}; + // // GoogleMaps-Specific SimpleMarker // diff --git a/htdocs/lib/Leaflet.js b/htdocs/lib/Leaflet.js index 589c1272..938f4180 100644 --- a/htdocs/lib/Leaflet.js +++ b/htdocs/lib/Leaflet.js @@ -66,6 +66,9 @@ function LFeatureMarker() { $.extend(this, new LMarker(), new FeatureMarker()); // Leaflet-Specific AprsMarker function LAprsMarker () { $.extend(this, new LMarker(), new AprsMarker()); } +// Leaflet-Specific AircraftMarker +function LAircraftMarker () { $.extend(this, new LMarker(), new AircraftMarker()); } + // // Leaflet-Specific Locator // diff --git a/htdocs/lib/MapMarkers.js b/htdocs/lib/MapMarkers.js index b600aeab..cbe3d79a 100644 --- a/htdocs/lib/MapMarkers.js +++ b/htdocs/lib/MapMarkers.js @@ -390,8 +390,7 @@ FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) { // // APRS Marker -// Represents APRS transmitters, as well as AIS (vessels) -// and HFDL (planes). +// Represents APRS transmitters, as well as AIS (vessels). // Derived classes have to implement: // setMarkerOpacity() // @@ -700,3 +699,198 @@ AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) { + commentString + weatherString + detailsString + messageString + hopsString; }; + +// +// Aircraft Marker +// Represents aircraft reported by various aeronautic services, +// such as HFDL, ACARS, VDL2, and ADSB. +// Derived classes have to implement: +// setMarkerOpacity() +// + +function AircraftMarker() {} + +AircraftMarker.prototype = new Marker(); + +AircraftMarker.prototype.update = function(update) { + this.lastseen = update.lastseen; + this.mode = update.mode; + this.comment = update.location.comment; + // HFDL, ACARS, VDL2, ADSB + this.altitude = update.location.altitude; + this.aircraft = update.location.aircraft; + this.destination = update.location.destination; + this.origin = update.location.origin; + this.flight = update.location.flight; + this.icao = update.location.icao; + this.vspeed = update.location.vspeed; + this.squawk = update.location.squawk; + this.rssi = update.location.rssi; + this.msglog = update.location.msglog; + + // Implementation-dependent function call + this.setMarkerPosition(update.callsign, update.location.lat, update.location.lon); + + // Age locator + this.age(new Date().getTime() - update.lastseen); +}; + +AircraftMarker.prototype.draw = function() { + var div = this.div; + var overlay = this.overlay; + if (!div || !overlay) return; + + if (this.symbol) { + var tableId = this.symbol.table === '/' ? 0 : 1; + div.style.background = 'url(static/gfx/adsb-72.png)'; + div.style['background-size'] = '576px 792px'; + div.style['background-position-x'] = -this.symbol.x * 72 + 'px'; + div.style['background-position-y'] = -this.symbol.y * 72 + 'px'; + } + + // If aircraft is flying at a significant altitude... + if (this.altitude >= 500) { + // r = elevation, a = rotation, = shadow offset + var r = Math.round(this.altitude / 1000); + var a = - Math.PI * (this.course? this.course - 45 : -45) / 180; + var x = r * Math.cos(a); + var y = r * Math.sin(a); + div.style.filter = 'drop-shadow(' + x + 'px ' + y + 'px 0.5px rgba(0,0,0,0.5))'; + } else { + div.style.filter = 'none'; + } + + div.style.transform = 'scale(0.5)'; + + if (this.course) { + div.style.transform += ' rotate(' + this.course + 'deg)'; + } + + if (this.symbol && this.symbol.table !== '/' && this.symbol.table !== '\\') { + overlay.style.display = 'block'; + overlay.style['background-position-x'] = -this.symbol.x * 72 + 'px'; + overlay.style['background-position-y'] = -this.symbol.y * 72 + 'px'; + } else { + overlay.style.display = 'none'; + } + + if (this.opacity) { + div.style.opacity = this.opacity; + } else { + div.style.opacity = null; + } + + if (this.place) this.place(); +}; + +AircraftMarker.prototype.create = function() { + var div = this.div = document.createElement('div'); + + div.style.position = 'absolute'; + div.style.cursor = 'pointer'; + div.style.width = '72px'; + div.style.height = '72px'; + + var overlay = this.overlay = document.createElement('div'); + overlay.style.width = '72px'; + overlay.style.height = '72px'; + overlay.style.background = 'url(static/gfx/adsb-72.png)'; + overlay.style['background-size'] = '576px 792px'; + overlay.style.display = 'none'; + + div.appendChild(overlay); + + return div; +}; + +AircraftMarker.prototype.getAnchorOffset = function() { + return [0, -12]; +}; + +AircraftMarker.prototype.getInfoHTML = function(name, receiverMarker = null) { + var timeString = moment(this.lastseen).fromNow(); + var commentString = ''; + var detailsString = ''; + var messageString = ''; + var distance = ''; + + if (this.comment) { + commentString += '
' + Marker.makeListTitle('Comment') + '
' + + this.comment + '
'; + } + + if (this.msglog) { + messageString += '
' + Marker.makeListTitle('Messages') + + '
' +
+            this.msglog.join('\n
') + '
'; + } + + if (this.icao) { + detailsString += Marker.makeListItem('ICAO', Marker.linkify(this.icao, modes_url)); + } + + if (this.aircraft) { + detailsString += Marker.makeListItem('Aircraft', Marker.linkify(this.aircraft, flight_url)); + } + + if (this.squawk) { + detailsString += Marker.makeListItem('Squawk', this.squawk); + } + + if (this.origin) { + detailsString += Marker.makeListItem('Origin', this.origin); + } + + if (this.destination) { + detailsString += Marker.makeListItem('Destination', this.destination); + } + + // Combine course and speed if both present + if (this.course && this.speed) { + detailsString += Marker.makeListItem('Course', + Marker.degToCompass(this.course) + ' ' + + this.speed.toFixed(1) + ' km/h' + ); + } else { + if (this.course) { + detailsString += Marker.makeListItem('Course', Marker.degToCompass(this.course)); + } + if (this.speed) { + detailsString += Marker.makeListItem('Speed', this.speed.toFixed(1) + ' km/h'); + } + } + + // Combine altitude and vertical speed + if (this.altitude) { + var alt = this.altitude.toFixed(0) + ' m'; + if (this.vspeed > 0) alt += ' ↗' + this.vspeed + ' m/m'; + else if (this.vspeed < 0) alt += ' ↘' + (-this.vspeed) + ' m/m'; + detailsString += Marker.makeListItem('Altitude', alt); + } + + if (this.rssi) { + detailsString += Marker.makeListItem('RSSI', this.rssi + ' dB'); + } + + if (detailsString.length > 0) { + detailsString = '
' + Marker.makeListTitle('Details') + detailsString + '
'; + } + + if (receiverMarker) { + distance = ' at ' + Marker.distanceKm(receiverMarker.position, this.position) + ' km'; + } + + // Linkify title based on what it is (vessel, flight, mode-S code, or else) + var url = modes_url; + if (this.flight) { + name = this.flight; + url = this.flight.match(/^[A-Z]{3}[0-9]+[A-Z]*$/)? flight_url : null; + } else if (this.aircraft) { + name = this.aircraft; + url = flight_url; + } + + return '

' + Marker.linkify(name, url) + distance + '

' + + '
' + timeString + ' using ' + this.mode + '
' + + commentString + detailsString + messageString; +}; diff --git a/htdocs/map-google.js b/htdocs/map-google.js index f0a11419..d8c4a824 100644 --- a/htdocs/map-google.js +++ b/htdocs/map-google.js @@ -160,13 +160,24 @@ MapManager.prototype.processUpdates = function(updates) { case 'latlon': var marker = self.mman.find(update.callsign); var markerClass = GSimpleMarker; - var aprsOptions = {} + var options = {} + + switch(update.mode) { + case 'HFDL': case 'VDL2': case 'ADSB': case 'ACARS': + markerClass = GAircraftMarker; + break; + case 'APRS': case 'AIS': + markerClass = GAprsMarker; + break; + } + + options.color = update.location.color? + update.location.color : self.mman.getColor(update.mode); if (update.location.symbol) { - markerClass = GAprsMarker; - aprsOptions.symbol = update.location.symbol; - aprsOptions.course = update.location.course; - aprsOptions.speed = update.location.speed; + options.symbol = update.location.symbol; + options.course = update.location.course; + options.speed = update.location.speed; } // If new item, create a new marker for it @@ -188,7 +199,7 @@ MapManager.prototype.processUpdates = function(updates) { marker.setMap(self.mman.isEnabled(update.mode)? map : undefined); // Apply marker options - marker.setMarkerOptions(aprsOptions); + marker.setMarkerOptions(options); if (expectedCallsign && expectedCallsign == update.callsign) { map.panTo(marker.position); @@ -206,16 +217,10 @@ MapManager.prototype.processUpdates = function(updates) { var options = {} // If no symbol or color supplied, use defaults by type - if (update.location.symbol) { - options.symbol = update.location.symbol; - } else { - options.symbol = self.mman.getSymbol(update.mode); - } - if (update.location.color) { - options.color = update.location.color; - } else { - options.color = self.mman.getColor(update.mode); - } + options.symbol = update.location.symbol? + update.location.symbol : self.mman.getSymbol(update.mode); + options.color = update.location.color? + update.location.color : self.mman.getColor(update.mode); // If new item, create a new marker for it if (!marker) { diff --git a/owrx/aircraft.py b/owrx/aircraft.py index 30583f61..3d6b88d3 100644 --- a/owrx/aircraft.py +++ b/owrx/aircraft.py @@ -39,30 +39,30 @@ MODE_S_FORMATS = [ # Aircraft categories # ADSB_CATEGORIES = { - "A0": ("^", "/"), # No ADS-B emitter category information - "A1": ("'", "/"), # Light (< 15500 lbs) - "A2": ("'", "/"), # Small (15500 to 75000 lbs) - "A3": ("^", "/"), # Large (75000 to 300000 lbs) - "A4": ("^", "/"), # High vortex large (aircraft such as B-757) - "A5": ("^", "/"), # Heavy (> 300000 lbs) - "A6": ("^", "/"), # High performance (> 5g acceleration and 400 kts) - "A7": ("X", "/"), # Rotorcraft, regardless of weight - "B0": ("^", "/"), # No ADS-B emitter category information - "B1": ("g", "/"), # Glider or sailplane, regardless of weight - "B2": ("O", "/"), # Airship or balloon, regardless of weight - "B3": ("g", "/"), # Parachutist / skydiver - "B4": ("g", "/"), # Ultralight / hang-glider / paraglider - "B5": ("^", "/"), # Reserved - "B6": ("S", "\\"), # Unmanned aerial vehicle, regardless of weight - "B7": ("S", "/"), # Space / trans-atmospheric vehicle - "C0": ("D", "/"), # No ADS-B emitter category information - "C1": ("f", "/"), # Surface vehicle – emergency vehicle - "C2": ("u", "\\"), # Surface vehicle – service vehicle - "C3": ("D", "/"), # Point obstacle (includes tethered balloons) - "C4": ("D", "/"), # Cluster obstacle - "C5": ("D", "/"), # Line obstacle - "C6": ("D", "/"), # Reserved - "C7": ("D", "/"), # Reserved + "A0": (0, 0), # No ADS-B emitter category information + "A1": (3, 0), # Light (< 15500 lbs) + "A2": (5, 0), # Small (15500 to 75000 lbs) + "A3": (4, 0), # Large (75000 to 300000 lbs) + "A4": (0, 0), # High vortex large (aircraft such as B-757) + "A5": (1, 7), # Heavy (> 300000 lbs) + "A6": (7, 0), # High performance (> 5g acceleration and 400 kts) + "A7": (6, 5), # Rotorcraft, regardless of weight + "B0": (0, 0), # No ADS-B emitter category information + "B1": (1, 6), # Glider or sailplane, regardless of weight + "B2": (2, 0), # Airship or balloon, regardless of weight + "B3": (10, 0), # Parachutist / skydiver + "B4": (10, 0), # Ultralight / hang-glider / paraglider + "B5": (0, 0), # Reserved + "B6": (4, 3), # Unmanned aerial vehicle, regardless of weight + "B7": (4, 5), # Space / trans-atmospheric vehicle + "C0": (4, 8), # No ADS-B emitter category information + "C1": (2, 8), # Surface vehicle – emergency vehicle + "C2": (3, 8), # Surface vehicle – service vehicle + "C3": (5, 8), # Point obstacle (includes tethered balloons) + "C4": (6, 9), # Cluster obstacle + "C5": (2, 8), # Line obstacle + "C6": (2, 8), # Reserved + "C7": (2, 8), # Reserved } @@ -83,11 +83,10 @@ class AircraftLocation(LatLngLocation): if "category" in self.data and self.data["category"] in ADSB_CATEGORIES: # Add APRS-like symbol by aircraft category cat = ADSB_CATEGORIES[self.data["category"]] - res["symbol"] = getSymbolData(cat[0], cat[1]) + res["symbol"] = { "x": cat[0], "y": cat[1] } else: # Add APRS-like aircraft symbol (red or blue, depending on mode) - mod = '/' if self.data["mode"]=="ADSB" else '\\' - res["symbol"] = getSymbolData('^', mod) + res["symbol"] = { "x": 0, "y": 0 } # Convert aircraft-specific data into APRS-like data for x in ["icao", "aircraft", "flight", "speed", "altitude", "course", "destination", "origin", "vspeed", "squawk", "rssi", "msglog"]: if x in self.data: