├── adsb.ground.station ├── src │ ├── main │ │ ├── assembly │ │ │ ├── filter.properties │ │ │ └── sources.xml │ │ ├── resources │ │ │ ├── application.properties │ │ │ └── log4j.properties │ │ └── java │ │ │ └── com │ │ │ └── ibm │ │ │ └── iot │ │ │ └── adsb │ │ │ └── ground │ │ │ └── station │ │ │ ├── IotClient.java │ │ │ └── Flight.java │ └── test │ │ └── java │ │ └── com │ │ └── ibm │ │ └── iot │ │ └── adsb │ │ └── ground │ │ └── station │ │ └── AppTest.java ├── images │ └── rpi_sdr_config.jpg ├── Dockerfile ├── pom.xml ├── README-ja.md └── README.md ├── assets ├── arview-weather.png ├── mapview-weather.png ├── architecture_diagram.png └── architecture_diagram_v2.png ├── .travis.yml ├── ARFlightTracker-iOS-Swift ├── close.png ├── compass.png ├── ARFlightTracker-iOS-Swift │ ├── box.png │ ├── plane.png │ ├── radar.png │ ├── bubble.png │ ├── radar_dot.png │ ├── ARKit │ │ ├── ARKit.h │ │ ├── View │ │ │ ├── ARObjectView.h │ │ │ ├── RadarView.h │ │ │ ├── ARObjectView.m │ │ │ └── RadarView.m │ │ ├── LocalizationDelegate.h │ │ ├── ARViewDelegate.h │ │ ├── Model │ │ │ ├── ARGeoCoordinate.h │ │ │ └── ARGeoCoordinate.m │ │ ├── LocalizationHelper.h │ │ ├── ARKitConfig.h │ │ ├── ARKitConfig.m │ │ ├── ARKitEngine.h │ │ └── LocalizationHelper.m │ ├── ARFlightAnnotation.swift │ ├── IBMFlightTracker-Bridging-Header.h │ ├── MarkerView.h │ ├── FlightCalloutView.swift │ ├── ARAnnotation.swift │ ├── Assets.xcassets │ │ ├── AppIcon-2.appiconset │ │ │ └── Contents.json │ │ ├── AppIcon-3.appiconset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── MQTTConnection.swift │ ├── ARFlightAnnotationView.swift │ ├── ARAnnotationView.swift │ ├── FlightAnnotation.swift │ ├── TestFlights.json │ ├── Info.plist │ ├── FlightInfo.swift │ ├── AppDelegate.swift │ ├── MarkerView.m │ ├── RestCall.swift │ ├── ARFlightAnnotationCustom.swift │ ├── ARFlightConfiguration.swift │ ├── FlightBubble.swift │ ├── Base.lproj │ │ └── Main.storyboard │ ├── ARViewHelper.swift │ ├── ViewController.swift │ ├── ARFlightViewController.swift │ ├── FlightCalloutView.xib │ ├── ARFlightViewController_v1.swift │ └── FlightMapViewController.swift ├── ARFlightTracker-iOS-Swift.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── ARFlightTracker-iOS-Swift.xcworkspace │ └── contents.xcworkspacedata ├── FlightBubbleV1.swift ├── Podfile ├── ARFlightTracker-iOS-SwiftUITests │ ├── Info.plist │ └── IBMFlightTrackerUITests.swift ├── ARFlightTracker-iOS-SwiftTests │ ├── Info.plist │ └── IBMFlightTrackerTests.swift ├── LICENSE ├── Assets.xcassets │ └── LaunchImage.launchimage │ │ └── Contents.json ├── .gitignore ├── README-ja.md ├── README.md └── FlightBubbleV1.xib ├── images └── arch-iot-airtrafficcontrol-1024x878.png ├── .gitignore ├── CONTRIBUTING.md ├── pom.xml ├── README-cn.md ├── README-ja.md ├── MAINTAINERS.md ├── README.md └── LICENSE /adsb.ground.station/src/main/assembly/filter.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/arview-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/assets/arview-weather.png -------------------------------------------------------------------------------- /assets/mapview-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/assets/mapview-weather.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: Java 2 | jdk: 3 | - oraclejdk8 4 | install: mvn -Pbootstrap verify 5 | script: mvn verify 6 | 7 | -------------------------------------------------------------------------------- /assets/architecture_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/assets/architecture_diagram.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/close.png -------------------------------------------------------------------------------- /assets/architecture_diagram_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/assets/architecture_diagram_v2.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/compass.png -------------------------------------------------------------------------------- /adsb.ground.station/images/rpi_sdr_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/adsb.ground.station/images/rpi_sdr_config.jpg -------------------------------------------------------------------------------- /images/arch-iot-airtrafficcontrol-1024x878.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/images/arch-iot-airtrafficcontrol-1024x878.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/box.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/plane.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/radar.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/bubble.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/radar_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/air-traffic-control/master/ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/radar_dot.png -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/ARKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARKit.h 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "ARKitEngine.h" 10 | #import "ARObjectView.h" 11 | 12 | -------------------------------------------------------------------------------- /adsb.ground.station/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | ## Mandatory fields 2 | Authentication-Method=apikey 3 | Organization-ID= 4 | API-Key= 5 | Authentication-Token= 6 | ## Device on behalf of which the application needs to publish events. 7 | Device-Type= 8 | Device-ID= 9 | 10 | ## Optional fields 11 | Clean-Session=true 12 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /adsb.ground.station/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=INFO, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n 10 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARFlightAnnotation.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/5/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import MapKit 12 | import CoreLocation 13 | 14 | /// Defines POI with title and location. 15 | open class ARFlightAnnotation 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/View/ARObjectView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARObjectView.h 3 | // Santander 4 | // 5 | // Created by Carlos on 10/11/10. 6 | // Copyright 2010 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ARKitEngine; 12 | 13 | @interface ARObjectView : UIView 14 | 15 | @property (nonatomic, weak) ARKitEngine *controller; 16 | @property (nonatomic) BOOL displayed; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /adsb.ground.station/src/main/assembly/sources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | sources 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | 11 | 12 | src/main/java 13 | / 14 | 15 | **/* 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/LocalizationDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationDelegate.h 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol LocalizationDelegate 13 | 14 | - (void) locationFound:(CLLocation *)location; 15 | - (void) headingFound:(CLHeading *)heading; 16 | - (void) locationUnavailable; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/FlightBubbleV1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightBubbleV1.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 2/15/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | class FlightBubbleV1:UIView { 14 | @IBOutlet weak var distance: UILabel! 15 | @IBOutlet weak var altitude: UILabel! 16 | 17 | @IBOutlet weak var flightImage: UIImageView! 18 | @IBOutlet weak var flightName: UILabel! 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | *~ 15 | target 16 | GTAGS 17 | GRTAGS 18 | GPATH 19 | prop 20 | out 21 | .DS_Store 22 | .idea 23 | *.iml 24 | .project 25 | .classpath 26 | .settings 27 | *.sublime-project 28 | *.sublime-workspace 29 | build-local.properties 30 | .gradle 31 | .checkstyle 32 | dependency-reduced-pom.xml 33 | 34 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/IBMFlightTracker-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // IBMFlightTracker-Bridging-Header.h 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 1/5/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | #import "ARKit.h" 10 | #import "ARKitConfig.h" 11 | #import "ARKitEngine.h" 12 | #import "ARViewDelegate.h" 13 | #import "LocalizationDelegate.h" 14 | #import "LocalizationHelper.h" 15 | #import "ARGeoCoordinate.h" 16 | #import "ARObjectView.h" 17 | #import "RadarView.h" 18 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/ARViewDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARViewDelegate.h 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ARObjectView.h" 11 | #import "ARGeoCoordinate.h" 12 | 13 | @protocol ARViewDelegate 14 | 15 | - (ARObjectView *)viewForCoordinate:(ARGeoCoordinate *)coordinate floorLooking:(BOOL)floorLooking; 16 | - (void) itemTouchedWithIndex:(NSInteger) index; 17 | - (void) didChangeLooking:(BOOL)floorLooking; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#journeys on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/View/RadarView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RadarView.h 3 | // Santander 4 | // 5 | // Created by Carlos on 22/11/10. 6 | // Copyright 2010 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ARGeoCoordinate.h" 11 | 12 | @interface RadarView : UIView { 13 | NSMutableArray *points; 14 | UIView *dotsView; 15 | double farthest; 16 | } 17 | 18 | @property (nonatomic) double farthest; 19 | @property (nonatomic, strong) NSMutableArray *points; 20 | 21 | - (id)initAtPoint:(CGPoint)middlePoint; 22 | 23 | - (void) updatePoints:(ARGeoCoordinate *)centerCoord; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '9.0' 3 | 4 | target 'ARFlightTracker-iOS-Swift' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for IBMFlightTracker 9 | pod 'CocoaMQTT' 10 | pod 'CocoaAsyncSocket' 11 | pod 'SwiftyJSON' 12 | 13 | target 'ARFlightTracker-iOS-SwiftTests' do 14 | inherit! :search_paths 15 | # Pods for testing 16 | end 17 | 18 | target 'ARFlightTracker-iOS-SwiftUITests' do 19 | inherit! :search_paths 20 | # Pods for testing 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/MarkerView.h: -------------------------------------------------------------------------------- 1 | //// 2 | //// MarkerView.h 3 | //// Around Me 4 | //// 5 | //// Created by jdistler on 11.02.13. 6 | //// Copyright (c) 2013 Jean-Pierre Distler. All rights reserved. 7 | //// 8 | // 9 | //#import 10 | // 11 | //@class ARGeoCoordinate; 12 | //@protocol MarkerViewDelegate; 13 | // 14 | //@interface MarkerView : UIView 15 | // 16 | //@property (nonatomic, strong) ARGeoCoordinate *coordinate; 17 | //@property (nonatomic, weak) id delegate; 18 | //- (id)initWithCoordinate:(ARGeoCoordinate *)coordinate delegate:(id)delegate; 19 | // 20 | //@end 21 | // 22 | //@protocol MarkerViewDelegate 23 | // 24 | //- (void)didTouchMarkerView:(MarkerView *)markerView; 25 | // 26 | //@end 27 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-SwiftUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightCalloutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightCalloutView.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/23/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class FlightCalloutView:UIView { 13 | 14 | @IBOutlet weak var flightName: UILabel! 15 | @IBOutlet weak var flightImage: UIImageView! 16 | @IBOutlet weak var altitudeInMeters: UILabel! 17 | @IBOutlet weak var velocityInMetersPerSecond: UILabel! 18 | @IBOutlet weak var currentCity: UILabel! 19 | @IBOutlet weak var weatherIcon: UIImageView! 20 | @IBOutlet weak var currentTemprature: UILabel! 21 | @IBOutlet weak var weatherDescription: UILabel! 22 | public var calloutOpen: Bool = false 23 | 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /adsb.ground.station/src/test/java/com/ibm/iot/adsb/ground/station/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.ibm.iot.adsb.ground.station; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-SwiftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSLocationWhenInUseUsageDescription 6 | App needs location service to start 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARAnnotation.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 3/10/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | 12 | /// Defines POI with title and location. 13 | open class ARAnnotation: NSObject 14 | { 15 | /// Title of annotation 16 | open var title: String? 17 | 18 | ///coordinate 19 | open var coordinate: CLLocationCoordinate2D! 20 | 21 | /// View for annotation. It is set inside ARViewController after fetching view from dataSource. 22 | internal(set) open var annotationView: ARAnnotationView? 23 | 24 | // Internal use only, do not set this properties 25 | internal(set) open var radialDistance: Double = 0 26 | internal(set) open var altitude: Double = 0 27 | internal(set) open var active: Bool = false 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/Model/ARGeoCoordinate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARGeoCoordinate.h 3 | // ARKitDemo 4 | // 5 | // Created by Haseman on 8/1/09. 6 | // Copyright 2009 Zac White. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | #define degreesToRadians(x) (M_PI * x / 180.0) 14 | #define radiansToDegrees(x) (x * (180.0/M_PI)) 15 | 16 | @interface ARGeoCoordinate : NSObject { 17 | 18 | } 19 | 20 | @property (nonatomic, strong) id dataObject; 21 | @property (nonatomic) double radialDistance; 22 | @property (nonatomic) double inclination; 23 | @property (nonatomic) double azimuth; 24 | @property (nonatomic, strong) CLLocation *geoLocation; 25 | 26 | + (ARGeoCoordinate *)coordinateWithLocation:(CLLocation *)location; 27 | - (void)calibrateUsingOrigin:(CLLocation *)origin useAltitude:(BOOL) useAltitude; 28 | - (NSUInteger)hash; 29 | - (BOOL)isEqual:(id)other; 30 | - (BOOL)isEqualToCoordinate:(ARGeoCoordinate *)otherCoordinate; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/Assets.xcassets/AppIcon-2.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "60x60" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "scale" : "1x", 11 | "size" : "76x76" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "scale" : "2x", 16 | "size" : "76x76" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "2x", 21 | "size" : "40x40" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "scale" : "1x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "29x29" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "scale" : "1x", 41 | "size" : "29x29" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "2x", 46 | "size" : "29x29" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/Assets.xcassets/AppIcon-3.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "60x60" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "scale" : "1x", 11 | "size" : "76x76" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "scale" : "2x", 16 | "size" : "76x76" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "2x", 21 | "size" : "40x40" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "scale" : "1x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "29x29" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "scale" : "1x", 41 | "size" : "29x29" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "2x", 46 | "size" : "29x29" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.ibm.iot 6 | air.traffic.control 7 | develop-SNAPSHOT 8 | pom 9 | 10 | Cloud-based Air Traffic Control 11 | https://github.com/ibm/air-traffic-control 12 | 13 | 14 | https://github.com/ibm/air-traffic-control 15 | scm:git:https://github.com/ibm/air-traffic-control.git 16 | 17 | 18 | 19 | UTF-8 20 | 4.12-beta-1 21 | 1.8 22 | 1.8 23 | 1.8+ 24 | 25 | 26 | 27 | adsb.ground.station 28 | 29 | 30 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sanjeev Ghimire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/View/ARObjectView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARObjectView.m 3 | // Santander 4 | // 5 | // Created by Carlos on 10/11/10. 6 | // Copyright 2010 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "ARObjectView.h" 10 | #import "ARKitEngine.h" 11 | 12 | @implementation ARObjectView 13 | 14 | - (id)initWithFrame:(CGRect)frame { 15 | if ((self = [super initWithFrame:frame])) { 16 | self.clipsToBounds = YES; 17 | self.autoresizesSubviews = YES; 18 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 19 | self.userInteractionEnabled = YES; 20 | self.opaque = YES; 21 | _displayed = YES; 22 | } 23 | return self; 24 | } 25 | 26 | - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 27 | } 28 | 29 | - (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 30 | } 31 | 32 | - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 33 | // TODO fix touch on objectviews at half side right 34 | [_controller viewTouched:self]; 35 | } 36 | 37 | - (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/LocalizationHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationHelper.h 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | #import "UIKit/UIKit.h" 9 | #import 10 | #import 11 | #import "LocalizationDelegate.h" 12 | 13 | typedef enum { 14 | kLocalizationUnknown, 15 | kLocalizationDisabled, 16 | kLocalizationEnabled 17 | } kLocalizationStatus; 18 | 19 | @interface LocalizationHelper : NSObject { 20 | CLLocationManager *locationManager; 21 | UIView *loadingView; 22 | 23 | kLocalizationStatus localizationStatus; 24 | BOOL isHeadingInfoAvailable; 25 | 26 | NSMutableArray *onceRegistered; 27 | NSMutableArray *registered; 28 | 29 | id locDelegate; 30 | 31 | CLLocation *lastLocation; 32 | } 33 | 34 | + (LocalizationHelper *) sharedHelper; 35 | - (BOOL) canReceiveHeadingUpdates; 36 | - (void) registerForUpdates:(id)receiver once:(BOOL)once; 37 | - (void) deregisterForUpdates:(id)receiver; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-SwiftTests/IBMFlightTrackerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IBMFlightTrackerTests.swift 3 | // IBMFlightTrackerTests 4 | // 5 | // Created by Sanjeev Ghimire on 11/14/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import IBMFlightTracker 11 | 12 | class IBMFlightTrackerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "ipad", 6 | "minimum-system-version" : "7.0", 7 | "extent" : "full-screen", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "minimum-system-version" : "7.0", 14 | "extent" : "full-screen", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "orientation" : "landscape", 19 | "idiom" : "ipad", 20 | "minimum-system-version" : "7.0", 21 | "extent" : "full-screen", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "orientation" : "portrait", 26 | "idiom" : "iphone", 27 | "minimum-system-version" : "7.0", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "orientation" : "portrait", 32 | "idiom" : "iphone", 33 | "minimum-system-version" : "7.0", 34 | "subtype" : "retina4", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "orientation" : "portrait", 39 | "idiom" : "ipad", 40 | "minimum-system-version" : "7.0", 41 | "extent" : "full-screen", 42 | "scale" : "1x" 43 | } 44 | ], 45 | "info" : { 46 | "version" : 1, 47 | "author" : "xcode" 48 | } 49 | } -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/MQTTConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MQTTUtil.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/17/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CocoaMQTT 11 | 12 | class MQTTConnection{ 13 | 14 | var mqtt: CocoaMQTT? 15 | 16 | //IOT device configuration 17 | public static let API_KEY = "" 18 | public static let API_TOKEN = "" 19 | public static let IOT_CLIENT = "a::Flights" 20 | public static let IOT_HOST = ".messaging.internetofthings.ibmcloud.com" 21 | public static let IOT_PORT = 1883 22 | public static let IOT_TOPIC = "iot-2/type//id//evt//fmt/json" 23 | 24 | func connectToMQTT(_delegate:CocoaMQTTDelegate){ 25 | mqtt = CocoaMQTT(clientID: MQTTConnection.IOT_CLIENT, host: MQTTConnection.IOT_HOST, port: UInt16(MQTTConnection.IOT_PORT)) 26 | if let mqtt = mqtt { 27 | mqtt.username = MQTTConnection.API_KEY 28 | mqtt.password = MQTTConnection.API_TOKEN 29 | mqtt.willMessage = CocoaMQTTWill(topic: MQTTConnection.IOT_TOPIC, message: "dieout") 30 | mqtt.keepAlive = 60 31 | } 32 | 33 | mqtt!.delegate = _delegate 34 | mqtt!.connect() 35 | } 36 | 37 | } 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /adsb.ground.station/Dockerfile: -------------------------------------------------------------------------------- 1 | # ADS-B/SDR image for armhf architecture. This will NOT work on x86 platforms. 2 | # 3 | # Build an image using this Dockerfile as shown below: 4 | # 5 | # $ docker build . -t 6 | # 7 | # Start Dump1090 Server in a container based on the newly created image as 8 | # shown below: 9 | # 10 | # $ docker run -d --privileged -v /dev/bus/usb:/dev/bus/usb -p 30002:30002 11 | # 12 | # Attach to the container to see the ADS-B messages being received from SDR 13 | # as shown below: 14 | # 15 | # $ docker attach 16 | # 17 | # is generated when the image is run. 18 | # 19 | FROM resin/rpi-raspbian:jessie-20160831 20 | 21 | RUN apt-get update && \ 22 | apt-get -qy install curl ca-certificates 23 | RUN apt-get upgrade && \ 24 | apt-get dist-upgrade 25 | RUN apt-get install build-essential 26 | RUN apt-get install apt-utils 27 | RUN apt-get install usbutils 28 | RUN apt-get install pkg-config 29 | RUN apt-get install cmake 30 | RUN apt-get install libusb-1.0-0-dev 31 | RUN apt-get install git-core git 32 | 33 | RUN git clone git://git.osmocom.org/rtl-sdr.git /tmp/rtl-sdr 34 | WORKDIR /tmp/rtl-sdr 35 | RUN cmake ./ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON 36 | RUN make 37 | RUN make install 38 | RUN ldconfig 39 | 40 | RUN git clone https://github.com/MalcolmRobb/dump1090 /tmp/dump1090 41 | WORKDIR /tmp/dump1090 42 | RUN make 43 | 44 | EXPOSE 30002 45 | CMD ["./dump1090", "--raw", "--net"] 46 | 47 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-SwiftUITests/IBMFlightTrackerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IBMFlightTrackerUITests.swift 3 | // IBMFlightTrackerUITests 4 | // 5 | // Created by Sanjeev Ghimire on 11/14/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class IBMFlightTrackerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightAnnotationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARFlightAnnotationView.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/5/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class ARFlightAnnotationView: UIView 13 | { 14 | open weak var annotation: FlightAnnotation? 15 | fileprivate var initialized: Bool = false 16 | 17 | public init() 18 | { 19 | super.init(frame: CGRect.zero) 20 | self.initializeInternal() 21 | } 22 | 23 | public required init?(coder aDecoder: NSCoder) 24 | { 25 | super.init(coder: aDecoder) 26 | self.initializeInternal() 27 | } 28 | 29 | override init(frame: CGRect) 30 | { 31 | super.init(frame: frame) 32 | self.initializeInternal() 33 | } 34 | 35 | fileprivate func initializeInternal() 36 | { 37 | if self.initialized 38 | { 39 | return 40 | } 41 | self.initialized = true; 42 | self.initialize() 43 | } 44 | 45 | open override func awakeFromNib() 46 | { 47 | self.bindUi() 48 | } 49 | 50 | /// Will always be called once, no need to call super 51 | open func initialize() 52 | { 53 | 54 | } 55 | 56 | /// Called when distance/azimuth changes, intended to be used in subclasses 57 | open func bindUi() 58 | { 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARAnnotationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARAnnotationView.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 3/10/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// View for annotation. Subclass to customize. Annotation views should be lightweight, 12 | /// try to avoid xibs and autolayout. 13 | open class ARAnnotationView: UIView 14 | { 15 | open weak var annotation: ARAnnotation? 16 | fileprivate var initialized: Bool = false 17 | 18 | public init() 19 | { 20 | super.init(frame: CGRect.zero) 21 | self.initializeInternal() 22 | } 23 | 24 | public required init?(coder aDecoder: NSCoder) 25 | { 26 | super.init(coder: aDecoder) 27 | self.initializeInternal() 28 | } 29 | 30 | override init(frame: CGRect) 31 | { 32 | super.init(frame: frame) 33 | self.initializeInternal() 34 | } 35 | 36 | fileprivate func initializeInternal() 37 | { 38 | if self.initialized 39 | { 40 | return 41 | } 42 | self.initialized = true; 43 | self.initialize() 44 | } 45 | 46 | open override func awakeFromNib() 47 | { 48 | self.bindUi() 49 | } 50 | 51 | /// Will always be called once, no need to call super 52 | open func initialize() 53 | { 54 | 55 | } 56 | 57 | /// Called when distance/azimuth changes, intended to be used in subclasses 58 | open func bindUi() 59 | { 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightAnnotation.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/15/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | import Foundation 9 | import MapKit 10 | import UIKit 11 | 12 | open class FlightAnnotation: NSObject, MKAnnotation { 13 | 14 | public dynamic var coordinate: CLLocationCoordinate2D 15 | public var title: String? 16 | public var image: UIImage! 17 | public var speed: Double! 18 | public var altitude: Double! 19 | 20 | // use on map view 21 | var calloutView: FlightCalloutView! 22 | var calloutOpen: Bool = false 23 | // 24 | public var heading: Double! 25 | public var velocity: Double! 26 | public var callSign: String! 27 | public var createdInMillis: Int64! 28 | public var lastUpdatedInMillis:Int64! 29 | 30 | // to run in test mode. determines what value to show 31 | public var testFlight: Bool = false 32 | public var realAzimuth:Double! = 0.0 33 | 34 | /// View for annotation. It is set inside ARFlightViewController after fetching view from dataSource. 35 | internal(set) open var annotationView: ARFlightAnnotationView? 36 | 37 | // Internal use only, do not set this properties 38 | internal(set) open var distanceFromUser: Double = 0 39 | internal(set) open var azimuth: Double = 0 40 | internal(set) open var verticalLevel: Double = 0 41 | internal(set) open var active: Bool = false 42 | 43 | internal(set) open var annotationExpiryTimer: Timer? 44 | 45 | init(coordinate: CLLocationCoordinate2D) { 46 | self.coordinate=coordinate 47 | super.init() 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/ARKitConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARKitConfig.h 3 | // ARKit Example 4 | // 5 | // Created by Carlos on 21/10/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "ARViewDelegate.h" 11 | 12 | @interface ARKitConfig : NSObject 13 | 14 | // Do we need to show different images when floor looking? 15 | @property (nonatomic) BOOL showsFloorImages; 16 | 17 | // Does the views need to be scaled based on distance? 18 | @property (nonatomic) BOOL scaleViewsBasedOnDistance; 19 | 20 | // If so, which is the minimum scale factor? 21 | @property (nonatomic) CGFloat minimumScaleFactor; 22 | 23 | // Does the views need to be rotated based on perspective? 24 | @property (nonatomic) BOOL rotateViewsBasedOnPerspective; 25 | 26 | // If so, which is the maximum rotation angle? 27 | @property (nonatomic) CGFloat maximumRotationAngle; 28 | 29 | // Which is the rendering update frequency? 30 | @property (nonatomic) CGFloat updateFrequency; 31 | 32 | // Do you want the engine to consider geopoints altitude? 33 | @property (nonatomic) BOOL useAltitude; 34 | 35 | // Do you want to use the debug mode? 36 | @property (nonatomic) BOOL debugMode; 37 | 38 | // In which orientation will the rendering be made? 39 | @property (nonatomic) UIInterfaceOrientation orientation; 40 | 41 | // The delegate to which notify touches and so 42 | @property (nonatomic, strong) id delegate; 43 | 44 | // Where is the point where you want to place the radar view? 45 | @property (nonatomic) CGPoint radarPoint; 46 | 47 | // Do you want to use a custom loading view or the default one? (nil = default) 48 | @property (nonatomic, strong) UIView *loadingView; 49 | 50 | 51 | 52 | + (ARKitConfig *) defaultConfigFor:(id) delegate; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode artifacts 2 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata/ 18 | 19 | ## Other 20 | *.moved-aside 21 | *.xcuserstate 22 | 23 | ## Obj-C/Swift specific 24 | *.hmap 25 | *.ipa 26 | *.dSYM.zip 27 | *.dSYM 28 | 29 | ## Playgrounds 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | Packages/ 37 | .build/ 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | 66 | #node modules 67 | node_modules/ 68 | 69 | Podfile.lock 70 | 71 | .DS_Store 72 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/ARKitConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARKitConfig.m 3 | // ARKit Example 4 | // 5 | // Created by Carlos on 21/10/13. 6 | // 7 | // 8 | 9 | #import "ARKitConfig.h" 10 | 11 | static const BOOL DEFAULT_SHOWS_FLOOR_IMAGES_CONFIG = YES; 12 | static const BOOL DEFAULT_SCALE_VIEWS_BASED_ON_DISTANCE_CONFIG = YES; 13 | static const CGFloat DEFAULT_MINIMUM_SCALE_FACTOR_CONFIG = 0.5; 14 | static const BOOL DEFAULT_ROTATE_VIEWS_BASED_ON_PERSPECTIVE_CONFIG = YES; 15 | static const CGFloat DEFAULT_MAXIMUM_ROTATION_ANGLE_CONFIG = M_PI / 6.0; 16 | static const CGFloat DEFAULT_UPDATE_FREQUENCY_CONFIG = 1.0 / 20.0; 17 | static const BOOL DEFAULT_USE_ALTITUDE_CONFIG = NO; 18 | static const BOOL DEFAULT_DEBUG_MODE_CONFIG = NO; 19 | static const UIInterfaceOrientation DEFAULT_ORIENTATION_CONFIG = UIInterfaceOrientationPortrait; 20 | 21 | @implementation ARKitConfig 22 | 23 | + (ARKitConfig *) defaultConfigFor:(id) delegate { 24 | 25 | ARKitConfig *config = [[ARKitConfig alloc] init]; 26 | config.showsFloorImages = DEFAULT_SHOWS_FLOOR_IMAGES_CONFIG; 27 | config.scaleViewsBasedOnDistance = DEFAULT_SCALE_VIEWS_BASED_ON_DISTANCE_CONFIG; 28 | config.minimumScaleFactor = DEFAULT_MINIMUM_SCALE_FACTOR_CONFIG; 29 | config.rotateViewsBasedOnPerspective = DEFAULT_ROTATE_VIEWS_BASED_ON_PERSPECTIVE_CONFIG; 30 | config.maximumRotationAngle = DEFAULT_MAXIMUM_ROTATION_ANGLE_CONFIG; 31 | config.updateFrequency = DEFAULT_UPDATE_FREQUENCY_CONFIG; 32 | config.useAltitude = DEFAULT_USE_ALTITUDE_CONFIG; 33 | config.debugMode = DEFAULT_DEBUG_MODE_CONFIG; 34 | config.orientation = DEFAULT_ORIENTATION_CONFIG; 35 | config.delegate = delegate; 36 | config.radarPoint = CGPointZero; 37 | config.loadingView = nil; 38 | 39 | return config; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/View/RadarView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RadarView.m 3 | // Santander 4 | // 5 | // Created by Carlos on 22/11/10. 6 | // Copyright 2010 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "RadarView.h" 10 | 11 | 12 | @implementation RadarView 13 | 14 | @synthesize points, farthest; 15 | 16 | - (id)initAtPoint:(CGPoint)middlePoint { 17 | if ((self = [super initWithFrame:CGRectZero])) { 18 | // Initialization code 19 | UIImage *radarImg = [UIImage imageNamed:@"radar.png"]; 20 | UIImageView *background = [[UIImageView alloc] initWithImage: radarImg]; 21 | [self addSubview:background]; 22 | 23 | self.frame = CGRectMake(middlePoint.x - radarImg.size.width / 2, middlePoint.y - radarImg.size.height / 2, radarImg.size.width, radarImg.size.height); 24 | 25 | dotsView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; 26 | [self addSubview:dotsView]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void) updatePoints:(ARGeoCoordinate *)centerCoord { 32 | double centerAzimuth = centerCoord.azimuth + M_PI / 2; 33 | if (centerAzimuth < 0.0) { 34 | centerAzimuth = 2 * M_PI + centerAzimuth; 35 | } 36 | 37 | UIImage *dot = [UIImage imageNamed:@"radar_dot.png"]; 38 | 39 | for (UIView *sub in dotsView.subviews) { 40 | [sub removeFromSuperview]; 41 | } 42 | 43 | for (ARGeoCoordinate *coord in points) { 44 | double coordAzimuth = coord.azimuth - centerAzimuth; 45 | if (coordAzimuth < 0.0) { 46 | coordAzimuth = 2 * M_PI + coordAzimuth; 47 | } 48 | CGPoint pt; 49 | 50 | pt.x = dotsView.center.x + cos(coordAzimuth) * coord.radialDistance * self.frame.size.width / (2 * farthest); 51 | pt.y = dotsView.center.y + sin(coordAzimuth) * coord.radialDistance * self.frame.size.height / (2 * farthest); 52 | 53 | UIImageView *point = [[UIImageView alloc] initWithImage:dot]; 54 | point.center = pt; 55 | 56 | [dotsView addSubview:point]; 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/TestFlights.json: -------------------------------------------------------------------------------- 1 | { 2 | "flights": [ 3 | { 4 | "icao": "LinkedIn office", 5 | "altitudeInMeters": 117, 6 | "callSign": "CPZ6051", 7 | "headingInDegrees": 132.56732841750284, 8 | "latitude": 37.786793, 9 | "longitude": -122.398263, 10 | "sensorMacAddress": "B8-27-EB-12-A5-E5", 11 | "type": "ar_flights_schema", 12 | "velocityInMetersPerSecond": 222.82611999540126, 13 | "createdInMillis": 1481673865141, 14 | "lastUpdatedInMillis": 1481674254888 15 | }, 16 | { 17 | "icao": "Millenium Tower,SF", 18 | "altitudeInMeters": 198, 19 | "callSign": "CPZ6051", 20 | "headingInDegrees": 132.56732841750284, 21 | "latitude": 37.790457, 22 | "longitude": -122.396696, 23 | "sensorMacAddress": "B8-27-EB-12-A5-E5", 24 | "type": "ar_flights_schema", 25 | "velocityInMetersPerSecond": 222.82611999540126, 26 | "createdInMillis": 1481673865141, 27 | "lastUpdatedInMillis": 1481674254888 28 | }, 29 | { 30 | "icao": "Transmerica Pyramid", 31 | "altitudeInMeters": 265, 32 | "callSign": "CPZ6051", 33 | "headingInDegrees": 132.56732841750284, 34 | "latitude": 37.795110, 35 | "longitude": -122.403260, 36 | "sensorMacAddress": "B8-27-EB-12-A5-E5", 37 | "type": "ar_flights_schema", 38 | "velocityInMetersPerSecond": 222.82611999540126, 39 | "createdInMillis": 1481673865141, 40 | "lastUpdatedInMillis": 1481674254888 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | *阅读本文的其他语言版本:[English](README.md)。* 2 | # 空中交通管制 3 | [![构建状态](https://travis-ci.org/IBM/air-traffic-control.svg?branch=master)](https://travis-ci.org/IBM/air-traffic-control) 4 | 5 | 这个存储库包含使用 IBM Cloud 构建基于云计算的现代空中交通管制系统的操作说明。 6 | 7 | Air Traffic Control 服务通过软件定义无线电 (SDR) 技术从 Raspberry Pi 支持的 ADS-B 地面接收站接收航班信息,以便直接从商业航班接收 ADS-B 消息,并将 MQTT 消息发布到正在 IBM Cloud 中运行的 IBM IoT Platform。它还包括一个基于 Swift 的 iOS 应用程序,该应用程序通过从 IoT Platform 接收 MQTT 消息,使用增强现实工具包来跟踪航班。该应用程序将显示接收器接收范围内的各个机场之间飞行的所有航班。 8 | 9 | 借助航空电子领域中的进步和容易获得的 Raspberry Pi (RPi) 等廉价计算资源,可以轻松地构建一个最先进的地面接收站。可以使用 Docker 等虚拟化技术轻松复制这些地面接收站,以覆盖大片地区。由 RPi 提供支持的地面接收站分散在全球各地,它们将实现以下功能: 10 | * 使用一个带天线的 SDR 接收器接收约 100-150 英里半径范围内的航班信息,具体范围取决于海拔高度和视野。 11 | * 充当联网的 IoT 设备,以消息队列遥测传输 (MQTT) 消息格式将航班信息发送到基于云的空中交通管制系统,该系统在一个可扩展、安全、可靠且开放的云基础架构中运行。 12 | 13 | 这个基于云的空中交通管制系统可使用 IBM 的 Cloud Platform As A Service (PaaS) 实现,后者采用 IBM 的 Open Cloud Architecture ,基于 CloudFoundry 开放技术和 SoftLayer 基础架构实现。因为将地面接收站建模为 IoT 设备,而且它们是联网设备,并以 MQTT 消息格式发送航班信息,所以使用 IBM Cloud 中的 Internet of Things (IoT) Platform 服务是合理的做法,因为该服务不仅能随着地面接收站数量增长而弹性扩展,还能作为汇集点来接收所有事件,以便可以使用航班数据创建分析应用程序、可视化仪表板等。 14 | 15 | IoT Platform 服务还能向所有与之相连的 iOS 设备提供航班信息。在飞机出现在用户上空之前,在 iOS 设备上运行的基于 Swift 的移动应用程序可以使用增强现实技术在屏幕上呈现飞往该方向的航班! 16 | 17 | ## 架构 18 | 下图显示了一个基于云计算的空中交通管制系统的总体架构,该系统依靠低廉的地面接收站来跟踪航班。 19 | 20 | ![alt 标记](https://github.com/IBM/air-traffic-control/blob/master/assets/architecture_diagram_v2.png) 21 | 22 | ## Application Workflow 23 | ![Application Workflow](./images/arch-iot-airtrafficcontrol-1024x878.png) 24 | 25 | 1. Raspberry Pi 将流量数据传输到物联网平台。 26 | 2. MQTT 将数据传递到物联网分析仪表板中进行分析。 27 | 3. 从天气服务 API 中获得的当前天气情况。 28 | 4. 向手机设备发送分析结果和天气数据。 29 | 30 | ## Raspberry Pi 支持的 ADS-B 地面接收站 31 | 32 | [这里](https://github.com/IBM/air-traffic-control/blob/master/adsb.ground.station/README.md) 提供了构建受 Raspberry Pi 支持的地面接收站的操作说明。 33 | 34 | ## 基于 Swift 的 iOS 应用程序 35 | 36 | [这里](https://github.com/IBM/air-traffic-control/blob/master/ARFlightTracker-iOS-Swift/README.md) 提供了使用基于 Swift 的 iOS 应用程序跟踪航班的操作说明。 37 | 38 | # 许可 39 | 40 | [Apache 2.0](LICENSE.md) 41 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ARFlightTracker-iOS-Swift 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | NSCameraUsageDescription 30 | Camera is required for this app 31 | NSLocationAlwaysUsageDescription 32 | Location is required for this app 33 | NSLocationWhenInUseUsageDescription 34 | Location is required for this app 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UIRequiredDeviceCapabilities 40 | 41 | armv7 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightInfo.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/15/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | import SwiftyJSON 11 | 12 | struct FlightInfo { 13 | 14 | let icao: String? 15 | let altitudeInMeters:Double 16 | let callSign: String 17 | let headingInDegrees: Double 18 | let latitude : Double 19 | let groundStationId : String 20 | let velocityInMetersPerSecond : Double 21 | let type: String 22 | let createdInMillis: Int64 23 | let lastUpdatedInMillis:Int64 24 | //let realAzimuth:Double! 25 | 26 | } 27 | 28 | 29 | extension FlightInfo{ 30 | init?(json: JSON) { 31 | guard let icao = json["icao"].string, 32 | let altitudeInMeters = json["altitudeInMeters"].double, 33 | let callSign = json["callSign"].string, 34 | let headingInDegrees = json["headingInDegrees"].double, 35 | let latitude = json["latitude"].double, 36 | let longitude = json["longitude"].double, 37 | let groundStationId = json["groundStationId"].string, 38 | let velocityInMetersPerSecond = json["velocityInMetersPerSecond"].double, 39 | let type = json["type"].string, 40 | let createdInMillis = json["createdInMillis"].int64, 41 | let lastUpdatedInMillis = json["lastUpdatedInMillis"].int64//, 42 | //let realAzimuth = json["realAzimuth"].double 43 | else { 44 | return nil 45 | } 46 | 47 | self.icao = icao; 48 | self.altitudeInMeters=altitudeInMeters; 49 | self.callSign=callSign; 50 | self.headingInDegrees=headingInDegrees; 51 | self.latitude=latitude; 52 | self.longitude=longitude; 53 | self.groundStationId=groundStationId; 54 | self.velocityInMetersPerSecond=velocityInMetersPerSecond; 55 | self.type=type; 56 | self.createdInMillis=createdInMillis; 57 | self.lastUpdatedInMillis=lastUpdatedInMillis; 58 | //self.realAzimuth=realAzimuth; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/14/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/README-ja.md: -------------------------------------------------------------------------------- 1 | *他の言語で読む: [English](README.md).* 2 | 3 | # ARFlightTracker 4 | 5 | ARFlightTracker は、SDR/ADSB メッセージ受信局が MQTT サーバーを介してプッシュしたフライト情報を追跡するiOSベースのアプリです。 6 | アプリは、レシーバーの範囲内のポイントからポイントへ移動するすべてのフライトを表示します。 7 | ARFlightTracker アプリケーションは、IBM MQTT サーバーに接続し、新しい/更新されたフライト情報をトピックとして受け取り、それを地図ビューにレンダリングします。 8 | データは、SDR/ADSBメッセージレシーバによってトピックに供給されます。 9 | また地図上では、それぞれの目的地へのフライトが、行先の方向がわかるアニメーションとして表示されます。 10 | フライトの詳細ビューには、そのフライトの現在位置の天気に関するフライト情報が含まれています。 11 | 12 | ## 地図 (Map) ビュー 13 | 14 | 地図ビューでは、iOS デバイスに用意されている標準マップ上にすべてのフライトが表示されます。 15 | フライト方向は、ペイロード内の現在の heading 情報に基づいて調整されます 16 | アプリが MQTT メッセージを受信すると、そのフライト情報が目的地に向かって移動するのが見られます。 17 | 各フライトをタップすると、飛行番号、高度、距離などの詳細情報が表示されます。 18 | 下の図は、iOS デバイス上の Swift ベースのアプリケーションにおける、フライトを表示した地図ビューのレンダリングを示しています: 19 | 20 | ![alt tag](../assets/mapview-weather.png) 21 | 22 | ## AR (Augmented Reality) ビュー 23 | 24 | ユーザーは、アプリの `AR View` タブをタップして、AR ベースのビューに切り替えることができます。 25 | このモードでは、アプリはカメラビューになり、利用者が表示されたフライトをポイントすることで、フライトの詳細情報が実際の風景の上にオーバレイで表示されます。 26 | 現実世界で飛行機は動いていますので、それに連動して情報を含む吹き出しがカメラビューのフライトと一緒に移動します。 27 | AR ビューには、デバイスの向きに基づくコンパスと、視野角内のすべてのフライトを表示するレーダービューも表示されます。 28 | 下の図は、iOS デバイス上の Swift ベースのアプリケーションにおける、フライトを表示した Augmented Reality ビューのレンダリングを示しています: 29 | 30 | ![alt tag](../assets/arview-weather.png) 31 | 32 | # 前提条件 33 | 34 | - Swift 3 35 | - Xcode 8.0+ 36 | - CocoaPod - https://cocoapods.org/ 37 | - iOS 10+ 38 | 39 | 40 | # 依存する要素 41 | 42 | - CocoaMQTT - ノート: IBM の aphid client から移行 43 | - SwiftyJSON 44 | - ios-arkit for iphone - (コードベースの一部) 45 | 46 | # 手順: 47 | 48 | 1. ARFlightTracker-iOS-Swift ディレクトリに移動し、ARFlightTracker-iOS-Swift.xcworkspace を Xcode で開きます。 49 | 2. プロジェクトディレクトリから `pod install` を実行します。 これは `Podfile` に定義された依存関係をインストールします。 50 | 3. Xcodeエディタを使用して `util/MQTTConnection.swift` を更新します。資格情報を取得するには、IBM Cloud (Bluemix) コンソールで Internet of Things サービスを作成する必要があります。資格情報は次のようになります: 51 | ``` 52 | API_KEY = "" 53 | API_TOKEN = "" 54 | IOT_CLIENT = "a::Flights" 55 | IOT_HOST = ".messaging.internetofthings.ibmcloud.com" 56 | IOT_PORT = 1883 (DEFAULT) 57 | IOT_TOPIC = "iot-2/type//id//evt/flight/fmt/json" 58 | ``` 59 | 4. IBM Weather API の資格情報で `util/RestCall.swift` を更新します。IBM Cloud コンソールを使用して Weather API サービスを作成します: 60 | ``` 61 | private static let WEATHER_API_USERNAME : String = "" 62 | private static let WEATHER_API_PASSWORD : String = "" 63 | ``` 64 | 5. ビルドして実行します。 65 | 66 | # テストモード: 67 | 68 | テストモードでアプリを実行すると、IBM Cloud MQTTサーバーとは独立した状態になります。ViewController で、フラグを設定できます: 69 | ``` 70 | flightTestMode = true 71 | ``` 72 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/ARKitEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARKitEngine.h 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "LocalizationDelegate.h" 12 | #import "ARViewDelegate.h" 13 | #import "ARGeoCoordinate.h" 14 | #import "RadarView.h" 15 | #import "ARKitConfig.h" 16 | 17 | @class ARObjectView; 18 | 19 | typedef struct { 20 | CGFloat xOffset; 21 | CGFloat rotationAngle; 22 | UIInterfaceOrientation orientation; 23 | CGSize viewSize; 24 | } ARKitOrientationSupport; 25 | 26 | typedef enum { 27 | kFrontLookingType, 28 | kFloorLookingType 29 | } ARKitLookingType; 30 | 31 | @interface ARKitEngine : NSObject { 32 | 33 | @private 34 | NSMutableArray *ar_coordinates; 35 | NSMutableArray *ar_coordinateViews; 36 | NSMutableArray *ar_floorCoordinateViews; 37 | 38 | id delegate; 39 | 40 | UIImagePickerController *cameraController; 41 | 42 | RadarView *radar; 43 | UIView *ar_overlayView; 44 | UILabel *ar_debugView; 45 | 46 | NSTimer *_updateTimer; 47 | 48 | double maximumScaleDistance; 49 | 50 | BOOL showsFloorImages; 51 | BOOL scaleViewsBasedOnDistance; 52 | CGFloat minimumScaleFactor; 53 | BOOL rotateViewsBasedOnPerspective; 54 | CGFloat maximumRotationAngle; 55 | CGFloat updateFrequency; 56 | BOOL debugMode; 57 | BOOL useAltitude; 58 | ARKitLookingType lookingType; 59 | 60 | ARGeoCoordinate *centerCoordinate; 61 | 62 | CMMotionManager *motionManager; 63 | 64 | UIViewController *baseViewController; 65 | 66 | ARKitOrientationSupport orientationSupporter; 67 | 68 | UIView *loadingView; 69 | } 70 | 71 | - (id) initWithConfig:(ARKitConfig *) conf; 72 | 73 | - (void) addCoordinate:(ARGeoCoordinate *)coordinate; 74 | - (void) addCoordinates:(NSArray *)newCoordinates; 75 | - (void) removeCoordinate:(ARGeoCoordinate *)coordinate; 76 | - (void) removeCoordinates:(NSArray *)coordinates; 77 | - (void) removeAllCoordinates; 78 | 79 | - (void) addExtraView:(UIView *)extra; 80 | 81 | - (void) viewTouched:(ARObjectView *) view; 82 | 83 | - (void) startListening; 84 | - (void) hide; 85 | 86 | - (id) dataObjectWithIndex:(NSInteger)index; 87 | - (ARObjectView *) frontViewWithIndex:(NSInteger)index; 88 | - (ARObjectView *) floorViewWithIndex:(NSInteger)index; 89 | 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/MarkerView.m: -------------------------------------------------------------------------------- 1 | //// 2 | //// MarkerView.m 3 | //// Around Me 4 | //// 5 | //// Created by jdistler on 11.02.13. 6 | //// Copyright (c) 2013 Jean-Pierre Distler. All rights reserved. 7 | //// 8 | // 9 | //#import "MarkerView.h" 10 | // 11 | //#import "ARGeoCoordinate.h" 12 | // 13 | //const float kWidth = 200.0f; 14 | //const float kHeight = 100.0f; 15 | // 16 | //@interface MarkerView () 17 | // 18 | //@property (nonatomic, strong) UILabel *lblDistance; 19 | // 20 | //@end 21 | // 22 | // 23 | //@implementation MarkerView 24 | // 25 | //- (id)initWithFrame:(CGRect)frame 26 | //{ 27 | // self = [super initWithFrame:frame]; 28 | // if (self) { 29 | // // Initialization code 30 | // } 31 | // return self; 32 | //} 33 | // 34 | //- (id)initWithCoordinate:(ARGeoCoordinate *)coordinate delegate:(id)delegate { 35 | // if((self = [super initWithFrame:CGRectMake(0.0f, 0.0f, kWidth, kHeight)])) { 36 | // _coordinate = coordinate; 37 | // _delegate = delegate; 38 | // 39 | // [self setUserInteractionEnabled:YES]; 40 | // 41 | // UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, kWidth, 40.0f)]; 42 | // [title setBackgroundColor:[UIColor colorWithWhite:0.3f alpha:0.7f]]; 43 | // [title setTextColor:[UIColor whiteColor]]; 44 | // [title setTextAlignment:NSTextAlignmentCenter]; 45 | // [title setText:[coordinate title]]; 46 | // [title sizeToFit]; 47 | // 48 | // _lblDistance = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 45.0f, kWidth, 40.0f)]; 49 | // 50 | // [_lblDistance setBackgroundColor:[UIColor colorWithWhite:0.3f alpha:0.7f]]; 51 | // [_lblDistance setTextColor:[UIColor whiteColor]]; 52 | // [_lblDistance setTextAlignment:NSTextAlignmentCenter]; 53 | // [_lblDistance setText:[NSString stringWithFormat:@"%.2f km", [coordinate distanceFromOrigin] / 1000.0f]]; 54 | // [_lblDistance sizeToFit]; 55 | // 56 | // [self addSubview:title]; 57 | // [self addSubview:_lblDistance]; 58 | // 59 | // [self setBackgroundColor:[UIColor clearColor]]; 60 | // } 61 | // 62 | // return self; 63 | //} 64 | // 65 | //- (void)drawRect:(CGRect)rect { 66 | // [super drawRect:rect]; 67 | // [[self lblDistance] setText:[NSString stringWithFormat:@"%.2f km", [[self coordinate] distanceFromOrigin] / 1000.0f]]; 68 | //} 69 | // 70 | //- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 71 | // if(_delegate && [_delegate conformsToProtocol:@protocol(MarkerViewDelegate)]) { 72 | // [_delegate didTouchMarkerView:self]; 73 | // } 74 | //} 75 | // 76 | //- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 77 | // 78 | // CGRect theFrame = CGRectMake(0, 0, kWidth, kHeight); 79 | // 80 | // return CGRectContainsPoint(theFrame, point); 81 | //} 82 | // 83 | //@end 84 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/RestCall.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RestCall.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 3/14/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | 13 | class RestCall { 14 | 15 | 16 | private static let WEATHER_API_USERNAME : String = "" 17 | private static let WEATHER_API_PASSWORD : String = "" 18 | 19 | 20 | func fetchWeatherBasedOnCurrentLocation(latitude: String, longitude: String, completion: @escaping (_ result: [String: Any]) -> Void){ 21 | 22 | var weatherData : [String : Any] = [:] 23 | 24 | let url:String = self.getURL(latitude: latitude, longitude: longitude) 25 | 26 | guard let endpointURL = URL(string: url) else { 27 | print("Error: cannot create URL") 28 | return 29 | } 30 | 31 | let urlRequest = URLRequest(url: endpointURL) 32 | 33 | let session = URLSession.shared 34 | 35 | let task = session.dataTask(with: urlRequest) { 36 | (data, response, error) in 37 | // check for any errors 38 | guard error == nil else { 39 | print("error calling weather api") 40 | print(error!) 41 | return 42 | } 43 | // make sure we got data 44 | guard let responseData = data else { 45 | print("Error: did not receive data") 46 | return 47 | } 48 | 49 | 50 | let weatherJson = JSON(data: responseData) 51 | 52 | let observation : [String : Any] = weatherJson["observation"].dictionaryObject! 53 | 54 | weatherData["city"] = observation["obs_name"] 55 | weatherData["temperature"] = observation["temp"] 56 | weatherData["description"] = observation["wx_phrase"] 57 | 58 | let weatherIconUrl:String = "http://weather-company-data-demo.mybluemix.net/images/weathericons/icon\(observation["wx_icon"]!).png" 59 | 60 | weatherData["weatherIconUrl"] = weatherIconUrl 61 | 62 | completion(weatherData) 63 | } 64 | task.resume() 65 | } 66 | 67 | 68 | 69 | func getURL(latitude: String, longitude: String) -> String{ 70 | let url: String = "https://\(RestCall.WEATHER_API_USERNAME):\(RestCall.WEATHER_API_PASSWORD)@twcservice.mybluemix.net/api/weather/v1/geocode/\(latitude)/\(longitude)/observations.json?units=e&language=en-US" 71 | 72 | return url 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | *他の言語で読む: [English](README.md), [中国語](README-cn.md).* 2 | 3 | # air-traffic-control (航空交通管制) 4 | [![Build Status](https://travis-ci.org/IBM/air-traffic-control.svg?branch=master)](https://travis-ci.org/IBM/air-traffic-control) 5 | 6 | このレポジトリには、IBM Cloud を使用して最新の Cloud ベースの航空交通管制を構築するための手順が含まれています。 7 | 8 | > ノート: このコードパターンを完全に完成させるには、ラズベリーパイ、アンテナ付き SDR レシーバー、iOS デバイスが必要です。 9 | 10 | 航空交通管制サービスは、Software Defined Radio (SDR) 搭載の Raspberry Pi で構築した ADS-B 地上局を用いて、民間航空便から直接 ADS-B メッセージを受信します。 11 | そして得られたフライト情報を IBM Cloud で動作する IBM IoT プラットフォームに MQTT メッセージとして発行します。 12 | また、IoT プラットフォームから MQTT メッセージを受信し、Augmented Reality ツールキットを使用してフライトを追跡できる Swift ベースの iOS アプリケーションもサポートしています。 13 | このアプリは、レシーバーの範囲内をを移動するすべてのフライトを表示します。 14 | 15 | アビオニクス分野の進歩と Raspberry Pi (RPi) などの安価なコンピューティングリソースの利用により、最先端の地上局を簡単に構築できます。これらの地上局は、Docker などの仮想化技術を使用して複製でき、より大きな領域をカバーできるようになります。世界中に散在する RPi 搭載の地上局は、次のことを行います: 16 | 17 | * アンテナを備えたSDR受信機を使用して、高度と見通し線に応じて約 100-150 マイルの飛行に関する情報を受信します。 18 | * ネットワークに接続された IoT デバイスとして機能し、スケーラブルで安全で信頼性が高くオープンなクラウド環境で実行されるクラウドベースの航空交通管制に Message Queuing Telemetry Transport (MQTT) メッセージとしてフライト情報を発行します。 19 | 20 | クラウドベースの航空交通管制は、CloudFoundry オープンテクノロジーに基づく IBM の Open Cloud Architecture の実装であり、SoftLayer インフラストラクチャに基づく IBM の Cloud Platform­-As-­A-­Service (PaaS) を使用して実装できます。 21 | 地上局はネットワークに接続され、MQTTメッセージとしてフライト情報を送信する IoT デバイスとしてモデル化されているため、IBM Cloud 内で Internet of Things(IoT) Platform サービスを使用することは理にかなっています。 22 | また、それは単に地上局の数をスケールさせるためだけではなく、フライトデータを使用して分析アプリケーション、視覚化ダッシュボードなどを構成できるように、すべてのイベントを受信するためのファネルポイントとして機能します。 23 | 24 | また、IoT プラットフォームサービスは、接続されているすべての iOS デバイスにフライト情報を提供することができます。 25 | iOS デバイス上で実行される Swift ベースのモバイルアプリは、拡張現実 (AR: Augmented Reality) を使用して、今見ている実際の景色に重ね合わせて、その方向に向かうフライトをレンダリングすることができます。 26 | 27 | ## アーキテクチャー 28 | 29 | 次の図は、安価な地上局を利用して航空便を追跡するクラウドベースの航空交通管制に関するハイレベルのアーキテクチャを示しています。 30 | 31 | ![alt tag](assets/architecture_diagram_v2.png) 32 | 33 | 34 | ## アプリケーションのワークフロー 35 | 36 | ![Application Workflow](./images/arch-iot-airtrafficcontrol-1024x878.png) 37 | 38 | 1. 地上局を表す、SDR レシーバー搭載の Raspberry PI が、民間飛行機からの ADS-B メッセージを受信してデコードし、JSON ペイロードを含む MQTT メッセージを IoT Platform にパブリッシュします。 39 | 2. 該当するデバイス・タイプとデバイス ID を持つ IoT Platform が MQTT メッセージを受信し、トピックの 1 つ上で使用できるようにします。 40 | 3. 必要に応じて、Streaming Analytics サービスが IoT Platform 内のトピックにサブスクライブしてメッセージを処理することもできます。 41 | 4. Streaming Analytics サービスからのデータを使用してダッシュボードが作成されます。 42 | 5. アプリは IoT Platform 内のトピックにサブスクライブし、Weather Company Data API を呼び出してフライト情報と気象情報を地図および拡張現実ビューにレンダリングします。 43 | 6. Weather Company Data Service では、座標を使用して気象データにアクセスするための API を公開しています。 44 | 45 | ## Raspberry Pi による ADS-B 地上局 46 | 47 | Raspberry Pi による ADS-B 地上局を建設するための指示は [こちら](adsb.ground.station/README-ja.md) にあります。 48 | 49 | ## Swift ベースの iOS アプリ 50 | 51 | Swift ベースの iOS アプリを使用してフライトを追跡する手順は [こちら](ARFlightTracker-iOS-Swift/README-ja.md) にあります。 52 | 53 | # ライセンス 54 | 55 | [Apache 2.0](LICENSE) 56 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/Model/ARGeoCoordinate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARGeoCoordinate.m 3 | // ARKitDemo 4 | // 5 | // Created by Haseman on 8/1/09. 6 | // Copyright 2009 Zac White. All rights reserved. 7 | // 8 | 9 | #import "ARGeoCoordinate.h" 10 | 11 | 12 | @implementation ARGeoCoordinate 13 | 14 | @synthesize geoLocation; 15 | 16 | @synthesize radialDistance, inclination, azimuth; 17 | 18 | @synthesize dataObject; 19 | 20 | - (float)angleFromCoordinate:(CLLocationCoordinate2D)first toCoordinate:(CLLocationCoordinate2D)second { 21 | float longitudinalDifference = second.longitude - first.longitude; 22 | float latitudinalDifference = second.latitude - first.latitude; 23 | float possibleAzimuth = (M_PI * .5f) - atan(latitudinalDifference / longitudinalDifference); 24 | if (longitudinalDifference > 0) return possibleAzimuth; 25 | else if (longitudinalDifference < 0) return possibleAzimuth + M_PI; 26 | else if (latitudinalDifference < 0) return M_PI; 27 | 28 | return 0.0f; 29 | } 30 | 31 | - (void)calibrateUsingOrigin:(CLLocation *)origin useAltitude:(BOOL) useAltitude { 32 | 33 | if (!self.geoLocation) return; 34 | 35 | double baseDistance = [origin distanceFromLocation:self.geoLocation]; 36 | 37 | self.radialDistance = sqrt(pow(origin.altitude - self.geoLocation.altitude, 2) + pow(baseDistance, 2)); 38 | 39 | float angle = sin(ABS(origin.altitude - self.geoLocation.altitude) / self.radialDistance); 40 | 41 | if (!useAltitude) { 42 | angle = 0; 43 | } 44 | 45 | if (origin.altitude > self.geoLocation.altitude) angle = -angle; 46 | 47 | self.inclination = angle; 48 | self.azimuth = [self angleFromCoordinate:origin.coordinate toCoordinate:self.geoLocation.coordinate]; 49 | } 50 | 51 | + (ARGeoCoordinate *)coordinateWithLocation:(CLLocation *)location { 52 | ARGeoCoordinate *newCoordinate = [[ARGeoCoordinate alloc] init]; 53 | newCoordinate.geoLocation = location; 54 | 55 | return newCoordinate; 56 | } 57 | 58 | - (NSUInteger)hash{ 59 | return ([dataObject hash] + (int)(self.radialDistance + self.inclination + self.azimuth)); 60 | } 61 | 62 | - (BOOL)isEqual:(id)other { 63 | if (other == self) 64 | return YES; 65 | if (!other || ![other isKindOfClass:[self class]]) 66 | return NO; 67 | return [self isEqualToCoordinate:other]; 68 | } 69 | 70 | - (BOOL)isEqualToCoordinate:(ARGeoCoordinate *)otherCoordinate { 71 | if (self == otherCoordinate) return YES; 72 | 73 | BOOL equal = self.radialDistance == otherCoordinate.radialDistance; 74 | equal &= self.inclination == otherCoordinate.inclination; 75 | equal &= self.azimuth == otherCoordinate.azimuth; 76 | equal &= self.dataObject == otherCoordinate.dataObject; 77 | 78 | return equal; 79 | } 80 | 81 | - (NSString *)description { 82 | return [NSString stringWithFormat:@"r: %.3fm φ: %.3f° θ: %.3f°", self.radialDistance, radiansToDegrees(self.azimuth), radiansToDegrees(self.inclination)]; 83 | } 84 | 85 | 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers Guide 2 | 3 | This guide is intended for maintainers - anybody with commit access to one or 4 | more Code Pattern repositories. 5 | 6 | ## Methodology 7 | 8 | This repository does not have a traditional release management cycle, but 9 | should instead be maintained as as a useful, working, and polished reference at 10 | all times. While all work can therefore be focused on the master branch, the 11 | quality of this branch should never be compromised. 12 | 13 | The remainder of this document details how to merge pull requests to the 14 | repositories. 15 | 16 | ## Merge approval 17 | 18 | The project maintainers use LGTM (Looks Good To Me) in comments on the pull 19 | request to indicate acceptance prior to merging. A change requires LGTMs from 20 | two project maintainers. If the code is written by a maintainer, the change 21 | only requires one additional LGTM. 22 | 23 | ## Reviewing Pull Requests 24 | 25 | We recommend reviewing pull requests directly within GitHub. This allows a 26 | public commentary on changes, providing transparency for all users. When 27 | providing feedback be civil, courteous, and kind. Disagreement is fine, so long 28 | as the discourse is carried out politely. If we see a record of uncivil or 29 | abusive comments, we will revoke your commit privileges and invite you to leave 30 | the project. 31 | 32 | During your review, consider the following points: 33 | 34 | ### Does the change have positive impact? 35 | 36 | Some proposed changes may not represent a positive impact to the project. Ask 37 | whether or not the change will make understanding the code easier, or if it 38 | could simply be a personal preference on the part of the author (see 39 | [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)). 40 | 41 | Pull requests that do not have a clear positive impact should be closed without 42 | merging. 43 | 44 | ### Do the changes make sense? 45 | 46 | If you do not understand what the changes are or what they accomplish, ask the 47 | author for clarification. Ask the author to add comments and/or clarify test 48 | case names to make the intentions clear. 49 | 50 | At times, such clarification will reveal that the author may not be using the 51 | code correctly, or is unaware of features that accommodate their needs. If you 52 | feel this is the case, work up a code sample that would address the pull 53 | request for them, and feel free to close the pull request once they confirm. 54 | 55 | ### Does the change introduce a new feature? 56 | 57 | For any given pull request, ask yourself "is this a new feature?" If so, does 58 | the pull request (or associated issue) contain narrative indicating the need 59 | for the feature? If not, ask them to provide that information. 60 | 61 | Are new unit tests in place that test all new behaviors introduced? If not, do 62 | not merge the feature until they are! Is documentation in place for the new 63 | feature? (See the documentation guidelines). If not do not merge the feature 64 | until it is! Is the feature necessary for general use cases? Try and keep the 65 | scope of any given component narrow. If a proposed feature does not fit that 66 | scope, recommend to the user that they maintain the feature on their own, and 67 | close the request. You may also recommend that they see if the feature gains 68 | traction among other users, and suggest they re-submit when they can show such 69 | support. 70 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightAnnotationCustom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARFlightAnnotationCustom.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/6/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class ARFlightAnnotationViewCustom: ARFlightAnnotationView, UIGestureRecognizerDelegate 13 | { 14 | open var titleLabel: UILabel? 15 | open var infoButton: UIButton? 16 | 17 | override open func didMoveToSuperview() 18 | { 19 | super.didMoveToSuperview() 20 | if self.titleLabel == nil 21 | { 22 | self.loadUi() 23 | } 24 | } 25 | 26 | func loadUi() 27 | { 28 | // Title label 29 | self.titleLabel?.removeFromSuperview() 30 | let label = UILabel() 31 | label.font = UIFont.systemFont(ofSize: 10) 32 | label.numberOfLines = 0 33 | label.backgroundColor = UIColor.clear 34 | label.textColor = UIColor.white 35 | self.addSubview(label) 36 | self.titleLabel = label 37 | 38 | // Info button 39 | self.infoButton?.removeFromSuperview() 40 | let button = UIButton(type: UIButtonType.detailDisclosure) 41 | button.isUserInteractionEnabled = false // Whole view will be tappable, using it for appearance 42 | self.addSubview(button) 43 | self.infoButton = button 44 | 45 | // Gesture 46 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ARFlightAnnotationViewCustom.tapGesture)) 47 | self.addGestureRecognizer(tapGesture) 48 | 49 | // Other 50 | self.backgroundColor = UIColor.black.withAlphaComponent(0.5) 51 | self.layer.cornerRadius = 5 52 | 53 | if self.annotation != nil 54 | { 55 | self.bindUi() 56 | } 57 | } 58 | 59 | func layoutUi() 60 | { 61 | let buttonWidth: CGFloat = 40 62 | let buttonHeight: CGFloat = 40 63 | 64 | self.titleLabel?.frame = CGRect(x: 10, y: 0, width: self.frame.size.width - buttonWidth - 5, height: self.frame.size.height); 65 | self.infoButton?.frame = CGRect(x: self.frame.size.width - buttonWidth, y: self.frame.size.height/2 - buttonHeight/2, width: buttonWidth, height: buttonHeight); 66 | } 67 | 68 | // This method is called whenever distance/azimuth is set 69 | override open func bindUi() 70 | { 71 | if let annotation = self.annotation, let title = annotation.title 72 | { 73 | let distance = annotation.distanceFromUser > 1000 ? String(format: "%.1fkm", annotation.distanceFromUser / 1000) : String(format:"%.0fm", annotation.distanceFromUser) 74 | self.titleLabel?.text = String(format: "%@\nAZ: %.0f°\nDST: %@", title, annotation.azimuth, distance) 75 | } 76 | } 77 | 78 | open override func layoutSubviews() 79 | { 80 | super.layoutSubviews() 81 | self.layoutUi() 82 | } 83 | 84 | open func tapGesture() 85 | { 86 | if let annotation = self.annotation 87 | { 88 | let alertView = UIAlertView(title: annotation.title, message: "Tapped", delegate: nil, cancelButtonTitle: "OK") 89 | alertView.show() 90 | } 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARFlightConfiguration.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/5/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | import UIKit 12 | 13 | let LAT_LON_FACTOR: CGFloat = 1.33975031663 // Used in azimuzh calculation, don't change 14 | //let LAT_LON_FACTOR: CGFloat = 1 // Used in azimuzh calculation, don't change 15 | let VERTICAL_SENS: CGFloat = 960 16 | let H_PIXELS_PER_DEGREE: CGFloat = 14 // How many pixels per degree 17 | //let H_PIXELS_PER_DEGREE: CGFloat = 1 // How many pixels per degree 18 | let OVERLAY_VIEW_WIDTH: CGFloat = 360 * H_PIXELS_PER_DEGREE // 360 degrees x sensitivity 19 | 20 | let MAX_VISIBLE_ANNOTATIONS: Int = 500 // Do not change, can affect performance 21 | let MAX_VERTICAL_LEVELS: Double = 10 // Do not change, can affect performance 22 | 23 | internal func radiansToDegrees(_ radians: Double) -> Double 24 | { 25 | return (radians) * (180.0 / M_PI) 26 | } 27 | 28 | internal func degreesToRadians(_ degrees: Double) -> Double 29 | { 30 | return (degrees) * (M_PI / 180.0) 31 | } 32 | 33 | /// Normalizes degree to 360 34 | internal func normalizeDegree(_ degree: Double) -> Double 35 | { 36 | var degreeNormalized = fmod(degree, 360) 37 | if degreeNormalized < 0 38 | { 39 | degreeNormalized = 360 + degreeNormalized 40 | } 41 | return degreeNormalized 42 | } 43 | 44 | /// Finds shortes angle distance between two angles. Angles must be normalized(0-360) 45 | internal func deltaAngle(_ angle1: Double, angle2: Double) -> Double 46 | { 47 | var deltaAngle = angle1 - angle2 48 | 49 | if deltaAngle > 180 50 | { 51 | deltaAngle -= 360 52 | } 53 | else if deltaAngle < -180 54 | { 55 | deltaAngle += 360 56 | } 57 | return deltaAngle 58 | } 59 | 60 | ///// DataSource provides the ARFlightViewController with the information needed to display annotations. 61 | //@objc public protocol ARDataSource : NSObjectProtocol 62 | //{ 63 | // /// Asks the data source to provide annotation view for annotation. Annotation view must be subclass of ARFlightAnnotationView. 64 | // func ar(_ arViewController: ARFlightViewController, viewForAnnotation: FlightAnnotation) -> ARFlightAnnotationView 65 | // 66 | // /** 67 | // * READ BEFORE IMPLEMENTING 68 | // * ARFlightViewController tracks user movement and shows/hides annotations accordingly. But if there is huge amount 69 | // * of annotations or for some other reason annotations cannot be set all at once, this method can be used to 70 | // * set annotations part by part. 71 | // * 72 | // * Use ARFlightViewController.trackingManager.reloadDistanceFilter to change how often this is called. 73 | // * 74 | // * - parameter arViewController: ARFlightViewController instance 75 | // * - parameter location: Current location of the user 76 | // * - returns: Annotations to load, previous annotations are removed 77 | // */ 78 | // @objc optional func ar(_ arViewController: ARFlightViewController, shouldReloadWithLocation location: CLLocation) -> [FlightAnnotation] 79 | // 80 | //} 81 | 82 | 83 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/README.md: -------------------------------------------------------------------------------- 1 | *Read this in other languages: [日本](README-ja.md).* 2 | 3 | # ARFlightTracker 4 | ARFlightTracker is an iOS based app which tracks flight pushed by SDR/ADSB message receiver through MQTT server. The app will display all the flights travelling point to point within the range of the receiver. AR Flight tracker app is connected to IBM MQTT server to a topic which receives new/updated flight information based on which is rendered into the map view. The data is fed to the topic by SDR/ADSB message receiver. The map also shows animated view of flights heading in a particular direction towards its destination. The callout view on the flight contains flight details with weather in current location of the flight. 5 | 6 | ## Map View 7 | Map View displays all the flights on a default map provided in the iOS device. The flight orientation is adjusted based on the current heading information in the payload. As the app receives MQTT messages, the flight will be seen moving in the direction towards its destination. A flight can be tapped to see more details such as such as flight number, altitude, distance etc. Figure below shows the rendering of the Map View with flights in the Swift-based app on an iOS device: 8 | 9 | ![alt tag](https://github.com/IBM/air-traffic-control/blob/master/assets/mapview-weather.png) 10 | 11 | ## Augmented Reality View 12 | The user can tap the AR View tab in the app to switch to the AR-based View. In this mode, the app opens up a camera view where the user can point to a flight to be able to see the flight data on a callout that overlays on top of the flight in the real world. As the flight is moving in the real world, the callout with the information moves along with the flight in the camera view. The AR view also displays a compass based on the device heading and a radar view displaying all the flights within the viewing angle. Figure 5 below shows the rendering of the Augmented Reality View with flights in the Swift-based app on an iOS device. 13 | 14 | ![alt tag](https://github.com/IBM/air-traffic-control/blob/master/assets/arview-weather.png) 15 | 16 | # Pre-requisites 17 | - Swift 3 18 | - Xcode 8.0+ 19 | - CocoaPod - https://cocoapods.org/ 20 | - iOS 10+ 21 | 22 | 23 | # Dependencies 24 | - CocoaMQTT - Note: moving to aphid client by IBM 25 | - SwiftyJSON 26 | - ios-arkit for iphone - (part of the code base) 27 | 28 | # Steps: 29 | 1. cd ARFlightTracker-iOS-Swift && open ARFlightTracker-iOS-Swift.xcworkspace using Xcode. 30 | 2. Run `pod install` from the project directory. This will install the dependencies define in `Podfile` 31 | 3. Update `util/MQTTConnection.swift` using Xcode editor. You have to create Internet of Things service in IBM Bluemix console to get the credentials. The credentials looks like as shown below: 32 | ``` 33 | API_KEY = "" 34 | API_TOKEN = "" 35 | IOT_CLIENT = "a::Flights" 36 | IOT_HOST = ".messaging.internetofthings.ibmcloud.com" 37 | IOT_PORT = 1883 (DEFAULT) 38 | IOT_TOPIC = "iot-2/type//id//evt/flight/fmt/json" 39 | ``` 40 | 4. Update `util/RestCall.swift` to with credentials for IBM Weather API. Create Weather API service using IBM Bluemix console: 41 | ``` 42 | private static let WEATHER_API_USERNAME : String = "" 43 | private static let WEATHER_API_PASSWORD : String = "" 44 | ``` 45 | 5. Build and Run 46 | 47 | # Test Mode: 48 | You can run the app in test mode to be independant of IBM Bluemix MQTT server. In ViewController you can set the flag 49 | `flightTestMode = true` 50 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightBubble.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightBubble.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 1/12/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | open class FlightBubble: ARAnnotationView, UIGestureRecognizerDelegate 13 | { 14 | open var titleLabel: UILabel? 15 | open var weatherImageView: UIImageView? 16 | 17 | override open func didMoveToSuperview() 18 | { 19 | super.didMoveToSuperview() 20 | if self.titleLabel == nil 21 | { 22 | self.loadUi() 23 | } 24 | } 25 | 26 | func loadUi() 27 | { 28 | // Title label 29 | self.titleLabel?.removeFromSuperview() 30 | let label = UILabel() 31 | label.font = UIFont.systemFont(ofSize: 10) 32 | label.numberOfLines = 0 33 | label.backgroundColor = UIColor.clear 34 | label.textColor = UIColor.white 35 | self.addSubview(label) 36 | self.titleLabel = label 37 | 38 | // weather image 39 | self.weatherImageView?.removeFromSuperview() 40 | let weatherImg = UIImageView() 41 | self.weatherImageView = weatherImg 42 | self.addSubview(weatherImg) 43 | 44 | // Other 45 | self.backgroundColor = UIColor.black.withAlphaComponent(0.5) 46 | self.layer.cornerRadius = 5 47 | 48 | if self.annotation != nil 49 | { 50 | self.bindUi() 51 | } 52 | } 53 | 54 | func layoutUi() 55 | { 56 | let buttonWidth: CGFloat = 40 57 | let buttonHeight: CGFloat = 40 58 | 59 | self.titleLabel?.frame = CGRect(x: 10, y: 0, width: self.frame.size.width - buttonWidth - 5, height: self.frame.size.height); 60 | self.weatherImageView?.frame = CGRect(x: self.frame.size.width - buttonWidth, y: self.frame.size.height/2 - buttonHeight/2, width: buttonWidth, height: buttonHeight); 61 | } 62 | 63 | // This method is called whenever distance/azimuth is set 64 | override open func bindUi() 65 | { 66 | if let annotation = self.annotation, let title = annotation.title 67 | { 68 | let distance = String(format:"%.0fm", annotation.radialDistance) 69 | 70 | var weatherMessage: String = "" 71 | 72 | RestCall().fetchWeatherBasedOnCurrentLocation(latitude: String(annotation.coordinate.latitude),longitude: String(annotation.coordinate.longitude)){ 73 | (result: [String: Any]) in 74 | 75 | let weatherIconUrl: String = result["weatherIconUrl"] as! String 76 | let imageUrl = URL(string: weatherIconUrl) 77 | let data = try? Data(contentsOf: imageUrl!) 78 | let city = "\(result["city"]!)" 79 | let image = UIImage(data: data!) 80 | let currentTemp = "\(result["temperature"]!)°F" 81 | let weatherDesc = "\(result["description"]!)" 82 | 83 | weatherMessage = String(format: "\nCity:%@, %@ %@", city,currentTemp,weatherDesc) 84 | 85 | DispatchQueue.main.async(execute: { () -> Void in 86 | self.weatherImageView?.image = image 87 | self.titleLabel?.text?.append(weatherMessage) 88 | }) 89 | 90 | } 91 | 92 | let text = String(format: "%@\nAlt: %.0f\nDst: %@", title, annotation.altitude, distance) 93 | self.titleLabel?.text = text 94 | } 95 | } 96 | 97 | open override func layoutSubviews() 98 | { 99 | super.layoutSubviews() 100 | self.layoutUi() 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This repository is no longer maintained :warning: 2 | 3 | > This content is no longer being updated or maintained. The content is provided “as is.” Given the rapid evolution of technology, some content, steps, or illustrations may have changed. 4 | 5 | 6 | *Read this in other languages: [中国](README-cn.md), [日本](README-ja.md).* 7 | 8 | 9 | # air-traffic-control 10 | [![Build Status](https://travis-ci.org/IBM/air-traffic-control.svg?branch=master)](https://travis-ci.org/IBM/air-traffic-control) 11 | 12 | This repository contains instructions to build modern Cloud-based Air Traffic Control using IBM Cloud. 13 | 14 | > Note: A Raspberry Pi, SDR receiver with antenna, and an iOS device is required to fully complete this Code Pattern. 15 | 16 | The Air Traffic Control Service receives flight information from a Raspberry Pi powered ADS-B Ground Stations with Software Defined Radio(SDR) to receive ADS-B messages directly from commercial flights and publish MQTT messages to the IBM IoT Platform running in IBM Cloud. It also supports a Swift-based iOS app to track flights using the Augmented Reality toolkit by receiving MQTT messages from the IoT Platform. The app will display all the flights traveling point to point within the range of the receiver. 17 | 18 | With the advances in the field of avionics and the availability of cheap computing resources such as Raspberry Pi (RPi), one can very easily build a state­ of­ the­ art Ground Station. These Ground Stations can be replicated trivially using virtualization technologies such as Docker to be able to cover large swathes of areas. The RPi­-powered Ground Stations, scattered all over the world, will do the following: 19 | * Use a SDR receiver with an antenna to receive information about flights that are in approximately 100­-150 miles radius depending on the altitude and the line­ of ­sight. 20 | * Act as network­ connected IoT devices to publish the flight information as Message Queuing Telemetry Transport (MQTT) messages to a Cloud­-based Air Traffic Control running in scalable, secure, and, reliable, and open cloud infrastructure. 21 | 22 | The Cloud­-based Air Traffic Control can be implemented using IBM's Cloud Platform­-As-­A-­Service (PaaS) which is an implementation of IBM’s Open Cloud Architecture based on CloudFoundry open technology and based on SoftLayer infrastructure. Since the Ground Stations are modeled as IoT devices that are network­ connected and send flight information as MQTT messages, it makes sense to use the Internet of Things(IoT) Platform service within IBM Cloud as it can not only scale elastically with the number of Ground Stations but also serve as funneling point to receive all the events so that one can compose analytics applications, visualization dashboards, etc. using the flight data. 23 | 24 | IoT Platform service will also be able to serve the flight information to all the iOS devices that are connected to it. A Swift­-based mobile app running on an iOS device can use Augmented Reality to render flights that are headed in that direction on the screen before they show up outside one’s window! 25 | 26 | ## Architecture 27 | Following figure shows the high-level architecture of a Cloud-based Air Traffic Control that relies on inexpensive Ground Stations to track flights 28 | 29 | ![alt tag](https://github.com/IBM/air-traffic-control/blob/master/assets/architecture_diagram_v2.png) 30 | 31 | 32 | ## Application Workflow 33 | ![Application Workflow](./images/arch-iot-airtrafficcontrol-1024x878.png) 34 | 35 | 1. Raspberry Pi streams airtraffic data to IoT Platform 36 | 2. MQTT streams data to IoT Analytics dashboard for analysis 37 | 3. Current weather is pulled from the Weather Service API 38 | 4. Analytics and weather data are sent to phone device 39 | 40 | 41 | ## Raspberry Pi powered ADS-B Ground Station 42 | 43 | The instructions for building a Raspberry Pi powered Ground Station are [here](https://github.com/IBM/air-traffic-control/blob/master/adsb.ground.station/README.md). 44 | 45 | ## Swift-based iOS App 46 | 47 | The instructions for tracking flights using Swift-based iOS app are [here](https://github.com/IBM/air-traffic-control/blob/master/ARFlightTracker-iOS-Swift/README.md). 48 | 49 | # License 50 | 51 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](http://www.apache.org/licenses/LICENSE-2.0.txt). 52 | 53 | [Apache Software License (ASL) FAQ](http://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 54 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARViewHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARViewHelper.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/23/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | 13 | class ARViewHelper { 14 | 15 | public static let OnePI = M_PI 16 | public static let TwoPI = M_PI * 2.0 17 | public static let HalfPI = M_PI / 2.0 18 | public static let RadiansConst = M_PI / 180.0 19 | public static let DegreesConst = M_PI * 180.0 20 | 21 | 22 | func GetScreenXCoordinate(annotationView: ARFlightAnnotationView, camera: CLLocation, yaw: Double, viewWidth: Double) -> CGFloat 23 | { 24 | 25 | //Result 26 | var screenX = 0 as CGFloat //Location of POI on screen 27 | 28 | var screenKoefX = 0.0 29 | 30 | let camera2D = camera.coordinate 31 | let annotation = annotationView.annotation! 32 | let poi2D = annotation.coordinate 33 | 34 | let rise = poi2D.latitude - camera2D.latitude 35 | let run = poi2D.longitude - camera2D.longitude 36 | //var inclinationXPOI = (float)Math.Atan(rise / run) - HalfPI; 37 | var inclinationXPOI = atan2(rise, run) - ARViewHelper.HalfPI 38 | if (run < 0.0){ 39 | inclinationXPOI += ARViewHelper.OnePI; 40 | } 41 | 42 | if (inclinationXPOI < 0.0){ 43 | inclinationXPOI += ARViewHelper.TwoPI 44 | } 45 | 46 | // Heading correction 47 | var inclinationX = 0.0 48 | if (yaw < 0) 49 | { 50 | inclinationX = yaw + ARViewHelper.TwoPI 51 | } 52 | else 53 | { 54 | inclinationX = yaw 55 | } 56 | 57 | //region Coordinate X 58 | //var distanceX = (float)Math.Sqrt((float)2 - (float)2.0 * (float)Math.Cos(inclinationX - inclinationXPOI)); 59 | var distanceX = sqrt(2 - 2.0 * cos(inclinationX - inclinationXPOI)) 60 | if (inclinationX < inclinationXPOI) 61 | { 62 | distanceX = -distanceX 63 | } 64 | if (inclinationX <= ARViewHelper.TwoPI && inclinationX >= (3 * ARViewHelper.HalfPI) && inclinationXPOI >= 0 && inclinationXPOI < (ARViewHelper.HalfPI)) 65 | { 66 | distanceX = -distanceX 67 | } 68 | 69 | screenKoefX = distanceX 70 | screenX = CGFloat(viewWidth.multiplied(by: screenKoefX)) 71 | 72 | return screenX 73 | //return CGFloat(viewWidth.divided(by: 2.0)) + screenX 74 | //endregion 75 | } 76 | 77 | 78 | 79 | func GetScreenYCoordinate(annotationView: ARFlightAnnotationView, camera: CLLocation, roll: Double, viewHeight: Double) -> CGFloat 80 | { 81 | 82 | var screenY = 0 as CGFloat 83 | 84 | let annotation = annotationView.annotation! 85 | let poi2D = annotation.coordinate 86 | //let distanceAB = MKGeometry.MetersBetweenMapPoints(camera, poi.LastLocation); 87 | let distanceAB = camera.distance(from: CLLocation.init(latitude:poi2D.latitude, longitude: poi2D.longitude)) 88 | 89 | //POI 90 | let poiAltitude = annotation.altitude as Double 91 | //Camera location 92 | let altitude = camera.altitude as Double 93 | var screenKoefY = 0.0 94 | 95 | 96 | //var inclinationYPOI = (float)Math.Atan((float)(poiAltitude - altitude) / distanceAB); 97 | var inclinationYPOI = atan2(poiAltitude - altitude, distanceAB) 98 | if (inclinationYPOI <= 0.0){ 99 | inclinationYPOI += ARViewHelper.TwoPI 100 | } 101 | 102 | //Heading correction 103 | var inclinationY = abs(roll) - ARViewHelper.HalfPI; 104 | if (inclinationY <= 0.0){ 105 | inclinationY += ARViewHelper.TwoPI 106 | } 107 | 108 | screenKoefY = sqrt(2 - 2.0 * cos(inclinationYPOI - inclinationY)); 109 | if (inclinationYPOI < inclinationY) 110 | { 111 | screenKoefY = -screenKoefY 112 | } 113 | if (inclinationYPOI <= ARViewHelper.TwoPI && inclinationYPOI >= (3 * ARViewHelper.HalfPI) && inclinationY >= 0 && inclinationY <= (ARViewHelper.HalfPI)) 114 | { 115 | screenKoefY = -screenKoefY 116 | } 117 | if (inclinationY <= ARViewHelper.TwoPI && inclinationY >= (3 * ARViewHelper.HalfPI) && inclinationYPOI >= 0 && inclinationYPOI <= (ARViewHelper.HalfPI)) 118 | { 119 | screenKoefY = -screenKoefY 120 | } 121 | 122 | screenY = CGFloat(viewHeight.multiplied(by: screenKoefY)) 123 | 124 | return screenY 125 | //endregion 126 | 127 | //return CGFloat(viewHeight.divided(by: 2.0)) - screenY 128 | 129 | 130 | } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /adsb.ground.station/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.ibm.iot 6 | adsb.ground.station 7 | develop-SNAPSHOT 8 | jar 9 | 10 | adsb.ground.station 11 | https://github.com/ibm/air-traffic-control/adsb.ground.station 12 | 13 | 14 | UTF-8 15 | 4.12-beta-1 16 | 1.8 17 | 1.8 18 | 1.8+ 19 | 20 | 21 | 22 | 23 | 24 | org.opensky-network 25 | libadsb 26 | 2.0 27 | 28 | 29 | com.ibm.messaging 30 | watson-iot 31 | 0.2.2 32 | 33 | 34 | org.slf4j 35 | slf4j-api 36 | 1.7.21 37 | 38 | 39 | org.slf4j 40 | slf4j-log4j12 41 | 1.7.21 42 | 43 | 44 | junit 45 | junit 46 | ${junit.version} 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-shade-plugin 56 | 57 | 58 | package 59 | 60 | shade 61 | 62 | 63 | 64 | 65 | *:* 66 | 67 | META-INF/*.SF 68 | META-INF/*.DSA 69 | META-INF/*.RSA 70 | 71 | 72 | 73 | 74 | 75 | com.ibm.iot.adsb.ground.station.AdsbClient 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-jar-plugin 85 | 2.5 86 | 87 | 88 | 89 | 90 | 91 | true 92 | all-permissions 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-dependency-plugin 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-assembly-plugin 105 | 106 | 107 | 108 | src/main/assembly/filter.properties 109 | 110 | 111 | 112 | 113 | verify 114 | 115 | attached 116 | 117 | 118 | 119 | src/main/assembly/sources.xml 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARKit/LocalizationHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationHelper.m 3 | // ARModule 4 | // 5 | // Created by Carlos on 06/06/11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | #import "UIKit/UIKit.h" 9 | #import "LocalizationHelper.h" 10 | 11 | static LocalizationHelper *sharedHelper; 12 | 13 | @implementation LocalizationHelper 14 | 15 | #pragma mark - Singleton Initialization 16 | 17 | + (LocalizationHelper *) sharedHelper { 18 | @synchronized ([LocalizationHelper class]) { 19 | if (!sharedHelper) { 20 | sharedHelper = [[LocalizationHelper alloc] init]; 21 | } 22 | return sharedHelper; 23 | } 24 | return nil; 25 | } 26 | 27 | - (id) init { 28 | if ((self = [super init])) { 29 | locationManager = [[CLLocationManager alloc] init]; 30 | locationManager.delegate = self; 31 | locationManager.headingFilter = kCLHeadingFilterNone; 32 | locationManager.desiredAccuracy = kCLLocationAccuracyBest; 33 | locationManager.distanceFilter = kCLDistanceFilterNone; 34 | [locationManager requestAlwaysAuthorization]; 35 | 36 | isHeadingInfoAvailable = [CLLocationManager headingAvailable]; 37 | 38 | onceRegistered = [[NSMutableArray alloc] init]; 39 | registered = [[NSMutableArray alloc] init]; 40 | 41 | localizationStatus = kLocalizationUnknown; 42 | } 43 | return self; 44 | } 45 | 46 | #pragma mark - Clients management 47 | 48 | - (void) registerForUpdates:(id)receiver once:(BOOL)once { 49 | if (localizationStatus == kLocalizationDisabled) { 50 | [self locationManager:locationManager didFailWithError:nil]; 51 | } else { 52 | if (once) { 53 | if (lastLocation) { 54 | [self locationManager:locationManager didUpdateLocations:@[lastLocation]]; 55 | } else { 56 | [onceRegistered addObject:receiver]; 57 | } 58 | } else { 59 | [registered addObject:receiver]; 60 | } 61 | if ([registered count] + [onceRegistered count] == 1) { 62 | [locationManager startUpdatingHeading]; 63 | [locationManager startUpdatingLocation]; 64 | } 65 | } 66 | } 67 | 68 | - (void) deregisterForUpdates:(id)receiver { 69 | if ([registered containsObject:receiver]) { 70 | [registered removeObject:receiver]; 71 | } 72 | if ([onceRegistered containsObject:receiver]) { 73 | [onceRegistered removeObject:receiver]; 74 | } 75 | 76 | if (![registered count] && ![onceRegistered count]) { 77 | [locationManager stopUpdatingHeading]; 78 | [locationManager stopUpdatingLocation]; 79 | } 80 | } 81 | 82 | - (BOOL) canReceiveHeadingUpdates { 83 | return isHeadingInfoAvailable; 84 | } 85 | 86 | #pragma mark - CLLocator delegate methods 87 | 88 | - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { 89 | localizationStatus = kLocalizationEnabled; 90 | CLLocation *newLocation = [locations lastObject]; 91 | NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow]; 92 | if (locationAge > 5.0) { 93 | //Probably a cached result. Restart 94 | [manager stopUpdatingLocation]; 95 | [manager startUpdatingLocation]; 96 | return; 97 | } 98 | 99 | for (id delegate in registered) { 100 | [delegate locationFound:newLocation]; 101 | } 102 | for (id delegate in onceRegistered) { 103 | [delegate locationFound:newLocation]; 104 | } 105 | [onceRegistered removeAllObjects]; 106 | if (![registered count]) { 107 | [manager stopUpdatingLocation]; 108 | [manager stopUpdatingHeading]; 109 | } 110 | } 111 | 112 | - (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager { 113 | return YES; 114 | } 115 | 116 | - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { 117 | for (id delegate in registered) { 118 | [delegate headingFound:newHeading]; 119 | } 120 | for (id delegate in onceRegistered) { 121 | [delegate headingFound:newHeading]; 122 | } 123 | } 124 | 125 | - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { 126 | [manager stopUpdatingLocation]; 127 | [loadingView removeFromSuperview]; 128 | localizationStatus = kLocalizationDisabled; 129 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" 130 | message:NSLocalizedString(@"GPS_Unavailable", @"") 131 | delegate:nil 132 | cancelButtonTitle:NSLocalizedString(@"Ok", @"") 133 | otherButtonTitles:nil]; 134 | [alert show]; 135 | [locDelegate locationUnavailable]; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /adsb.ground.station/src/main/java/com/ibm/iot/adsb/ground/station/IotClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, IBM Corporation. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.ibm.iot.adsb.ground.station; 17 | 18 | import java.io.IOException; 19 | import java.net.InetAddress; 20 | import java.net.NetworkInterface; 21 | import java.net.SocketException; 22 | import java.net.UnknownHostException; 23 | import java.util.Enumeration; 24 | import java.util.Properties; 25 | 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import com.google.gson.JsonObject; 30 | import com.ibm.iotf.client.app.ApplicationClient; 31 | 32 | public class IotClient { 33 | private static final String MAC_ADDRESS = computeMacAddress(); 34 | private static final String APP_PROPERTIES_FILE_NAME = "/application.properties"; 35 | private static final Properties APP_PROPERTIES = loadProperties(APP_PROPERTIES_FILE_NAME); 36 | 37 | private final Logger logger = LoggerFactory.getLogger(IotClient.class); 38 | private final String deviceType; 39 | private final String deviceId; 40 | 41 | private ApplicationClient applicationClient; 42 | 43 | public IotClient( ) { 44 | if (logger.isDebugEnabled()) { 45 | logger.debug(APP_PROPERTIES.toString()); 46 | } 47 | 48 | deviceType = APP_PROPERTIES.getProperty("Device-Type"); 49 | deviceId = APP_PROPERTIES.getProperty("Device-ID"); 50 | 51 | // Specify unique "id" so that multiple instances of this app can concurrently publish MQTT 52 | // messages on behalf of the same IoT device without requiring a hardcoded entry in the 53 | // properties file. 54 | APP_PROPERTIES.setProperty("id", MAC_ADDRESS + 55 | "-" + 56 | String.valueOf(System.currentTimeMillis())); 57 | } 58 | 59 | public void connect() throws IOException { 60 | try { 61 | applicationClient = new ApplicationClient(APP_PROPERTIES); 62 | applicationClient.connect(); 63 | 64 | if (logger.isDebugEnabled()) { 65 | logger.debug("Connected to Watson Iot Platform: " + applicationClient.isConnected()); 66 | } 67 | } catch (Exception e) { 68 | logger.error("Failed to connect to Watson IoT Platform", e.getMessage()); 69 | throw new IOException("Failed to connect to Watson Iot Platform", e); 70 | } 71 | } 72 | 73 | public void disconnect() { 74 | applicationClient.disconnect(); 75 | applicationClient = null; 76 | } 77 | 78 | public boolean isConnected() { 79 | return (applicationClient != null) ? applicationClient.isConnected() : false; 80 | } 81 | 82 | public void publishEvent(Flight flight) throws IOException { 83 | if ((flight == null) || !flight.isReadyForTracking()) { 84 | return; 85 | } 86 | 87 | if ((applicationClient == null) || !applicationClient.isConnected()) { 88 | logger.info("Reconnecting to IBM Bluemix..."); 89 | connect(); 90 | logger.info("Reconnected successfully to IBM Bluemix..."); 91 | } 92 | 93 | if (logger.isDebugEnabled()) { 94 | logger.debug(flight.toString()); 95 | } 96 | 97 | JsonObject jsonObject = flight.getJsonObject(); 98 | logger.info("Publishing MQTT message"); 99 | applicationClient.publishEvent(deviceType, deviceId, "flight", jsonObject); 100 | logger.info("Published MQTT message"); 101 | } 102 | 103 | public static String getMacAddress() { 104 | return MAC_ADDRESS; 105 | } 106 | 107 | private static Properties loadProperties(String propertiesFileName) { 108 | Properties props = new Properties(); 109 | try { 110 | props.load(IotClient.class.getResourceAsStream(propertiesFileName)); 111 | } catch (IOException ex) { 112 | System.out.println("Not able to read the properties file!"); 113 | ex.printStackTrace(); 114 | throw new RuntimeException(ex); 115 | } 116 | 117 | return props; 118 | } 119 | 120 | private static String computeMacAddress() { 121 | StringBuilder sb = new StringBuilder(); 122 | 123 | try { 124 | InetAddress ip = InetAddress.getLocalHost(); 125 | System.out.println("IP address : " + ip.getHostAddress()); 126 | 127 | Enumeration networks = NetworkInterface.getNetworkInterfaces(); 128 | while (networks.hasMoreElements()) { 129 | NetworkInterface network = networks.nextElement(); 130 | byte[] mac = network.getHardwareAddress(); 131 | 132 | if (mac != null) { 133 | System.out.print("MAC address : "); 134 | for (int i = 0; i < mac.length; i++) { 135 | sb.append(String.format("%02X", mac[i])); 136 | } 137 | System.out.println(sb.toString()); 138 | return sb.toString(); 139 | } 140 | } 141 | } catch (UnknownHostException | SocketException e) { 142 | e.printStackTrace(); 143 | } 144 | return sb.toString(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/FlightBubbleV1.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 32 | 39 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /adsb.ground.station/src/main/java/com/ibm/iot/adsb/ground/station/Flight.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, IBM Corporation. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.ibm.iot.adsb.ground.station; 17 | 18 | import static java.lang.String.format; 19 | 20 | import org.opensky.libadsb.PositionDecoder; 21 | 22 | import com.google.gson.JsonObject; 23 | 24 | public class Flight { 25 | public static enum State { 26 | CACHED, NEW 27 | } 28 | 29 | private static final String SCHEMA_TYPE = "ar_flights_schema"; 30 | private static final String PROPERTY_ICAO = "icao"; 31 | private static final String PROPERTY_ALTITUDE = "altitudeInMeters"; 32 | private static final String PROPERTY_CALLSIGN = "callSign"; 33 | private static final String PROPERTY_HEADING = "headingInDegrees"; 34 | private static final String PROPERTY_LATITUDE = "latitude"; 35 | private static final String PROPERTY_LONGITUDE = "longitude"; 36 | private static final String PROPERTY_SCHEMA_TYPE = "type"; 37 | private static final String PROPERTY_VELOCITY = "velocityInMetersPerSecond"; 38 | private static final String PROPERTY_CREATE_TIMESTAMP = "createdInMillis"; 39 | private static final String PROPERTY_LAST_UPDATED_TIMESTAMP = "lastUpdatedInMillis"; 40 | private static final String PROPERTY_GROUND_STATION_ID = "groundStationId"; 41 | 42 | private static final String JSON_MESSAGE_FORMAT = 43 | "{ " + 44 | "\"icao\" : " + "\"%s\", " + 45 | "\"altitudeInMeters\" : " + "%f, " + 46 | "\"callSign\" : " + "\"%s\", " + 47 | "\"headingInDegrees\" : " + "%f, " + 48 | "\"latitude\" : " + "%f, " + 49 | "\"longitude\" : " + "%f, " + 50 | "\"groundStationId\" : " + "\"%s\", " + 51 | "\"type\" : " + "\"%s\", " + 52 | "\"velocityInMetersPerSecond\" : " + "%f, " + 53 | "\"createdInMillis\" : " + "%d, " + 54 | "\"lastUpdatedInMillis\" : " + "%d" + 55 | "}"; 56 | 57 | private final String icao; 58 | private final long createdInMillis; 59 | private final PositionDecoder positionDecoder; 60 | 61 | private String callSign; 62 | private double altitudeInMeters; 63 | private double headingInDegrees; 64 | private double latitude; 65 | private double longitude; 66 | private double velocityInMetersPerSecond; 67 | private long lastUpdatedInMillis; 68 | private State state; 69 | 70 | public Flight(String icao) { 71 | if ((icao == null) || (icao.length() == 0)) { 72 | throw new NullPointerException(format("Invalid icao passed in: '%s'", icao)); 73 | } 74 | this.icao = icao; 75 | this.createdInMillis = System.currentTimeMillis(); 76 | this.positionDecoder = new PositionDecoder(); 77 | this.latitude = 100; // Invalid latitude initialization 78 | this.longitude = 200; // Invalid longitude initialization 79 | } 80 | 81 | public String getIcao() { 82 | return icao; 83 | } 84 | 85 | public long getCreatedInMillis() { 86 | return createdInMillis; 87 | } 88 | 89 | public PositionDecoder getPositionDecoder() { 90 | return positionDecoder; 91 | } 92 | 93 | public String getType() { 94 | return SCHEMA_TYPE; 95 | } 96 | 97 | public String getCallSign() { 98 | return callSign; 99 | } 100 | 101 | public void setCallSign(String callSign) { 102 | this.callSign = callSign; 103 | } 104 | 105 | public double getAltitudeInMeters() { 106 | return altitudeInMeters; 107 | } 108 | 109 | public void setAltitudeInMeters(double altitudeInMeters) { 110 | this.altitudeInMeters = altitudeInMeters; 111 | } 112 | 113 | public double getHeadingInDegrees() { 114 | return headingInDegrees; 115 | } 116 | 117 | public void setHeadingInDegrees(double headingInDegrees) { 118 | this.headingInDegrees = headingInDegrees; 119 | } 120 | 121 | public double getLatitude() { 122 | return latitude; 123 | } 124 | 125 | public void setLatitude(double latitude) { 126 | this.latitude = latitude; 127 | } 128 | 129 | public double getLongitude() { 130 | return longitude; 131 | } 132 | 133 | public void setLongitude(double longitude) { 134 | this.longitude = longitude; 135 | } 136 | 137 | public double getVelocityInMetersPerSecond() { 138 | return velocityInMetersPerSecond; 139 | } 140 | 141 | public void setVelocityInMetersPerSecond(double velocityInMetersPerSecond) { 142 | this.velocityInMetersPerSecond = velocityInMetersPerSecond; 143 | } 144 | 145 | public long getLastUpdatedInMillis() { 146 | return lastUpdatedInMillis; 147 | } 148 | 149 | public void setLastUpdatedInMillis(long lastUpdatedInMillis) { 150 | this.lastUpdatedInMillis = lastUpdatedInMillis; 151 | } 152 | 153 | public boolean isReadyForTracking() { 154 | return ((latitude != 100) && (longitude != 200)) ? true : false; 155 | } 156 | 157 | public JsonObject getJsonObject() { 158 | assert isReadyForTracking(); 159 | 160 | JsonObject jsonObject = new JsonObject(); 161 | String cs = (callSign != null) ? callSign : icao; 162 | 163 | jsonObject.addProperty(PROPERTY_ICAO, icao); 164 | jsonObject.addProperty(PROPERTY_ALTITUDE, altitudeInMeters); 165 | jsonObject.addProperty(PROPERTY_CALLSIGN, cs); 166 | jsonObject.addProperty(PROPERTY_HEADING, headingInDegrees); 167 | jsonObject.addProperty(PROPERTY_LATITUDE, latitude); 168 | jsonObject.addProperty(PROPERTY_LONGITUDE, longitude); 169 | jsonObject.addProperty(PROPERTY_GROUND_STATION_ID, IotClient.getMacAddress()); 170 | jsonObject.addProperty(PROPERTY_SCHEMA_TYPE, SCHEMA_TYPE); 171 | jsonObject.addProperty(PROPERTY_VELOCITY, velocityInMetersPerSecond); 172 | jsonObject.addProperty(PROPERTY_CREATE_TIMESTAMP, createdInMillis); 173 | jsonObject.addProperty(PROPERTY_LAST_UPDATED_TIMESTAMP, lastUpdatedInMillis); 174 | 175 | return jsonObject; 176 | } 177 | 178 | public String toJSON() { 179 | assert isReadyForTracking(); 180 | 181 | String cs = (callSign != null) ? callSign : icao; 182 | String json = format(JSON_MESSAGE_FORMAT, 183 | icao, 184 | altitudeInMeters, 185 | cs, 186 | headingInDegrees, 187 | latitude, 188 | longitude, 189 | IotClient.getMacAddress(), 190 | SCHEMA_TYPE, 191 | velocityInMetersPerSecond, 192 | createdInMillis, 193 | lastUpdatedInMillis 194 | ); 195 | return json; 196 | } 197 | 198 | public String toString() { 199 | return toJSON(); 200 | } 201 | 202 | public State getState() { 203 | return state; 204 | } 205 | 206 | public void setState(State state) { 207 | this.state = state; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 11/14/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | import Foundation 9 | import UIKit 10 | import CocoaMQTT 11 | import SwiftyJSON 12 | import CoreLocation 13 | 14 | open class ViewController: UIViewController, CocoaMQTTDelegate,CLLocationManagerDelegate{ 15 | 16 | var flightTestMode : Bool = true 17 | 18 | var flightImage : [String : UIImage] = [:] 19 | 20 | var flightAnnotations : [String : FlightAnnotation] = [:] 21 | 22 | var flightAnnotationsGeo : [String : ARGeoCoordinate] = [:] 23 | 24 | let locationManager = CLLocationManager() 25 | 26 | var currentView : ViewController? 27 | 28 | var points : [ARGeoCoordinate] = [] 29 | 30 | var arKitEngine : ARKitEngine? = nil 31 | 32 | open var userLocation: CLLocation? 33 | 34 | override open func viewDidLoad() 35 | { 36 | super.viewDidLoad() 37 | 38 | if(!self.flightTestMode){ 39 | //connection to MQTT IoT 40 | MQTTConnection().connectToMQTT(_delegate: self) 41 | }else{ 42 | print("Running in Test Mode") 43 | readTestFlights() 44 | currentView?.showTestFlights() 45 | } 46 | } 47 | 48 | public func mqtt(_ mqtt: CocoaMQTT, didConnect host: String, port: Int) { 49 | print("didConnect \(host):\(port)") 50 | } 51 | 52 | public func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { 53 | print("didConnectAck: \(ack),rawValue: \(ack.rawValue)") 54 | 55 | if ack == .accept { 56 | mqtt.subscribe(MQTTConnection.IOT_TOPIC, qos: CocoaMQTTQOS.qos1) 57 | } 58 | 59 | } 60 | 61 | public func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { 62 | print("didPublishMessage with message: \(message.string)") 63 | } 64 | 65 | public func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { 66 | print("didPublishAck with id: \(id)") 67 | } 68 | 69 | public func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16 ) { 70 | //print("didReceivedMessage: \(message.string) with id \(id)") 71 | 72 | if let flightInfoString = message.string!.data(using: .utf8, allowLossyConversion: false) { 73 | 74 | let json = JSON(data: flightInfoString) 75 | 76 | let flight = FlightInfo(json: json)! 77 | 78 | let createdMillis: UnixTime = flight.createdInMillis 79 | 80 | let lastUpdatedInMIllis: UnixTime = flight.lastUpdatedInMillis 81 | 82 | print("flight: \(flight.icao) | Longitude: \(flight.longitude) | Latitude: \(flight.latitude) | createdTS: \((createdMillis/1000).toDayAndHour) | lastUpdateTS: \((lastUpdatedInMIllis/1000).toDayAndHour)") 83 | 84 | let flightAnnotation = FlightAnnotation(coordinate:CLLocationCoordinate2D(latitude: flight.latitude,longitude: flight.longitude)) 85 | flightAnnotation.title=flight.icao 86 | flightAnnotation.coordinate=CLLocationCoordinate2D(latitude: flight.latitude,longitude: flight.longitude) 87 | // the logic of showing plane based on flight can be done here based on the icao code. 88 | flightAnnotation.image=UIImage(named:"plane.png")?.rotated(by: flight.headingInDegrees) 89 | flightAnnotation.altitude=flight.altitudeInMeters 90 | flightAnnotation.speed=flight.velocityInMetersPerSecond 91 | flightAnnotation.lastUpdatedInMillis=flight.lastUpdatedInMillis 92 | flightAnnotation.heading=flight.headingInDegrees 93 | flightAnnotation.callSign=flight.callSign 94 | 95 | //send the data to correspondign view 96 | currentView?.dataReceived(flightAnnotation: flightAnnotation) 97 | 98 | 99 | } 100 | 101 | } 102 | 103 | 104 | public func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopic topic: String) { 105 | print("didSubscribeTopic to \(topic)") 106 | } 107 | 108 | public func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopic topic: String) { 109 | print("didUnsubscribeTopic to \(topic)") 110 | } 111 | 112 | public func mqttDidPing(_ mqtt: CocoaMQTT) { 113 | print("didPing") 114 | } 115 | 116 | public func mqttDidReceivePong(_ mqtt: CocoaMQTT) { 117 | _console("didReceivePong") 118 | } 119 | 120 | public func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { 121 | _console("mqttDidDisconnect") 122 | if((err) != nil){ 123 | MQTTConnection().connectToMQTT(_delegate: self) 124 | } 125 | 126 | } 127 | 128 | func _console(_ info: String) { 129 | print("Delegate: \(info)") 130 | } 131 | 132 | // subclass will override this 133 | func dataReceived(flightAnnotation : FlightAnnotation){ 134 | 135 | } 136 | 137 | //sub class will override this to display test flights 138 | func showTestFlights(){ 139 | 140 | } 141 | 142 | 143 | 144 | func readTestFlights(){ 145 | if let path = Bundle.main.path(forResource: "TestFlights", ofType: "json") { 146 | do { 147 | let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped) 148 | let flightJSONObjects = JSON(data: data) 149 | if flightJSONObjects != JSON.null { 150 | // print("jsonData:\(flightJSONObjects)") 151 | 152 | let flights = flightJSONObjects["flights"].arrayValue 153 | for flight in flights { 154 | let flightInfo = FlightInfo(json: flight)! 155 | 156 | let flightAnnotation = FlightAnnotation(coordinate:CLLocationCoordinate2D(latitude: flightInfo.latitude,longitude: flightInfo.longitude)) 157 | flightAnnotation.title=flightInfo.icao 158 | flightAnnotation.coordinate=CLLocationCoordinate2D(latitude: flightInfo.latitude,longitude: flightInfo.longitude) 159 | // the logic of showing plane based on flight can be done here based on the icao code. 160 | flightAnnotation.image=UIImage(named:"plane.png")?.rotated(by: flightInfo.headingInDegrees) 161 | flightAnnotation.altitude=flightInfo.altitudeInMeters 162 | flightAnnotation.speed=flightInfo.velocityInMetersPerSecond 163 | flightAnnotation.lastUpdatedInMillis=flightInfo.lastUpdatedInMillis 164 | flightAnnotation.heading=flightInfo.headingInDegrees 165 | flightAnnotation.testFlight = true 166 | flightAnnotation.callSign=flightInfo.callSign 167 | flightAnnotations[flightInfo.icao!] = flightAnnotation 168 | } 169 | } else { 170 | print("Could not get json from file, make sure that file contains valid json.") 171 | } 172 | } catch let error { 173 | print(error.localizedDescription) 174 | } 175 | } else { 176 | print("Invalid filename/path.") 177 | } 178 | 179 | } 180 | 181 | 182 | 183 | } 184 | 185 | typealias UnixTime = Int64 186 | 187 | extension UnixTime { 188 | private func formatType(form: String) -> DateFormatter { 189 | let dateFormatter = DateFormatter() 190 | dateFormatter.locale = NSLocale(localeIdentifier: "en_US") as Locale! 191 | dateFormatter.dateFormat = form 192 | return dateFormatter 193 | } 194 | var dateFull: NSDate { 195 | return NSDate(timeIntervalSince1970: Double(self)) 196 | } 197 | var toHour: String { 198 | return formatType(form: "hh:mm").string(from: dateFull as Date) 199 | } 200 | var toDay: String { 201 | return formatType(form: "MM/dd/yyyy").string(from: dateFull as Date) 202 | } 203 | var toDayAndHour: String { 204 | return formatType(form: "MM/dd/yyyy hh:mm").string(from: dateFull as Date) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | //class ARFlightViewController: ViewController, ARLocationDelegate,ARDelegate,MarkerViewDelegate 3 | //{ 4 | // public func didTouch(_ markerView: MarkerView!) { 5 | // //<#code#> 6 | // } 7 | // 8 | // 9 | // 10 | // public func geoLocations() -> NSMutableArray! { 11 | // return NSMutableArray(array: Array(flightAnnotationsGeo.values)) 12 | // } 13 | // 14 | // 15 | // public func locationClicked(_ coordinate: ARGeoCoordinate!) { 16 | // 17 | // } 18 | // 19 | // fileprivate var didLayoutSubviews: Bool = false 20 | // fileprivate var annotations: [FlightAnnotation] = [] 21 | // open var userLocation: CLLocation? 22 | // fileprivate var arController: AugmentedRealityController? 23 | // fileprivate var closeButton: UIButton? 24 | // fileprivate var compassImage: UIImageView! 25 | // open var closeButtonImage: UIImage? 26 | // { 27 | // didSet 28 | // { 29 | // closeButton?.setImage(self.closeButtonImage, for: UIControlState()) 30 | // } 31 | // } 32 | // 33 | // 34 | // 35 | // init(){ 36 | // super.init(nibName: nil, bundle: nil) 37 | // super.currentView = self 38 | // if(arController == nil) { 39 | // arController = AugmentedRealityController(view: self.view, parentViewController: self, withDelgate: self) 40 | // } 41 | // arController?.minimumScaleFactor=0.5 42 | // arController?.scaleViewsBasedOnDistance=true 43 | // arController?.rotateViewsBasedOnPerspective=true 44 | // arController?.debugMode=false 45 | // } 46 | // 47 | // required init?(coder aDecoder: NSCoder) { 48 | // super.init(coder: aDecoder) 49 | // } 50 | // 51 | // 52 | //// override func viewDidLoad() 53 | //// { 54 | //// 55 | //// super.viewDidLoad() 56 | //// } 57 | // 58 | // open override func viewWillAppear(_ animated: Bool) 59 | // { 60 | // super.viewWillAppear(animated) 61 | // onViewWillAppear() // Doing like this to prevent subclassing problems 62 | // } 63 | // 64 | // fileprivate func onViewWillAppear() 65 | // { 66 | // self.generateGeoCoordinatesFromFlightAnnotaiton() 67 | // } 68 | // 69 | // open override func viewDidAppear(_ animated: Bool) 70 | // { 71 | // super.viewDidAppear(animated) 72 | // onViewDidAppear() // Doing like this to prevent subclassing problems 73 | // } 74 | // 75 | // override func viewDidLayoutSubviews() 76 | // { 77 | // super.viewDidLayoutSubviews() 78 | // onViewDidLayoutSubviews() 79 | // } 80 | // 81 | // fileprivate func onViewDidAppear() 82 | // { 83 | // //self.generateGeoCoordinatesFromFlightAnnotaiton() 84 | // 85 | // } 86 | // 87 | // open override func viewDidDisappear(_ animated: Bool) 88 | // { 89 | // super.viewDidDisappear(animated) 90 | // onViewDidDisappear() // Doing like this to prevent subclassing problems 91 | // } 92 | // 93 | // fileprivate func onViewDidDisappear(){ 94 | // arController?.removeCoordinates(Array(flightAnnotationsGeo.values)) 95 | // arController?.stopListening() 96 | // } 97 | // 98 | // fileprivate func onViewDidLayoutSubviews() 99 | // { 100 | // // Executed only first time when everything is layouted 101 | // if !self.didLayoutSubviews 102 | // { 103 | // self.didLayoutSubviews = true 104 | // 105 | // // Close button 106 | // self.addCloseButton() 107 | // 108 | // //compass view 109 | // self.addCompassView() 110 | // 111 | // self.view.layoutIfNeeded() 112 | // } 113 | // } 114 | // 115 | // 116 | // @available(iOS 3.0, *) 117 | // public func didUpdate(_ newHeading: CLHeading!) { 118 | // 119 | // } 120 | // 121 | // @available(iOS 2.0, *) 122 | // public func didUpdate(_ newLocation: CLLocation!) { 123 | // self.userLocation = newLocation 124 | // } 125 | // 126 | // public func didUpdate(_ orientation: UIDeviceOrientation) { 127 | // } 128 | // 129 | // 130 | // func setFlightAnnotations(annotations: [FlightAnnotation]){ 131 | // self.annotations = annotations 132 | // } 133 | // 134 | // open func generateGeoCoordinatesFromFlightAnnotaiton() 135 | // { 136 | // // Don't use annotations without valid location 137 | // for annotation in annotations 138 | // { 139 | // if CLLocationCoordinate2DIsValid(annotation.coordinate) 140 | // { 141 | // let timeInterval:TimeInterval=Double(annotation.lastUpdatedInMillis)/1000 142 | // 143 | // let arGeoCoordinate: ARGeoCoordinate = ARGeoCoordinate( 144 | // location: CLLocation.init(coordinate: annotation.coordinate, altitude: annotation.altitude, horizontalAccuracy: 0, verticalAccuracy: 0, course: annotation.heading , speed: annotation.speed, timestamp: NSDate(timeIntervalSinceNow: timeInterval) as Date), 145 | // locationTitle: annotation.title) 146 | // 147 | //// let arGeoCoordinate:ARGeoCoordinate = ARGeoCoordinate(location: CLLocation.init(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude),locationTitle:annotation.title) 148 | // // create marker 149 | // let markerForFlights = MarkerView(coordinate: arGeoCoordinate, delegate: self) 150 | // arGeoCoordinate.displayView = markerForFlights 151 | // //calibrate using altitude 152 | // arGeoCoordinate.calibrate(usingOrigin: userLocation) 153 | // 154 | // arController?.addCoordinate(arGeoCoordinate) 155 | // //print("User Location: ",userLocation) 156 | // flightAnnotationsGeo[annotation.title!] = arGeoCoordinate 157 | // } 158 | // } 159 | // } 160 | // 161 | // 162 | // // display data as it received in the map. 163 | // override func dataReceived(flightAnnotation : FlightAnnotation){ 164 | // flightAnnotations[flightAnnotation.title!] = flightAnnotation 165 | // self.setFlightAnnotations(annotations: Array(flightAnnotations.values)) 166 | // 167 | // let timeInterval:TimeInterval=Double(flightAnnotation.lastUpdatedInMillis)/1000 168 | // let arGeoCoordinate: ARGeoCoordinate = ARGeoCoordinate( 169 | // location: CLLocation.init(coordinate: flightAnnotation.coordinate, altitude: flightAnnotation.altitude, horizontalAccuracy: 0, verticalAccuracy: 0, course: flightAnnotation.heading , speed: flightAnnotation.speed, timestamp: NSDate(timeIntervalSinceNow: timeInterval) as Date), 170 | // locationTitle: flightAnnotation.title) 171 | // let markerForFlights = MarkerView(coordinate: arGeoCoordinate, delegate: self) 172 | // arGeoCoordinate.displayView = markerForFlights 173 | // //calibrate using altitude 174 | // arGeoCoordinate.calibrate(usingOrigin: userLocation) 175 | // 176 | // if let geoCoordinate: ARGeoCoordinate = flightAnnotationsGeo[flightAnnotation.title!]{ 177 | // //remove the coordinate. 178 | // geoCoordinate.displayView = nil 179 | // arController?.removeCoordinate(geoCoordinate) 180 | // } 181 | // 182 | // arController?.addCoordinate(arGeoCoordinate) 183 | // flightAnnotationsGeo[flightAnnotation.title!] = arGeoCoordinate 184 | // } 185 | // 186 | // // test flights 187 | // override func showTestFlights() { 188 | // self.setFlightAnnotations(annotations: Array(flightAnnotations.values)) 189 | // for annotation in annotations { 190 | // self.dataReceived(flightAnnotation: annotation) 191 | // } 192 | // } 193 | // 194 | // func addCompassView(){ 195 | // 196 | // let view = UIImageView() 197 | // view.frame = CGRect(x: 10, y: 10, width: 80, height: 78) 198 | // //view.backgroundColor = UIColor.white 199 | // view.image=UIImage(named: "compass.png") 200 | // self.view.addSubview(view) 201 | // self.compassImage=view 202 | // 203 | // } 204 | // 205 | // 206 | // //CLOSE button on AR flight view 207 | // func addCloseButton() 208 | // { 209 | // self.closeButton?.removeFromSuperview() 210 | // 211 | // if self.closeButtonImage == nil 212 | // { 213 | // let bundle = Bundle(for: ARFlightViewController.self) 214 | // let path = bundle.path(forResource: "close", ofType: "png") 215 | // if let path = path 216 | // { 217 | // self.closeButtonImage = UIImage(contentsOfFile: path) 218 | // } 219 | // } 220 | // 221 | // // Close button - make it customizable 222 | // let closeButton: UIButton = UIButton(type: UIButtonType.custom) 223 | // closeButton.setImage(closeButtonImage, for: UIControlState()); 224 | // closeButton.frame = CGRect(x: self.view.bounds.size.width - 45, y: 5,width: 40,height: 40) 225 | // closeButton.addTarget(self, action: #selector(ARFlightViewController.closeButtonTap), for: UIControlEvents.touchUpInside) 226 | // closeButton.autoresizingMask = [UIViewAutoresizing.flexibleLeftMargin, UIViewAutoresizing.flexibleBottomMargin] 227 | // self.view.addSubview(closeButton) 228 | // self.closeButton = closeButton 229 | // } 230 | // 231 | // internal func closeButtonTap() 232 | // { 233 | // self.presentingViewController?.dismiss(animated: true, completion: nil) 234 | // } 235 | // 236 | // 237 | // 238 | //} 239 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightCalloutView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 32 | 33 | 34 | 35 | 36 | 43 | 50 | 57 | 64 | 71 | 72 | 73 | 74 | 75 | 82 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /adsb.ground.station/README-ja.md: -------------------------------------------------------------------------------- 1 | *他の言語で読む: [English](README.md).* 2 | 3 | # adsb.ground.station (ADS-B 地上局) 4 | 5 | 以下を実施する商用フライトのための Raspberry Pi による ADS-B 地上局です: 6 | 7 | * _受信 (receives)_ : 1090MHz の周波数にチューニングされた Software-Defined Radio (SDR)デバイスを使用して、商用フライトによって放送される [Automatic Dependent Surveillance-Broadcast(ADS-B)](http://airfactsjournal.com/2013/01/ads-b-101-what-it-is-and-why-you-should-care/) メッセージを受信します。 8 | * _復号 (decodes)_ : 商用フライトからの ADS-B メッセージを復号します。 9 | * _発行 (publishes)_ : IBM Cloud (Bluemix) の [Internet of Things(IoT) Platform](https://console.ng.bluemix.net/catalog/services/internet-of-things-platform/?taxonomyNavigation=applications) に合致する [MQTT](http://mqtt.org/) メッセージを発行します。 10 | 11 | IBM Bluemix Cloud からの MQTT メッセージは、座標に基づいてデバイスに送信され、デバイスの近くのフライトを追跡することができます。 12 | 拡張現実 (AR: Augmented Reality) を使用した iOS のフライト追跡アプリの詳細は、こちらの [README](../ARFlightTracker-iOS-Swift/README-ja.md) を参照してください。 13 | 14 | USB2.0 インターフェースをサポートする [NooElec's RTL-SDR receiver set with antenna](http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) などの SDR デバイスは、適切な半径内であれば、商用飛行機によって平文で放送されているADS-Bメッセージを受信するために、USBポートを備えた任意のデバイスから実用的に使用することができます。 15 | 16 | この練習の一環として、[NooElec's RTL-SDR receiver set with antenna](http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) を接続した Raspberry Pi 3 は、商用フライトからの ADS-B メッセージを受信してデコードするだけでなく、対応する MQTT メッセージをIBM Cloud (Bluemix) IoT プラットフォームに公開するためにも使用されました。 17 | 18 | 以下のセクションでは、Raspberry Pi 3 をセットアップして、IBM Cloud上の商業フライトについての情報を公開できるようにする方法について詳しく説明します。このような Raspberry Pi による ADS-B 地上局は、世界中の航空機の 100-150 マイルの飛行を追跡することができます。IBM Cloud 上で先進的な航空交通管制を作成することで、現在の航空交通管制で使用されている時代遅れのレーダー技術を置き換えることが可能かもしれません。 19 | 20 | ## ハードウェア要件 21 | 22 | * Raspberry Pi 3 と 32GB 以上の容量の SD カード 23 | * [NooElec's RTL-SDR receiver set with antenna](http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) のような SDR デバイス 24 | 25 | ## ソフトウェア要件 26 | 27 | 前述のハードウェアに加えて、`adsb.ground.station` には以下のソフトウェアが必要です: 28 | 29 | * RTL-SDR USB ドライバー 30 | * SDR デバイスを 1090MHz の周波数に同調させ、データを収集し、それをポート `30002` から利用可能にする [Dump1090](https://github.com/MalcolmRobb/dump1090) デコーダ 31 | * Java SE Development Kit (JDK) 8 以上 32 | * Maven 3.2.5 以上 33 | 34 | ## Raspberry Pi 3 を設定する 35 | 36 | 新しい Raspberry Pi 3 を設定するのに役立つ多くのリソースがあります。この練習では、[こちら](https://www.raspberrypi.org/documentation/installation/noobs.md) で説明されている、32GB SDカードに配置した _New Out Of Box Software_ (NOOBS) を使用しました。 37 | 38 | ## ハードウェアを設定する 39 | 40 | 「百聞は一見に如かず」なので、ここではハードウェアの設定方法を示す画像を示します: 41 | 42 | ![Alt](images/rpi_sdr_config.jpg "Title1") 43 | 44 | 設定を完了するまでの手順は次のとおりです: 45 | 46 | * [NooElec's RTL-SDR receiver dongle](http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) にアンテナを接続します。 47 | * NooElec's RTL-SDR receiver を Raspberry Pi の USB ポートに接続します。 48 | * マイクロ USB ポートを使用して Raspberry Pi 3 の電源を入れます。 49 | 50 | これでおしまい! RTL-SDR receiver から ADS-B メッセージを受信する前に、Raspberry Pi 3 に `Dump1090 Server` という専用のソフトウェアをビルド、インストール、実行する必要があります。 51 | 52 | `Dump1090 Server` をビルドして実行するには2つのオプションがあります。 53 | [Dockerfile](Dockerfile) を使って、Docker コンテナに `Dump1090 Server` をビルドして実行するのに必要なすべてのソフトウェアが入っている Docker イメージを作成することもできますし、`Dump1090 Server` をラズベリーパイ3に直接インストールすることもできます。 54 | 以下のセクションでは、これらのアプローチの両方の手順を示します。 55 | 56 | ## Raspberry Pi 3 上の Docker コンテナで Dump1090 Server をビルドし実行する 57 | 58 | このリポジトリの一部である [Dockerfile](Dockerfile) を使用すると、Raspberry Pi 3 上で動作する Docker コンテナ内で `Dump1090 Server` をビルドして実行するのに必要なすべてのソフトウェアを含む Docker イメージを作成できます。 59 | 60 | これを達成するための手順は次のとおりです: 61 | 62 | ### Raspbian 環境を最新にする 63 | 64 | ``` 65 | $ sudo apt-get update 66 | $ sudo apt-get upgrade 67 | $ sudo apt-get dist-upgrade 68 | ``` 69 | 70 | ### Raspberry Pi 3 に Docker をインストールする 71 | 72 | Docker を Raspberry Pi 3 にインストールして設定する手順は [こちら](http://blog.alexellis.io/getting-started-with-docker-on-raspberry-pi/)。 73 | 74 | ### Dump1090 Server のための Docker イメージを作成する 75 | 76 | ``` 77 | $ git clone https://github.com/IBM/air-traffic-control 78 | $ cd air-traffic-control/adsb.ground.station 79 | $ docker build -t dump1090:1.0 . 80 | ``` 81 | 82 | Docker イメージが準備できたら、次のコマンドを使って `dump1090` の `image-id` を取得できます: 83 | 84 | ``` 85 | $ docker images dump1090 86 | ``` 87 | 88 | ### Docker コンテナと Dump1090 Server を開始する 89 | 90 | `Dump1090 Server` の Docker イメージが作成されたら、Docker コンテナを起動することができます。 91 | Docker コンテナは内部的に `Dump1090 Server` を起動します。 92 | 前のステップで取得した `image-id` を使ってコンテナを起動する手順は次のとおりです: 93 | 94 | ``` 95 | $ docker run -d --privileged -v /dev/bus/usb:/dev/bus/usb -p 30002:30002 96 | ``` 97 | このコマンドは指定されたイメージを使用してコンテナのプロセスを開始し、`container_id` をターミナルに表示します。 98 | 99 | 商用フライトから放送され、SDR から `Dump1090 Server` によって受信されている ADS-B メッセージのログを見るために、次のコマンドを使ってコンテナに接続することができます: 100 | 101 | ``` 102 | $ docker attach 103 | ``` 104 | 105 | ## Raspberry Pi 3 上で直接 Dump1090 Server をビルドし実行する 106 | 107 | このセクションでは、Docker を使用したくない場合に、必要なドライバと他のソフトウェアを Raspberry Pi 3 に直接インストールし、商用フライトから ADS-B メッセージを SDR で受信するための `Dump1090 Server` をビルドして実行する手順を詳しく説明します。 108 | 109 | Raspberry Pi 3 には HDMI ポートがあり、ディスプレイに接続することができます。 110 | ヘッドレスモードでも、他のマシンからの `ssh` で使うことができます。 111 | どちらのオプションを選択しても、実行する手順は次のとおりです。 112 | 113 | ### Raspbian 環境を最新にする 114 | ``` 115 | $ sudo apt-get update 116 | $ sudo apt-get upgrade 117 | $ sudo apt-get dist-upgrade 118 | ``` 119 | 120 | ### Build Essentials をインストールする 121 | ``` 122 | $ sudo apt-get install build-essential 123 | ``` 124 | 125 | ### Utils をインストールする 126 | ``` 127 | $ sudo apt-get install apt-utils 128 | $ sudo apt-get install usbutils 129 | $ sudo apt-get install pkg-config 130 | ``` 131 | 132 | ### `cmake` をインストールする 133 | ``` 134 | $ sudo apt-get install cmake 135 | ``` 136 | 137 | ### ドライバーのビルドに必要な USB ライブラリをインストールする 138 | ``` 139 | $ sudo apt-get install libusb-1.0-0-dev 140 | ``` 141 | 142 | ### git をインストールする 143 | ``` 144 | $ sudo apt-get install git-core git 145 | ``` 146 | 147 | ### RTL-SDR ドライバーをビルドしてインストールする 148 | ``` 149 | $ cd ~ 150 | $ git clone git://git.osmocom.org/rtl-sdr.git 151 | $ cd ~/rtl-sdr 152 | $ cmake ./ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON 153 | $ make 154 | $ sudo make install 155 | $ sudo ldconfig 156 | ``` 157 | 158 | ### [Dump1090 Server](https://github.com/MalcolmRobb/dump1090) をビルドする 159 | ``` 160 | $ cd ~ 161 | $ git clone https://github.com/MalcolmRobb/dump1090 162 | $ cd ~/dump1090 163 | $ make 164 | ``` 165 | 166 | Raspberry Pi 3 は、ADS-B地上局になる準備が整いました! 167 | 以下に示すように、USBポートに接続された SDR デバイスが Raspberry Pi 3 によって認識されているかどうかを確認します。 168 | 169 | ``` 170 | $ lsusb 171 | Bus 001 Device 005: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T 172 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 173 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 174 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 175 | ``` 176 | 177 | ### Dump1090 Server を実行する 178 | 179 | `Dump1090 Server` は以下のように開始できます: 180 | 181 | ``` 182 | $ cd ~/dump1090 183 | $ ./dump1090 --raw --net 184 | ``` 185 | 186 | デフォルトでは、`Dump1090 Server` は SDR を 1090MHz の周波数にチューニングし、`30002` ポートでクライアントからの TCP 接続を待ち受けます。 187 | 接続されたすべてのクライアントは、生の ADS-B メッセージを受信します。 188 | 189 | `adsb.ground.station` は、`Dump1090 Server` に接続して生の ADS-B メッセージを受信し、ADS-B メッセージを復号し、対応する MQTT メッセージを IBM Cloud の IoT プラットフォームに発行する TCP クライアントとして動作します。 190 | 191 | ## IBM IoT プラットフォームへのデバイス登録と API キーの生成 192 | 193 | このプロジェクトは IBM Cloud で実行されている IoT プラットフォームに MQTT メッセージを送信するので、IoTプラットフォームのダッシュボードから、 [レシピ](https://developer.ibm.com/recipes/tutorials/how-to-register-devices-in-ibm-iot-foundation/) に従って、次のことを実施します: 194 | 195 | * デバイスタイプとデバイス ID を登録する 196 | * アプリケーションの API キーと対応する認証トークンを生成する 197 | 198 | 登録されたデバイスには、独自の認証トークンが割り当てられます。 199 | ただしこの練習では、アプリの認証トークンを使用する **のみ** になります。 200 | 201 | 前述の手順を完了したら、次の情報が必要です: 202 | 203 | * Organization-ID=[あなたの組織 ID] 204 | * API-Key=[あなたのアプリの API キー] 205 | * Authentication-Token=**[あなたのアプリの認証トークン]** 206 | * Device-Type=[あなたのデバイスタイプ] 207 | * Device-ID=[あなたのデバイス ID] 208 | 209 | この情報は、このプロジェクト/リポジトリを使用して実行可能な jar をビルドする **前に**、 `application.properties` ファイルを更新する、次の一連のステップで使用されます。 210 | 211 | ## このプロジェクトをビルドする 212 | 213 | このプロジェクトをビルドするための最小要件は次のとおりです: 214 | 215 | * Java SE Development Kit (JDK) 8 以上: Raspbian イメージの一部として標準でインストールされます 216 | * Maven 3.2.5 以上 217 | 218 | このプロジェクトは Raspberry Pi 3 上で直接ビルドすることもできますが、通常のラップトップ/デスクトップ上で構築し、実行可能な jar ファイルを Raspberry Pi 3 にコピーすることをお勧めします。 219 | これは Raspberry Pi 3 ではリソースが限られているからです。 220 | 通常のラップトップ/デスクトップでこのプロジェクトをビルドすることで、Maven リポジトリを作成します。 221 | 222 | ## このプロジェクトをビルドする手順 223 | ``` 224 | $ cd ~ 225 | $ git clone --recursive https://github.com/IBM/air-traffic-control 226 | $ cd air-traffic-control 227 | ``` 228 | 前の手順で取得した情報で adsb.ground.station/src/main/resources/application.properties ファイルを更新します。 229 | これを行わないと、起動時に java.io.IOException が発生します。 230 | ``` 231 | $ mvn clean install 232 | ``` 233 | 234 | `adsb.ground.station/target` フォルダには、`src/main/resources/application.properties` ファイルに詳細を記載した IoT プラットフォームサービスを対象として設定された、実行可能な jar ファイル `adsb.ground.station-develop-SNAPSHOT.jar` が生成されています。 235 | 236 | ## `adsb.ground.station` クライアントを実行する 237 | 238 | ### Raspberry Pi 3 上の Dump1090 Server と共存させる 239 | 240 | 実行可能な jar がいったんビルドされたら、Raspberry Pi 3 で実行するのは非常に簡単です: 241 | 242 | ``` 243 | $ cd air-traffic-control/adsb.ground.station/target 244 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar 245 | ``` 246 | 247 | このコマンドを実行する前に `Dump1090 Server` が起動していることを確認しておいてください。 248 | 249 | ### クライアントとサーバーを別のホストで実行する 250 | 251 | `Dump1090 Server` and `adsb.ground.station` client can run on a separate hosts. For example, `Dump1090 Server` can run on Raspberry Pi 3 and the `adsb.ground.station` client can run on your laptop with additional command-line parameters as shown below: 252 | 253 | `Dump1090 Server` と `adsb.ground.station` クライアントは、別々のホスト上で実行させることができます。たとえば、 `Dump1090 Server` は Raspberry Pi 3 上で動作させ、`adsb.ground.station` クライアントはラップトップ上で以下のような追加のコマンドラインパラメータを使って実行できます: 254 | 255 | ``` 256 | $ cd air-traffic-control/adsb.ground.station/target 257 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar --host 192.168.1.10 --port 30002 258 | ``` 259 | 260 | 192.168.1.10 は `Dump1090 Server` が動作している Raspberry Pi 3 のIPアドレスです。 261 | 262 | このコマンドを実行する前に `Dump1090 Server` が起動していることを確認しておいてください。 263 | 264 | ### SDR をシミュレートする 265 | 266 | SDR や Raspberry Pi を所有していない場合は、ラップトップから `adsb.ground.station` クライアントを実行して、フライト追跡の仕組みを確認することができます。クライアントは、ADS-B メッセージをキャッシュしており、このメッセージは San Jose 地区で1時間の間にキャプチャされたもので、継続的に再生されます。`Dump1090 Server` を使わずにクライアントを走らせるには、以下のようにします: 267 | 268 | ``` 269 | $ cd air-traffic-control/adsb.ground.station/target 270 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar --simulate-sdr 271 | ``` 272 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/ARFlightViewController_v1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARFlightViewController_v1.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 1/9/17. 6 | // Copyright © 2017 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ARFlightViewControllerV1 : ViewController, ARViewDelegate,LocalizationDelegate 12 | { 13 | public func locationUnavailable() { 14 | // 15 | } 16 | 17 | @available(iOS 2.0, *) 18 | public func locationFound(_ location: CLLocation!) { 19 | super.userLocation = location 20 | } 21 | 22 | @available(iOS 3.0, *) 23 | public func headingFound(_ heading: CLHeading!) { 24 | self.rotateCompass(newHeading: fmod(heading.trueHeading, 360.0)) 25 | } 26 | 27 | 28 | 29 | fileprivate var closeButton: UIButton? 30 | fileprivate var compassImage: UIImageView! 31 | open var closeButtonImage: UIImage? 32 | { 33 | didSet 34 | { 35 | closeButton?.setImage(self.closeButtonImage, for: UIControlState()) 36 | } 37 | } 38 | 39 | init(){ 40 | super.init(nibName: nil, bundle: nil) 41 | super.currentView = self 42 | LocalizationHelper.shared().register(forUpdates: self, once: false) 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | } 48 | 49 | open func startFlightARView(){ 50 | super.currentView = self 51 | let config = ARKitConfig.defaultConfig(for: self) 52 | config?.useAltitude = true 53 | config?.orientation=UIApplication.shared.statusBarOrientation 54 | config?.scaleViewsBasedOnDistance = false 55 | 56 | // for radar display 57 | let s = UIScreen.main.bounds.size 58 | if UIApplication.shared.statusBarOrientation.isPortrait{ 59 | config?.radarPoint = CGPoint(x: s.width - 50, y: s.height - 50) 60 | }else{ 61 | config?.radarPoint = CGPoint(x: s.height - 50, y: s.width - 50) 62 | } 63 | 64 | arKitEngine = ARKitEngine.init(config: config) 65 | arKitEngine?.addCoordinates(points) 66 | self.addCompassView() 67 | self.addCloseButton() 68 | // 69 | arKitEngine?.startListening() 70 | } 71 | 72 | 73 | 74 | public func didChangeLooking(_ floorLooking: Bool) { 75 | if (floorLooking) { 76 | // The user has began looking at the floor 77 | print("floor looking") 78 | } else { 79 | // The user has began looking front 80 | print("not floor looking") 81 | } 82 | } 83 | 84 | public func itemTouched(with index: Int) { 85 | 86 | } 87 | 88 | 89 | public func view(for coordinate: ARGeoCoordinate!, floorLooking: Bool) -> ARObjectView! { 90 | var arObjectView : ARObjectView? = nil 91 | 92 | let annotation = coordinate.dataObject as! FlightAnnotation 93 | 94 | if floorLooking { 95 | arObjectView = ARObjectView.init() 96 | arObjectView?.displayed = false 97 | }else{ 98 | 99 | let flightBubbleView = FlightBubble() 100 | flightBubbleView.frame = CGRect(x: 0,y: 0,width: 170,height: 50) 101 | let arAnnotation = ARAnnotation() 102 | arAnnotation.title = annotation.title 103 | arAnnotation.altitude = annotation.altitude 104 | arAnnotation.coordinate = annotation.coordinate 105 | arAnnotation.radialDistance = coordinate.radialDistance.multiplied(by: 0.00062) 106 | 107 | flightBubbleView.annotation = arAnnotation 108 | flightBubbleView.annotation?.annotationView = flightBubbleView 109 | //flightBubbleView.loadUi() 110 | 111 | arObjectView = ARObjectView.init(frame: flightBubbleView.frame) 112 | arObjectView?.addSubview(flightBubbleView) 113 | 114 | } 115 | arObjectView?.sizeToFit() 116 | return arObjectView 117 | 118 | } 119 | 120 | 121 | func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { 122 | self.rotateCompass(newHeading: fmod(newHeading.trueHeading, 360.0)) 123 | } 124 | 125 | 126 | open func generateGeoCoordinatesFromFlightAnnotaiton(annotations: [FlightAnnotation]) 127 | { 128 | // Don't use annotations without valid location 129 | for annotation in annotations 130 | { 131 | if CLLocationCoordinate2DIsValid(annotation.coordinate) 132 | { 133 | let timeInterval:TimeInterval=Double(annotation.lastUpdatedInMillis)/1000 134 | 135 | let arGeoCoordinate: ARGeoCoordinate = ARGeoCoordinate( 136 | location: CLLocation.init(coordinate: annotation.coordinate, altitude: annotation.altitude, horizontalAccuracy: 0, verticalAccuracy: 0, course: annotation.heading , speed: annotation.speed, timestamp: NSDate(timeIntervalSinceNow: timeInterval) as Date)) 137 | arGeoCoordinate.dataObject = annotation 138 | arGeoCoordinate.calibrate(usingOrigin: userLocation, useAltitude: true) 139 | 140 | self.flightAnnotationsGeo[annotation.title!] = arGeoCoordinate 141 | 142 | self.points.append(arGeoCoordinate) 143 | } 144 | } 145 | } 146 | 147 | 148 | 149 | // test flights 150 | override func showTestFlights() { 151 | // self.setFlightAnnotations(annotations: Array(flightAnnotations.values)) 152 | // for annotation in annotations { 153 | // self.dataReceived(flightAnnotation: annotation) 154 | // } 155 | } 156 | 157 | 158 | override func dataReceived(flightAnnotation : FlightAnnotation){ 159 | let timeInterval:TimeInterval=Double(flightAnnotation.lastUpdatedInMillis)/1000 160 | 161 | let arGeoCoordinate: ARGeoCoordinate = ARGeoCoordinate( 162 | location: CLLocation.init(coordinate: flightAnnotation.coordinate, altitude: flightAnnotation.altitude, horizontalAccuracy: 0, verticalAccuracy: 0, course: flightAnnotation.heading , speed: flightAnnotation.speed, timestamp: NSDate(timeIntervalSinceNow: timeInterval) as Date)) 163 | arGeoCoordinate.dataObject = flightAnnotation 164 | arGeoCoordinate.calibrate(usingOrigin: userLocation, useAltitude: true) 165 | 166 | if let annGeo = flightAnnotationsGeo[flightAnnotation.title!]{ 167 | arKitEngine?.remove(annGeo) 168 | } 169 | 170 | // if let ann = flightAnnotations[flightAnnotation.title!] { 171 | // ann.annotationExpiryTimer?.invalidate() 172 | // } 173 | // 174 | // flightAnnotation.annotationExpiryTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(self.removeAnnotation), userInfo: flightAnnotation, repeats: true) 175 | // 176 | // flightAnnotations[flightAnnotation.title!] = flightAnnotation 177 | flightAnnotationsGeo[flightAnnotation.title!] = arGeoCoordinate 178 | arKitEngine?.add(arGeoCoordinate) 179 | } 180 | 181 | //runs every 30seconds 182 | @objc func removeAnnotation(timer: Timer) { 183 | let flightAnnotation = timer.userInfo as! FlightAnnotation 184 | if flightAnnotation.lastUpdatedInMillis != nil { 185 | let diff = Int64(Date().timeIntervalSince1970*1000) - flightAnnotation.lastUpdatedInMillis 186 | if(diff >= 5000) { 187 | flightAnnotation.annotationExpiryTimer?.invalidate() 188 | let timeInterval:TimeInterval=Double(flightAnnotation.lastUpdatedInMillis)/1000 189 | let arGeoCoordinate: ARGeoCoordinate = ARGeoCoordinate( 190 | location: CLLocation.init(coordinate: flightAnnotation.coordinate, altitude: flightAnnotation.altitude, horizontalAccuracy: 0, verticalAccuracy: 0, course: flightAnnotation.heading , speed: flightAnnotation.speed, timestamp: NSDate(timeIntervalSinceNow: timeInterval) as Date)) 191 | arGeoCoordinate.dataObject = flightAnnotation.callSign 192 | 193 | arKitEngine?.remove(arGeoCoordinate) 194 | } 195 | } 196 | } 197 | 198 | func addCompassView(){ 199 | let view = UIImageView() 200 | view.frame = CGRect(x: 10, y: 10, width: 80, height: 78) 201 | //view.backgroundColor = UIColor.white 202 | view.image=UIImage(named: "compass.png") 203 | self.compassImage=view 204 | 205 | self.arKitEngine?.addExtraView(view) 206 | 207 | } 208 | 209 | internal func rotateCompass(newHeading: Double) 210 | { 211 | self.compassImage.transform = CGAffineTransform(rotationAngle: newHeading.toRadians()) 212 | } 213 | 214 | 215 | //CLOSE button on AR flight view 216 | func addCloseButton() 217 | { 218 | self.closeButton?.removeFromSuperview() 219 | 220 | if self.closeButtonImage == nil 221 | { 222 | let bundle = Bundle(for: ARFlightViewControllerV1.self) 223 | let path = bundle.path(forResource: "close", ofType: "png") 224 | if let path = path 225 | { 226 | self.closeButtonImage = UIImage(contentsOfFile: path) 227 | } 228 | } 229 | 230 | // Close button - make it customizable 231 | let closeButton: UIButton = UIButton(type: UIButtonType.custom) 232 | closeButton.setImage(closeButtonImage, for: UIControlState()); 233 | closeButton.frame = CGRect(x: self.view.bounds.size.width - 45, y: 5,width: 40,height: 40) 234 | closeButton.addTarget(self, action: #selector(ARFlightViewControllerV1.closeAR), for: UIControlEvents.touchUpInside) 235 | closeButton.autoresizingMask = [UIViewAutoresizing.flexibleLeftMargin, UIViewAutoresizing.flexibleBottomMargin] 236 | self.closeButton=closeButton 237 | self.arKitEngine?.addExtraView(closeButton) 238 | } 239 | 240 | internal func closeButtonTap() 241 | { 242 | self.presentingViewController?.dismiss(animated: true, completion: nil) 243 | } 244 | 245 | func closeAR() { 246 | arKitEngine?.hide() 247 | } 248 | 249 | /* 250 | * returning bubble view for AR 251 | */ 252 | func createCalloutView() -> FlightBubbleV1 { 253 | let views = Bundle.main.loadNibNamed("FlightBubbleV1", owner: nil, options: nil) 254 | let bubbleView = views?[0] as! FlightBubbleV1 255 | return bubbleView 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ARFlightTracker-iOS-Swift/ARFlightTracker-iOS-Swift/FlightMapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightMapViewController.swift 3 | // IBMFlightTracker 4 | // 5 | // Created by Sanjeev Ghimire on 12/5/16. 6 | // Copyright © 2016 Sanjeev Ghimire. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import MapKit 12 | import SwiftyJSON 13 | 14 | class FlightMapViewController: ViewController, MKMapViewDelegate { 15 | 16 | @IBOutlet weak var mapView: MKMapView! 17 | 18 | @IBOutlet weak var mapARSegmentControl: UISegmentedControl! 19 | 20 | let annotationIdentifier = "AnnotationIdentifier" 21 | 22 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) 23 | { 24 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 25 | } 26 | 27 | required public init?(coder aDecoder: NSCoder) 28 | { 29 | super.init(coder: aDecoder) 30 | } 31 | 32 | fileprivate func onViewWillAppear() 33 | { 34 | self.mapARSegmentControl.selectedSegmentIndex = 0 35 | } 36 | 37 | 38 | override func viewDidLoad() 39 | { 40 | super.currentView = self 41 | super.viewDidLoad() 42 | 43 | //self.locationManager.delegate = self 44 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest 45 | self.locationManager.requestWhenInUseAuthorization() 46 | self.locationManager.startUpdatingLocation() 47 | self.mapView.delegate = self 48 | self.mapView.showsCompass = true 49 | self.mapView.showsUserLocation = true 50 | } 51 | 52 | open override func viewDidAppear(_ animated: Bool) 53 | { 54 | self.mapARSegmentControl.selectedSegmentIndex = 0 55 | super.currentView = self 56 | self.locationManager.delegate = self 57 | self.reloadAnnotations() 58 | super.viewDidAppear(animated) 59 | } 60 | 61 | //redraw all annotation again 62 | func reloadAnnotations(){ 63 | let allAnnotations = self.mapView.annotations 64 | self.mapView.removeAnnotations(allAnnotations) 65 | 66 | for annotation in flightAnnotations.values { 67 | self.mapView.addAnnotation(annotation) 68 | } 69 | } 70 | 71 | 72 | open override func viewDidDisappear(_ animated: Bool) 73 | { 74 | super.viewDidDisappear(animated) 75 | } 76 | 77 | // display data as it received in the map. 78 | override func dataReceived(flightAnnotation : FlightAnnotation){ 79 | if let ann = flightAnnotations[flightAnnotation.title!]{ 80 | UIView.animate(withDuration: 0.01) { 81 | ann.annotationExpiryTimer?.invalidate() 82 | self.mapView.removeAnnotation(ann) 83 | if(ann.calloutOpen){ 84 | self.addFlightAnnotationWithCallout(flightAnnotation: ann) 85 | ann.calloutOpen=true 86 | } 87 | flightAnnotation.annotationExpiryTimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.removeAnnotation), userInfo: flightAnnotation, repeats: true) 88 | 89 | self.mapView.addAnnotation(flightAnnotation) 90 | } 91 | } else { 92 | self.mapView.addAnnotation(flightAnnotation) 93 | } 94 | flightAnnotations[flightAnnotation.title!]=flightAnnotation 95 | } 96 | 97 | 98 | 99 | //runs every 30seconds 100 | @objc private func removeAnnotation(timer: Timer) { 101 | let flightAnnotation = timer.userInfo as! FlightAnnotation 102 | if flightAnnotation.lastUpdatedInMillis != nil { 103 | let diff = Int64(Date().timeIntervalSince1970*1000) - flightAnnotation.lastUpdatedInMillis 104 | if(diff >= 5000) { 105 | flightAnnotation.annotationExpiryTimer?.invalidate() 106 | flightAnnotations.removeValue(forKey: flightAnnotation.title!) 107 | self.mapView.removeAnnotation(flightAnnotation) 108 | } 109 | } 110 | } 111 | 112 | 113 | override func showTestFlights(){ 114 | for flightAnnotation in Array(flightAnnotations.values) { 115 | self.mapView.addAnnotation(flightAnnotation) 116 | } 117 | } 118 | 119 | 120 | // swich between AR and map view 121 | @IBAction func ARMapViewLoader(_ sender: UISegmentedControl) { 122 | if(sender.selectedSegmentIndex == 0){ 123 | showMapViewController() 124 | }else{ 125 | //load ar view controller. 126 | let arFlightViewController = ARFlightViewControllerV1() 127 | arFlightViewController.generateGeoCoordinatesFromFlightAnnotaiton(annotations: Array(flightAnnotations.values)) 128 | arFlightViewController.startFlightARView() 129 | } 130 | } 131 | 132 | func showMapViewController(){ 133 | let bundle = Bundle(for: FlightMapViewController.self) 134 | let mapViewController = FlightMapViewController(nibName: "FlightMapViewController", bundle: bundle) 135 | self.present(mapViewController, animated: true, completion: nil) 136 | } 137 | 138 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 139 | { 140 | let location = locations.last 141 | 142 | let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude) 143 | 144 | let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)) 145 | 146 | self.mapView.setRegion(region, animated: true) 147 | self.mapView.regionThatFits(region) 148 | 149 | super.userLocation = location 150 | 151 | // stop updating current location 152 | self.locationManager.stopUpdatingLocation() 153 | 154 | } 155 | 156 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 157 | if !(annotation is FlightAnnotation) { 158 | return nil 159 | } 160 | 161 | var annotationView: MKAnnotationView? 162 | if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) { 163 | annotationView = dequeuedAnnotationView 164 | annotationView?.annotation = annotation 165 | } 166 | else { 167 | annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier) 168 | annotationView?.canShowCallout=false 169 | } 170 | 171 | let flightAnnotation = annotation as! FlightAnnotation 172 | 173 | if let annotationView = annotationView { 174 | // Configure your annotation view here 175 | annotationView.canShowCallout = false 176 | annotationView.image = flightAnnotation.image 177 | } 178 | 179 | return annotationView 180 | } 181 | 182 | func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) 183 | { 184 | // 1 185 | if view.annotation is MKUserLocation 186 | { 187 | // Don't proceed with custom callout 188 | return 189 | } 190 | // 2 191 | let flightAnnotation = view.annotation as! FlightAnnotation 192 | 193 | let calloutView = self.createCalloutView(flightAnnotation: flightAnnotation) 194 | 195 | //saving the callout view 196 | flightAnnotation.calloutView=calloutView 197 | flightAnnotation.calloutOpen=true 198 | // update the flight annotations map 199 | flightAnnotations[flightAnnotation.title!]=flightAnnotation 200 | 201 | 202 | //weather data 203 | 204 | RestCall().fetchWeatherBasedOnCurrentLocation(latitude: String(flightAnnotation.coordinate.latitude),longitude: String(flightAnnotation.coordinate.longitude)){ 205 | (result: [String: Any]) in 206 | 207 | let weatherIconUrl: String = result["weatherIconUrl"] as! String 208 | let imageUrl = URL(string: weatherIconUrl) 209 | let data = try? Data(contentsOf: imageUrl!) 210 | 211 | DispatchQueue.main.async(execute: { () -> Void in 212 | calloutView.currentCity.text = "\(result["city"]!)" 213 | calloutView.weatherIcon.image = UIImage(data: data!) 214 | calloutView.currentTemprature.text = "\(result["temperature"]!)°F" 215 | calloutView.weatherDescription.text = "\(result["description"]!)" 216 | }) 217 | 218 | 219 | } 220 | 221 | 222 | calloutView.center = CGPoint(x: view.bounds.size.width / 2, y: -calloutView.bounds.size.height*0.52) 223 | view.addSubview(calloutView) 224 | mapView.setCenter((view.annotation?.coordinate)!, animated: true) 225 | 226 | 227 | } 228 | 229 | func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { 230 | if view.isKind(of: MKAnnotationView.self) 231 | { 232 | for subview in view.subviews 233 | { 234 | subview.removeFromSuperview() 235 | } 236 | } 237 | } 238 | 239 | 240 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) 241 | { 242 | print("Errors: " + error.localizedDescription) 243 | } 244 | 245 | override func didReceiveMemoryWarning() { 246 | super.didReceiveMemoryWarning() 247 | // Dispose of any resources that can be recreated. 248 | } 249 | 250 | /// This method is called by ARViewController, make sure to set dataSource property. 251 | func ar(_ arViewController: ARFlightViewControllerV1, viewForAnnotation: FlightAnnotation) -> ARFlightAnnotationView 252 | { 253 | // Annotation views should be lightweight views, try to avoid xibs and autolayout all together. 254 | let annotationView = ARFlightAnnotationViewCustom() 255 | annotationView.frame = CGRect(x: 0,y: 0,width: 150,height: 50) 256 | return annotationView; 257 | } 258 | 259 | 260 | func addFlightAnnotationWithCallout(flightAnnotation: FlightAnnotation){ 261 | let view = MKAnnotationView(annotation: flightAnnotation, reuseIdentifier: annotationIdentifier) 262 | view.canShowCallout=false 263 | var fcView = flightAnnotation.calloutView as FlightCalloutView? 264 | if(fcView == nil){ 265 | fcView = self.createCalloutView(flightAnnotation: flightAnnotation) 266 | } 267 | fcView!.center = CGPoint(x: view.bounds.size.width / 2, y: -fcView!.bounds.size.height*0.52) 268 | view.addSubview(fcView!) 269 | self.mapView.setCenter((view.annotation?.coordinate)!, animated: true) 270 | } 271 | 272 | func createCalloutView(flightAnnotation: FlightAnnotation) -> FlightCalloutView { 273 | let views = Bundle.main.loadNibNamed("FlightCalloutView", owner: nil, options: nil) 274 | let calloutView = views?[0] as! FlightCalloutView 275 | calloutView.flightName.text = flightAnnotation.callSign 276 | calloutView.altitudeInMeters.text = "\(flightAnnotation.altitude.roundTo(places: 2)) m" 277 | calloutView.velocityInMetersPerSecond.text = "\(flightAnnotation.speed.roundTo(places: 2)) m/s" 278 | calloutView.flightImage.image=flightAnnotation.image 279 | 280 | return calloutView; 281 | } 282 | 283 | 284 | } 285 | 286 | func showActivityIndicatory(uiView: UIView, actInd: UIActivityIndicatorView) { 287 | actInd.frame = CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0); 288 | actInd.center = uiView.center 289 | actInd.hidesWhenStopped = true 290 | actInd.activityIndicatorViewStyle = .gray 291 | uiView.addSubview(actInd) 292 | actInd.startAnimating() 293 | } 294 | 295 | extension Double { 296 | func toRadians() -> CGFloat { 297 | return CGFloat(self * .pi / 180.0) 298 | } 299 | 300 | /// Rounds the double to decimal places value 301 | func roundTo(places:Int) -> Double { 302 | let divisor = pow(10.0, Double(places)) 303 | return (self * divisor).rounded() / divisor 304 | } 305 | } 306 | 307 | extension UIImage { 308 | func rotated(by degrees: Double, flipped: Bool = false) -> UIImage? { 309 | guard let cgImage = self.cgImage else { return nil } 310 | 311 | let transform = CGAffineTransform(rotationAngle: degrees.toRadians()) 312 | var rect = CGRect(origin: .zero, size: self.size).applying(transform) 313 | rect.origin = .zero 314 | 315 | let renderer = UIGraphicsImageRenderer(size: rect.size) 316 | return renderer.image { renderContext in 317 | renderContext.cgContext.translateBy(x: rect.midX, y: rect.midY) 318 | renderContext.cgContext.rotate(by: degrees.toRadians()) 319 | renderContext.cgContext.scaleBy(x: flipped ? -1.0 : 1.0, y: -1.0) 320 | 321 | let drawRect = CGRect(origin: CGPoint(x: -self.size.width/2, y: -self.size.height/2), size: self.size) 322 | renderContext.cgContext.draw(cgImage, in: drawRect) 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /adsb.ground.station/README.md: -------------------------------------------------------------------------------- 1 | *Read this in other languages: [日本](README-ja.md).* 2 | 3 | # adsb.ground.station 4 | 5 | Raspberry Pi powered Ground Station for commercial flights that does the following: 6 | 7 | * _receives_ [Automatic Dependent Surveillance-Broadcast(ADS-B)] 8 | (http://airfactsjournal.com/2013/01/ads-b-101-what-it-is-and-why-you-should-care/) messages 9 | broadcasted by commercial flights using a Software-Defined Radio(SDR) device tuned to 1090 MHz 10 | frequency 11 | * _decodes_ ADS-B messages from commercial flights 12 | * _publishes_ corresponding [MQTT](http://mqtt.org/) messages to [Bluemix Internet of Things(IoT) Platform] 13 | (https://console.ng.bluemix.net/catalog/services/internet-of-things-platform/?taxonomyNavigation=applications) 14 | in IBM's Bluemix Cloud. 15 | 16 | The MQTT messages from the IBM Bluemix Cloud can then be transmitted over to devices, based on their 17 | coordinates, to be able to track flights in the vicinity of the device. Details of iOS flight tracking 18 | app using Augmented Reality are in it's own [README.md](../ARFlightTracker-iOS-Swift/README.md). 19 | 20 | SDR devices such as [NooElec's RTL-SDR receiver set with antenna] 21 | (http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html), which support USB 2.0 22 | interface, can be used to receive ADS-B messages that are being broadcasted in clear text by 23 | commercial flights, within a decent radius, on practically any device with a USB port. 24 | 25 | As part of this exercise, a Raspberry Pi 3 along with [NooElec's RTL-SDR receiver set with antenna] 26 | (http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) were used to not only 27 | receive ADS-B messages from commercial flights and decode them but also to publish corresponding 28 | MQTT messages to IBM Bluemix IoT Platform. 29 | 30 | The following sections describe in great detail how to set up Raspberry Pi 3 so that it can publish 31 | information about commercial flights in it's vicinity over IBM Bluemix. One can imagine a whole bunch 32 | of such Raspberry Pi powered ADS-B Ground Stations that can track aircraft in 100-150mile radius 33 | scattered all over the world publishing messages to create modern Air Traffic Controls in 34 | IBM Bluemix and perhaps replacing the out-dated radar technology that is currently used by the 35 | conventional Air Traffic Controls. 36 | 37 | ## Hardware Requirements 38 | * Raspberry Pi 3 with at least 32GB SD card 39 | * SDR device such as [NooElec's RTL-SDR receiver set with antenna] 40 | (http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html) 41 | 42 | ## Software Requirements 43 | In addition to the aforementioned hardware, `adsb.ground.station` requires the following software: 44 | 45 | * RTL-SDR USB driver 46 | * [Dump1090](https://github.com/MalcolmRobb/dump1090) decoder that tunes the SDR device to 1090MHz 47 | frequency, collects the data, and makes it available on port `30002`. 48 | * Java SE Development Kit (JDK) 8 or higher 49 | * Maven 3.2.5 higher 50 | 51 | ## Getting Raspberry Pi 3 Ready 52 | There are a lot of resources that can help you with getting your brand new Raspberry Pi 3 ready. For 53 | this exercise, the _New Out Of Box Software_(NOOBS) on a 32GB SD card was used as described 54 | [here](https://www.raspberrypi.org/documentation/installation/noobs.md). 55 | 56 | ## Configuring the Hardware 57 | Since a picture is worth a thousand words, here is the picture that shows how the hardware needs to 58 | be configured: 59 | 60 | ![Alt](images/rpi_sdr_config.jpg "Title1") 61 | 62 | Here are the steps to accomplish the configuration: 63 | 64 | * Attach the antenna to the [NooElec's RTL-SDR receiver dongle] 65 | (http://www.nooelec.com/store/sdr/sdr-receivers/nesdr-mini-2-plus.html). 66 | * Plug in NooElec's RTL-SDR receiver into the Raspberry Pi's USB port. 67 | * Power up Raspberry Pi 3 using the micro-USB port. 68 | 69 | That's it! Before we can receive ADS-B messages from RTL-SDR receiver, we have to build, install, 70 | and run specialized software called `Dump1090 Server` on Raspberry Pi 3. 71 | 72 | There are two options to build and run `Dump1090 Server`. You can either use the 73 | [Dockerfile](Dockerfile) to create a Docker image that would in turn contain all the necessary 74 | software to build and run `Dump1090 Server` in a Docker container or you can install the software 75 | that let's you build and run `Dump1090 Server` directly on your Raspberry Pi 3. The following 76 | sections provide steps for both of these approaches. 77 | 78 | ## Using Docker on Raspberry Pi 3 to build and run Dump1090 Server 79 | 80 | Using the [Dockerfile](Dockerfile) that is part of this repository, you can create a Docker image 81 | that would contain all the necessary software to build and run `Dump1090 Server` from within the 82 | Docker container running on Raspberry Pi 3. 83 | 84 | Here are the steps to accomplish this: 85 | 86 | ### Upgrade to the latest Raspbian image 87 | ``` 88 | $ sudo apt-get update 89 | $ sudo apt-get upgrade 90 | $ sudo apt-get dist-upgrade 91 | ``` 92 | 93 | ### Install Docker on Raspberry Pi 3 94 | Instructions to install and configure Docker on Raspberry Pi 3 are 95 | [here](http://blog.alexellis.io/getting-started-with-docker-on-raspberry-pi/). 96 | 97 | ### Create Docker Image for Dump1090 Server 98 | 99 | ``` 100 | $ git clone https://github.com/IBM/air-traffic-control 101 | $ cd air-traffic-control/adsb.ground.station 102 | $ docker build -t dump1090:1.0 . 103 | ``` 104 | 105 | Once the Docker image is ready, you can retrieve the `image-id` for `dump1090`, using the following command: 106 | 107 | ``` 108 | $ docker images dump1090 109 | ``` 110 | 111 | ### Start Docker Container and Dump1090 Server 112 | 113 | Once the Docker image for `Dump1090 Server` has been created, you can start a Docker container. The 114 | Docker container will launch `Dump1090 Server` internally. Here is the step to start the container using `image-id` from the previous command: 115 | 116 | ``` 117 | $ docker run -d --privileged -v /dev/bus/usb:/dev/bus/usb -p 30002:30002 118 | ``` 119 | This command will use the specified image to start a container process and print the `container_id` 120 | in the Terminal. 121 | 122 | In order to see the log of ADS-B messages broadcasted by the commercial flights that are being 123 | received by the `Dump1090 Server` from the SDR, you can attach to the container using the following 124 | command: 125 | 126 | ``` 127 | $ docker attach 128 | ``` 129 | 130 | ## Building and Running Dump1090 Server directly on Raspberry Pi 3 131 | 132 | If you don't want to use Docker, then this section describes in detail all the steps that you should 133 | perform to install the necessary drivers and other software directly on Raspberry Pi 3 to be able to 134 | build and run `Dump1090 Server` that would allow the SDR to receive ADS-B messages from the commercial 135 | flights. 136 | 137 | Raspberry Pi 3 comes with a HDMI port that can be used to connect a display. You can also use it in 138 | headless mode and `ssh` from your other machine. Regardless of which option you choose, here are the 139 | sequence of steps that you should perform: 140 | 141 | 142 | ### Upgrade to the latest Raspbian image 143 | ``` 144 | $ sudo apt-get update 145 | $ sudo apt-get upgrade 146 | $ sudo apt-get dist-upgrade 147 | ``` 148 | 149 | ### Install Build Essentials 150 | ``` 151 | $ sudo apt-get install build-essential 152 | ``` 153 | 154 | ### Install Utils 155 | ``` 156 | $ sudo apt-get install apt-utils 157 | $ sudo apt-get install usbutils 158 | $ sudo apt-get install pkg-config 159 | ``` 160 | 161 | ### Install `cmake` 162 | ``` 163 | $ sudo apt-get install cmake 164 | ``` 165 | 166 | ### Install USB library needed to build drivers 167 | ``` 168 | $ sudo apt-get install libusb-1.0-0-dev 169 | ``` 170 | 171 | ### Install git 172 | ``` 173 | $ sudo apt-get install git-core git 174 | ``` 175 | 176 | ### Build and Install RTL-SDR Drivers 177 | ``` 178 | $ cd ~ 179 | $ git clone git://git.osmocom.org/rtl-sdr.git 180 | $ cd ~/rtl-sdr 181 | $ cmake ./ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON 182 | $ make 183 | $ sudo make install 184 | $ sudo ldconfig 185 | ``` 186 | 187 | ### Build [Dump1090 Server](https://github.com/MalcolmRobb/dump1090) 188 | ``` 189 | $ cd ~ 190 | $ git clone https://github.com/MalcolmRobb/dump1090 191 | $ cd ~/dump1090 192 | $ make 193 | ``` 194 | 195 | Now your Raspberry Pi 3 is ready to be a ADS-B Ground Station! You can confirm whether the SDR 196 | device connected to the USB port is being recognized by Raspberry Pi 3 as shown below: 197 | 198 | ``` 199 | $ lsusb 200 | Bus 001 Device 005: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T 201 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter 202 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 203 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 204 | ``` 205 | 206 | ### Run Dump1090 Server 207 | 208 | `Dump1090 Server` can be started as shown below: 209 | 210 | ``` 211 | $ cd ~/dump1090 212 | $ ./dump1090 --raw --net 213 | ``` 214 | 215 | By default, `Dump1090 Server` will tune the SDR to 1090MHz frequency and listen for client TCP 216 | connections on port `30002`. All the connected clients will receive the raw ADS-B messages. 217 | 218 | `adsb.ground.station` will act as TCP client that will connect to the `Dump1090 Server` to 219 | receive raw ADS-B messages, decode the ADS-B messages, and publish corresponding MQTT messages 220 | to IoT Platform in IBM Bluemix Cloud. 221 | 222 | ## Registering Device and Generating API Key with IBM IoT Platform 223 | Since this project will be sending MQTT messages to the IoT Platform running in IBM Bluemix Cloud, 224 | follow the steps in this 225 | [recipe](https://developer.ibm.com/recipes/tutorials/how-to-register-devices-in-ibm-iot-foundation/) 226 | from within the IoT Platform's dashboard to do the following: 227 | * Register a device-type and device-id 228 | * Generate API Key and the corresponding Authentication Token for the app 229 | 230 | Note that the registered device will have it's own Authentication Token. However, in this exercise, 231 | we will be **only** using the the Authentication Token of the app. 232 | 233 | You should have the following information when you are done with the aforementioned steps: 234 | 235 | * Organization-ID=[Your Organization ID] 236 | * API-Key=[Your Apps' API-Key] 237 | * Authentication-Token=**[Your Apps' Authentication Token]** 238 | * Device-Type=[Your Device Type] 239 | * Device-ID=[Your Device ID] 240 | 241 | This information will be used in the next set of steps when updating `application.properties` file 242 | **before** building the executable jar using this project/repo. 243 | 244 | ## Building this project 245 | Minimum requirements for building this project are: 246 | 247 | * Java SE Development Kit (JDK) 8 or higher: Installed by default as part of Raspbian image 248 | * Maven 3.2.5 or higher 249 | 250 | Even though this project can be built directly on Raspberry Pi 3, it is advisible to build it on a 251 | regular laptop/desktop and then copy over the executable jar to Raspberry Pi 3. This is because 252 | Raspberry Pi 3 has limited resources and you can avoid creating the Maven repository on it by 253 | building this project on a regular laptop/desktop. 254 | 255 | ## Steps for building this project 256 | ``` 257 | $ cd ~ 258 | $ git clone --recursive https://github.com/IBM/air-traffic-control 259 | $ cd air-traffic-control 260 | Update adsb.ground.station/src/main/resources/application.properties file using the information obtained from the 261 | earlier step. Failure to do this will result in java.io.IOException during startup. 262 | $ mvn clean install 263 | ``` 264 | 265 | The `adsb.ground.station/target` folder will contain the executable jar 266 | `adsb.ground.station-develop-SNAPSHOT.jar` specifically targeted for the IoT Platform service 267 | and the device whose details are specified in `src/main/resources/application.properties` file. 268 | 269 | ## Running `adsb.ground.station` Client 270 | 271 | ### Co-located with Dump1090 Server on Raspberry Pi 3 272 | 273 | Once the executable jar is built, running it on Raspberry Pi 3 is very simple: 274 | 275 | ``` 276 | $ cd air-traffic-control/adsb.ground.station/target 277 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar 278 | ``` 279 | 280 | Make sure that the `Dump1090 Server` is up and running before running the aforementioned command. 281 | 282 | ### Client and Server Running on Separate Hosts 283 | 284 | `Dump1090 Server` and `adsb.ground.station` client can run on a separate hosts. For example, 285 | `Dump1090 Server` can run on Raspberry Pi 3 and the `adsb.ground.station` client can run on your 286 | laptop with additional command-line parameters as shown below: 287 | 288 | ``` 289 | $ cd air-traffic-control/adsb.ground.station/target 290 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar --host 192.168.1.10 --port 30002 291 | ``` 292 | 293 | where 192.168.1.10 is the IP address of the Raspberry Pi 3 on which `Dump1090 Server` is running. 294 | 295 | Make sure that the `Dump1090 Server` is up and running before running the aforementioned command. 296 | 297 | ### Simulate SDR 298 | 299 | If you don't have a SDR or a Raspberry Pi, you can still run `adsb.ground.station` client from your 300 | laptop to see how the flight tracking works. The client has cached ADS-B messages, which were 301 | captured over a period of an hour from San Jose area, and plays them continually. To run the client 302 | without `Dump1090 Server`, you can do the following: 303 | 304 | ``` 305 | $ cd air-traffic-control/adsb.ground.station/target 306 | $ java -cp . -jar adsb.ground.station-develop-SNAPSHOT.jar --simulate-sdr 307 | ``` 308 | --------------------------------------------------------------------------------