2P by GN⁺ | ★ favorite | 댓글 1개
  • 두 살 딸이 비행기에 빠진 뒤, 주변 항공기를 휴대폰 레이더처럼 보여주는 iOS 앱 Aviator를 몇 저녁 동안 사이드 프로젝트로 만듦
  • FlightRadar24는 항공기 위치를 지도에 보여주지만, 실제 하늘에서 어느 방향을 봐야 하는지와 아이가 찾기 쉬운 비행기가 무엇인지는 따로 판단해야 했음
  • 앱은 Core Location 방향 정보, OpenSky Network의 비상업용 REST API, MapKit 주석, SwiftUI UI를 묶어 항공기를 기기 방향에 맞춰 표시함
  • MVP 테스트에서 실제 비행기와 앱 표시가 맞는 것을 확인한 뒤, 지도 숨김, 낮은 고도 항공기 확대, 레이더 애니메이션, CRT 효과, 사운드·햅틱, 색상 설정을 더함
  • 최종 앱은 App Store에 공개됐고, 이후 개선 후보로 줌 레벨, OpenSky 고급 API, 출발·도착 국가 표시, Metal 셰이더 개선, 거리·고도 필터, “zany mode”가 남아 있음

비행기 관찰에서 앱 요구사항으로

  • 딸은 여름 해외여행에서 3시간 비행을 앞두고 비행기 여행을 기대했고, 탑승 후 승무원의 안내로 조종석을 구경한 뒤 비행기에 더 빠짐
  • 이후 하늘의 비행기를 찾아 달라고 자주 요청했고, 정원에서 한 시간 동안 어깨에 태운 채 저녁 하늘의 비행기를 찾는 일까지 생김
  • FlightRadar24는 지도 위 항공기 위치를 보여줬지만, 실제 하늘에서 어느 방향을 봐야 하는지는 사용자가 직접 맞춰야 했음
  • 2차원 지도에서는 40,000피트의 Learjet과 London City Airport에서 막 이륙한 AirBus가 비슷하게 보일 수 있지만, 실제 하늘에서는 낮은 고도의 큰 항공기가 더 찾기 쉬움
  • 두 살 아이에게 필요한 것은 지도 해석이 아니라 단순한 비행기 찾기였음
  • 초기 문제는 세 가지로 정리됨
    • 방향 맞춤: 기기 방향에 따라 항공기 위치가 실제 하늘 방향과 대응해야 함
    • 크기 표현: 항공기 고도에 따라 화면 크기가 달라져야 함
    • 사용성: 업무용 비행 추적 앱이 아니라 복고풍 장난감 같은 앱이어야 함

Aviator의 초기 설계

  • 앱 아이디어는 “주변 항공편을 레이더에 표시”하는 것이었고, 프로젝트 이름은 Aviator로 정함
  • 핵심 요구사항은 세 가지임
    • 기기가 회전하면 화면도 함께 돌아 항공기가 올바른 방향에 표시됨
    • 항공기 고도에 따라 화면상 크기가 달라짐
    • 아이가 쓰기 좋은 재미있는 복고풍 장난감 느낌을 냄
  • 방향 처리는 iOS Core Location API의 heading 업데이트를 사용함
    • CLLocationManagerDelegatedidUpdateHeading에서 기기 방향을 받아옴
    • Combine의 CurrentValueSubject로 SwiftUI 뷰에 방향 값을 전달함
  • 항공 데이터는 OpenSky Network를 사용함
    • 단순 REST API로 특정 위도·경도 범위의 실시간 항공 데이터를 제공함
    • 비상업적 사용은 무료임
    • 현실적인 레이더 스윕을 위해 몇 초마다 endpoint를 호출하려는 구상임
  • 위치 데이터는 Core Location에서 얻고, 사용자 위치 주변의 위도·경도 범위를 쿼리함
    • 초기 구상에서는 위도 ±1도, 위치 정밀도 0.1도 약 10km로 사용자 위치를 충분히 흐리게 하려 함
    • 실제 API 예시 코드에서는 영국 위도에서 대략 정사각형이 되도록 위도 0.5도, 경도 1도 범위를 사용함
  • OpenSky Network API가 비상업용으로 제한되어 있고 앱도 상업 서비스가 아니어서, SwiftUI에서는 단순한 MV 구조를 쓰고 API·Location 같은 핵심 서비스만 분리함

PoC: 방향, 항공 데이터, 지도 검증

  • 방향 PoC는 LocationManager 싱글턴으로 위치 권한 요청, delegate 설정, heading 업데이트 시작을 처리함
  • SwiftUI 뷰는 .onReceiverotationAngleSubject를 구독하고, 사각형들을 원형으로 배치해 나침반처럼 회전시키는 효과를 구현함
  • 테스트 기기에서는 실제 위치 변화에 잘 반응했지만, 0도와 360도를 별개 값으로 다루는 애니메이션 때문에 진북을 지날 때 사각형들이 한 바퀴 도는 시각적 글리치가 생김
  • OpenSky REST API는 위도·경도 범위를 지정한 GET 요청으로 지역 항공편 배열을 반환함
    • 예시 endpoint는 states/alllamin, lamax, lomin, lomax 파라미터를 붙이는 방식임
    • REST API 문서는 잘 되어 있지만, 응답이 key 없는 구조라 JSON 필드를 순서대로 파싱해야 했음
    • Swift의 UnkeyedContainericao24, callsign, origin_country, time_position, last_contact, longitude, latitude 등을 디코딩함
  • 항공기 그리기의 첫 시도는 사용자 위치와 항공기 위도·경도 차이에 고정 화면 배율을 곱해 SF Symbol airplane 이미지를 배치하는 방식이었음
    • 위도·경도 1도의 실제 거리는 위치에 따라 달라져 정확할 수 없음
    • 그래도 시작점으로는 유용했음
  • 정확도 검증을 위해 SwiftUI Map을 배경에 깔고, 위에 항공기와 나침반을 겹침
    • FlightRadar 투영과 비교했을 때 항공기 수와 클러스터는 대략 맞았지만, 위치는 꽤 어긋남
    • 이후 항공기를 직접 지도 위 주석(annotation) 으로 그리는 방식으로 전환함

MVP: MapKit 주석과 사용자 테스트

  • iOS 17을 타깃으로 하면서 MapKit의 지도 주석 기능을 사용해 항공기 모양 마커를 실제 지리 좌표에 표시함
  • FlightMapViewMap(position:) 위에 항공기 주석을 그리고, 레이더 용도에 맞게 .allowsHitTesting(false)로 지도 상호작용을 막음
  • 고도 기반 크기 조정은 초기에는 높은 항공기를 더 크게 보이도록 로그 스케일을 적용함
    • 항공기의 true_track과 Core Location의 사용자 방향을 합쳐 비행기 아이콘 방향을 맞춤
  • 첫 MVP 사용자 테스트에서 딸과 실제로 비행기를 찾았고, 앱에 표시된 항공기가 실제 하늘에서도 보이는 것을 확인함
  • 테스트 뒤 두 가지 수정점이 드러남
    • 크기 조정이 반대였음: 하늘에서 눈으로 찾기 쉬운 항공기를 보여주려면 낮은 고도의 항공기가 더 커야 했음
    • 딸은 지도가 아니라 비행기만 원했으므로, 잡음을 줄이기 위해 지도 제거가 필요했음
  • 수정된 스케일 식은 min(2, max(4.7 - log10(flight.geo_altitude + 1), 0.7))
    • 로컬 상공 스캔에서 0.7부터 2.0까지의 값이 나왔고, 화면상 분포가 잘 맞았음

레이더 UI 만들기

  • OpenSky API는 timeout, 502 Bad Gateway, null 데이터가 담긴 200 응답을 종종 반환함
    • 무료 비상업 API이고 SLA가 없기 때문에 클라이언트에서 기본 재시도 로직을 추가함
    • 다음 날에는 하루 종일 잘 동작했고, 특정 고트래픽 시간대 외에는 대체로 괜찮아 보였음
  • 실제 지도를 보이지 않게 하기 위해 MapPolygon 오버레이를 사용함
    • 원래 지도 영역 강조용 오버레이지만, 항공기 주석만 남기고 지도를 덮는 용도로 활용함
    • Apple의 오버레이가 지도 위, 주석 아래에 그려져 원하는 결과가 나옴
  • 레이더 화면은 선, 동심원, 20도 폭의 회전 angular gradient로 구현함
  • 이후 레이더 라인 효과는 360도 angular gradient로 단순화함
    • 초록색에서 투명, 검정으로 이어지는 gradient를 회전시켜 스윕과 잔상처럼 보이게 함
    • “grug-brained” 방식이 더 잘 맞는 경우가 많다고 평가함
  • 기기를 빠르게 돌릴 때 지도 조각이 화면 모서리에 보이는 시각적 아티팩트가 있었고, 레이더 뷰 바깥을 검은색으로 덮는 reverse mask로 해결함

제품화: CRT 효과, 소리, 설정, App Store

  • 복고풍 장난감 느낌을 위해 CRT 화면 효과를 추가함
    • iOS 17의 colorEffect에 내장된 Metal 셰이더 지원을 사용함
    • 셰이더는 검정색이 아닌 픽셀에 scanline 값을 적용함
    • time 파라미터를 넣어 scanline이 빠르게 위로 움직이도록 해 동적인 느낌을 줌
  • OpenSky Network 정책상 App Store 등록이 괜찮은지 확인하기 위해 문의했고, 20분 안에 답장을 받음
  • 레이더 경험과 접근성을 위해 항공편 업데이트 시 시스템 사운드와 햅틱을 추가함
    • AudioServicesPlaySystemSound(1052)로 beep-boop 소리를 냄
    • SwiftUI의 sensoryFeedback(.levelChange, trigger:)를 사용함
  • 소리가 거슬릴 수 있어 @AppStorage 기반 설정을 추가함
    • silent: 무음 모드
    • showMap: 레이더 오버레이를 끄고 아래 지도를 볼 수 있음
    • userColor: SwiftUI color picker로 레이더 색상을 선택함
  • Midjourney 구독을 중단한 상태라 Gencraft의 비상업 무료 생성기를 사용해 항공모자를 쓴 딸 느낌의 Aviator 마스코트를 만듦
  • Apple Developer Program에 다시 가입하면서 £79를 지불하고 App Store 배포를 준비함
    • iOS 17만 타깃하지만, 6.5인치와 5.5인치 iPhone 스크린샷을 제출해야 했음
    • 최신 5.5인치 iPhone인 iPhone 8 Plus는 최대 iOS 16이라, AppScreens로 두 크기 스크린샷을 내보냄
  • 앱은 App Store에 올라갔고, 다운로드 링크는 Aviator — Radar on your Phone

다음 버전 후보

  • 몇 저녁 동안 만든 사이드 프로젝트 결과에 만족했고, 딸을 위한 장난감을 만든 코딩 경험이 오랜만에 가장 즐거웠음
  • 다음 릴리스 후보 기능은 다음과 같음
    • 지도 줌 레벨을 추가해 더 가까운 항공기만 레이더에 제한
    • OpenSky Network 고급 API로 헬리콥터, 위성, 비행기 크기 등급 표시
    • 항공기에 출발·도착 국가 표시 토글 추가
    • 더 고급 Metal 셰이더로 CRT 화면 효과 개선
    • 컨트롤을 크기 조정 가능한 progressive-disclosure pull-out modal로 리팩터링
    • 거리·고도 슬라이더 필터를 구현해 낮고 먼 항공기 등을 숨김
    • UFO, 거대 벌레, 외계인을 레이더에 렌더링하는 zany mode 구현

댓글과 토론

Hacker News 의견들
  • 멋진 작업이고 글도 좋음. 원래 레이더 표시 장치의 설계가 이 프로젝트를 얼마나 강하게 붙잡고 있는지가 흥미로움
    아이는 실제 CRT는커녕 PPI 표시가 달린 ASR-9를 만져볼 일이 없을 수도 있는데, 그걸 시뮬레이션하려고 꽤 많은 노력을 들였음
    스큐어모피즘을 좋아해서이기도 하겠지만, 원조 레이더 화면이 “하늘에 뭔가 있는가, 그리고 내 기준 어디에 있는가?”를 훌륭하게 압축해 보여주기 때문이기도 해 보임
    요즘 하늘 관찰용 UI들은 맥락 정보, 외부 서비스 링크, 광고 공간 쪽으로 가면서 이 질문에 쉽게 답하는 능력을 잃어버린 느낌

    • RADAR 화면이 그걸 만들어낸 기술, 즉 스윕의 산물이면서도 관찰 대상을 명확히 보여주려는 의도적 표현이었다는 점이 좋음
      목표물의 거리는 중심에서의 거리로, 방위는 원 둘레의 각도로 표현됐음
      다만 스윕을 흉내 낸 부분은 의심해볼 만한 설계였을 수 있고, 밝은 색과 지도 없는 배경은 화면의 의도에 집중하게 하므로 전혀 문제 없어 보임
      폰을 들고 돌 때 나침반 추적 기능이 스윕의 환상을 깨는 것도 보였음
    • 하늘 관찰 UI가 다 그런 방향으로 간 건 아님. SkyMap도 볼 만함
      https://github.com/sky-map-team/stardroid
    • “하늘에 뭔가 있는가, 그리고 내 기준 어디에 있는가?”를 여전히 잘 잡아내는 서비스로는 FlightRadar24의 AR 모드가 있음
      관련 기술 요소들은 다 이해하고 직접 ADSB 스테이션도 운영하지만, 하늘에 카메라를 겨누면 시야 안의 비행기 메타데이터가 보이는 건 여전히 놀라움
    • 출발점이 아주 순수해서 좋았던 것 같음. “비행기를 찾으려면 어느 방향을 봐야 하지?”에서 시작해 거기서부터 쌓아 올렸음
  • 훌륭함. 언젠가 만들 목록에 올려둠
    비슷하게, 5살 아들에게 비행 로그북을 만들어줬고 아기 때부터 시작했음. 비행할 때마다 승무원에게 기장님이 적어줄 수 있는지 물어보게 함
    노선, 기종, 있었던 일을 기록하는데 승무원들이 이런 걸 정말 좋아함. 조종석과 승무원 휴게 공간도 둘러봤고, 적어준 메시지들도 늘 다정했음
    돌려받을 때마다 아이가 환하게 웃어서, 어린 비행기 덕후에게 강력 추천함

    • 아버지가 내가 유아일 때 로그북을 시작해줬고, 30대인 지금도 계속 업데이트하고 있음. 지금까지 탔던 모든 비행이 들어 있어서 정말 근사한 아이디어임
    • 로그북에 한 표. 딸이 생후 8주쯤 됐을 때 London - New York을 탔는데, 조종석에서 기장 모자를 쓴 사진까지 멋지게 남겼음
    • 천재적임. 다 큰 어른이 되어도 조종석 초대는 엄청 멋졌으니, 다음 비행 때 꼭 시작해볼 생각임
  • 초기 릴리스의 필수 기능으로 표시 색상 변경이 들어간 점이 좋음. 대상 사용자층의 필요를 아주 탄탄하게 이해하고 있다는 뜻임
    3살짜리도 색상 선택기가 있는 장난감은 뭐든 좋아함

    • 다 큰 남자인 나도 색상 선택기가 들어 있으면 신남
      HN에서 karma 250을 넘겼을 때의 비합리적 흥분을 상상하면 됨
    • SwiftUI에서 가장 과소평가된 컴포넌트 중 하나임
      오늘 보여줘도 아이가 제일 먼저 신경 쓰는 건 색을 고르는 일이고, 소근육 발달에도 좋음
  • “비행기 여행을 너무 띄워줘서 아이가 공항까지 택시를 타야 한다는 사실에 놀랐다. 집에서 바로 비행기에 걸어 탈 줄 알았다”는 부분이 정말 사랑스럽고 웃기면서도 이해됨

  • 귀여운 비행기 덕후 아이와 함께 있는 걸 승무원이 보면 조종석 구경을 시켜준다는 부분에서, NASA 주황색 점프수트 맞춰 입기는 모든 조종석으로 들어가는 여권임
    아이들보다 승무원들이 더 신나하고, 공항에서 아이 찾기도 아주 쉬움

    • 천재적임. 그러면 나나 아내가 NASA에 들어가기만 하면 되겠네. 우주에서 iOS 개발자가 많이 필요할까?
  • 망한 소셜 네트워크 앱 Tapstack을 가족용으로만 복제한 글 An app can be a home cooked meal도 관련 있음
    https://www.robinsloan.com/notes/home-cooked-app/

    • lovely한 글이라 북마크함. 다운로드가 0이어도 과정 자체가 보람 있었을 것 같다는 데 동의함
      물론 바이럴이 되는 것도 나쁘진 않음
  • 사소한 아쉬움 두 가지가 있음
    비행기 점이 그려진 뒤에 움직이면 몰입이 조금 깨짐. 스윕에 의해 점이 “칠해지고”, 다음 스윕 전까지는 고정돼 있으면 더 현실적으로 느껴질 것 같음
    더 현실감을 내려면 이전 데이터 포인트에서 외삽해 각 비행기가 스윕마다 일관되게 이동하도록 만들 수도 있음

    • 둘 다 타당한 지점임. 타이밍과 각도를 잘 맞추면 첫 번째는 충분히 가능해 보임
      두 번째는 더 단순할 수도 있음. API가 비행 속도를 반환하니 데이터 포인트 하나만으로도 계산 가능함
  • 딸과 하기 좋은 활동으로, 가장 가까운 국제공항 활주로의 착륙 방향 끝에 있는 도로, 주차장, 공원에 가면 좋음
    보통 바람 방향에 따라 달라지니 맞춰서 가고, 차를 세워 비행기가 들어와 착륙하는 걸 보면 됨
    아이가 자기 레이더로 어떤 비행기가 오는지 볼 수 있고, 우리는 flightradar24를 썼음
    접근 중인 큰 제트기가 지면에 얼마나 가까이 내려오는지 보고 아이들이 완전히 넋이 나갔고, 몇 시간도 쉽게 즐김. 다음 비행기가 더 크거나 더 가까운지 보겠다며 떠나기 싫어해서 협상해야 했음

    • 작성자라면 London City Airport (LCY) 가 해당될 것 같고, 여기는 접근 각도가 아주 가파름
      Airbus A318 크기 이하의 다발 고정익 항공기 중에서도 5.5° 접근을 수행할 수 있도록 특별 항공기 및 승무원 인증을 받은 경우만 운항 가능함
      https://www.youtube.com/watch?v=yZIDFgpT0-o
      https://en.wikipedia.org/wiki/London_City_Airport
    • D.C. 근처라면 Gravely Point가 딱 그 장소임. 이륙이든 착륙이든 비행기의 리벳까지 셀 수 있을 것처럼 느껴짐
    • 귀마개를 챙기는 게 좋음
    • 고등학교 때 활주로 끝 공항 울타리 바로 밖에 자갈 공터가 있었음. 바람 방향이 맞아 비행기가 그 공터 위로 이륙할 때면, 차를 세우고 데이트하기 좋은 장소였음
    • 제안과 완전히 같진 않지만, SFO에는 보안검색 전 구역에 Sky Terrace가 있어서 비행기를 구경할 수 있음
  • 이 앱에는 한 가지 문제가 있음. 앱 페이지에는 분명 4세 이상이라고 되어 있는데, 목표 사용자는 2살임
    잘 만들었음

    • App Store 경찰의 레이더망 아래에 머물 수 있길 바람
  • 훌륭함
    작은 세부 질문인데, CRT 레이더 스코프에 실제로 주사선이 있었나? 벡터 디스플레이였을 거라고 생각했음
    추가 기능 목록과 관련해서, 비행기 관찰용 앱이니 점을 탭하면 몇 초 동안 기종을 보여주면 멋질 것 같음

    • 맞음. 구식 레이더 화면은 기본적으로 오실로스코프처럼 X/Y 위치가 레이더 각도와 레이더 반사의 현재 거리로 제어됐음. 일종의 극좌표 플롯
      형광체 잔광 덕분에 강한 레이더 반사는 화면에 잠깐 남았음. 그 시간 안에 레이더가 한 바퀴 이상 돌면 같은 비행기가 조금 움직인 새 점, 즉 “plot”으로 보였음
      펠트펜으로 화면의 plot들을 표시해 “track”으로 만들 수도 있었음
      레이더 화면을 향해 영화 카메라를 둬서 몇 시간치 레이더 반사를 녹화하고 나중에 재생하는 특수 화면도 있었음
      예를 들어 1968년 체코슬로바키아 혁명 당시 바르샤바 조약기구 항공기들의 가속 재생 기록이 있음: https://youtu.be/rAUodXI4LPw?t=622
    • 벡터 디스플레이가 맞지만, 아마 떠올리는 방식과는 조금 다를 수 있음
      원래 레이더 화면은 중심에서 바깥쪽으로 방사 방향을 따라 스캔했음. 스캔 타이밍은 거리를 스케일링하도록 미리 정해져 있었고, 빔 강도 신호는 증폭된 레이더 반사 신호 그 자체였음
      그래서 더 강한 반사 신호는 장잔광 화면에서 더 잘 보이는 “blip”으로 나타났음
      흥미로운 부분은 레이더 빔이 CRT 화면 주위를 스캔하게 하려고, 전체 음극선관 방출기 조립체를 회전하는 레이더 접시와 동기화된 모터로 구동했다는 점임
      이 회전은 항상 레이더 접시의 속도와 방향에 맞아야 했고, 아니면 blip이 엉뚱한 위치에 나타났음
      고정된 방사선과 거리선은 CRT 튜브 자체나 투명 덮개에 인쇄됐음. 이런 화면은 수십 년 동안, 아마 1980년대나 1990년대 초까지도 쓰였을 것임
      더 최신 버전은 복잡한 회전식 빔 방출기 조립체를 피하려고 간단한 전자회로로 X/Y 방향을 독립적으로 스캔할 수 있었음
      자세한 내용: https://en.wikipedia.org/wiki/Radar_display#Plan_position_in...
    • 다른 답변들이 짚은 부분을 조금 더 확장하면, 빔은 실제로 중심에서 바깥쪽으로 방사형으로 스캔함
      각도는 편향판을 물리적으로 회전시키거나, sin/cos를 전자적으로 계산해 X/Y 편향판에 적용하는 방식으로 만들 수 있음
      반환 신호의 진폭이 빔 전류를 직접 구동하므로, 더 큰 반사를 가진 목표물은 더 밝게 보임
      여기에 저장관 효과도 있는데, 초기 컴퓨터의 저장관 X-Y 벡터 디스플레이에 있던 쌍안정 모드가 아니라 일부 오실로스코프의 가변 잔상 모드처럼 동작함
      이 특성 때문에 SR-71이나 XB-70 같은 초기 스텔스 항공기 설계 시도와 관련된 결과도 생김
      레이더 튜브는 여러 번의 스윕에 걸쳐 “blip”이 쌓이도록 빔 전류가 설정되고, 보통 blip은 스윕 사이에 자기 지름보다 덜 움직임
      하지만 비행기의 레이더 반사 면적이 본질적으로 낮고 너무 빨라서 blip이 스윕 사이에 더 먼 거리를 이동하면, 레이더 조작자는 특히 목표물이 많아 정신없는 상황에서 작은 유령 blip들을 잡음으로 해석할 수 있음