Cha mẹ làm một ứng dụng radar vì con nhỏ mê máy bay
(jacobbartlett.substack.com)- Sau khi cô con gái hai tuổi mê máy bay, tác giả đã làm ứng dụng iOS Aviator như một side project trong vài buổi tối để hiển thị các máy bay xung quanh giống radar trên điện thoại
- FlightRadar24 cho thấy vị trí máy bay trên bản đồ, nhưng vẫn phải tự xác định nên nhìn theo hướng nào trên bầu trời thật và máy bay nào dễ để trẻ nhận ra
- Ứng dụng kết hợp thông tin hướng từ Core Location, REST API phi thương mại của OpenSky Network, annotation của MapKit và giao diện SwiftUI để hiển thị máy bay theo hướng của thiết bị
- Sau khi kiểm tra MVP và xác nhận máy bay thật khớp với hiển thị trong ứng dụng, tác giả bổ sung tính năng ẩn bản đồ, phóng to máy bay bay thấp, hiệu ứng radar, hiệu ứng CRT, âm thanh·haptic và thiết lập màu sắc
- Ứng dụng cuối cùng đã được phát hành trên App Store, và các hạng mục cải tiến sau đó gồm mức zoom, API nâng cao của OpenSky, hiển thị quốc gia khởi hành·điểm đến, cải thiện shader Metal, bộ lọc khoảng cách·độ cao và “zany mode”
Từ thú ngắm máy bay đến yêu cầu cho ứng dụng
- Trong chuyến du lịch nước ngoài mùa hè với chuyến bay kéo dài 3 tiếng phía trước, cô con gái rất háo hức với việc đi máy bay, và sau khi được tiếp viên cho xem buồng lái thì càng mê máy bay hơn
- Từ đó, bé thường nhờ tìm máy bay trên trời, đến mức có lần tác giả phải bế bé trên vai suốt một tiếng trong vườn để tìm máy bay trên bầu trời chiều
- FlightRadar24 hiển thị vị trí máy bay trên bản đồ, nhưng người dùng vẫn phải tự căn xem nên nhìn theo hướng nào trên bầu trời thật
- Trên bản đồ 2D, một chiếc Learjet ở độ cao 40.000 feet và một chiếc AirBus vừa cất cánh từ London City Airport có thể trông giống nhau, nhưng ngoài trời thì chiếc máy bay lớn bay thấp sẽ dễ nhìn thấy hơn
- Điều một đứa trẻ hai tuổi cần không phải là giải thích bản đồ mà là một cách tìm máy bay thật đơn giản
- Vấn đề ban đầu được gói gọn trong ba điểm
- Căn hướng: vị trí máy bay phải tương ứng với hướng thật trên bầu trời theo hướng thiết bị
- Biểu diễn kích thước: kích thước trên màn hình phải thay đổi theo độ cao máy bay
- Tính dễ dùng: đây không nên là ứng dụng theo dõi chuyến bay kiểu công việc mà phải giống một món đồ chơi retro
Thiết kế ban đầu của Aviator
- Ý tưởng ứng dụng là “hiển thị các chuyến bay xung quanh trên radar”, và tên dự án được chọn là Aviator
- Có ba yêu cầu cốt lõi
- Khi thiết bị xoay, màn hình cũng phải xoay theo để máy bay hiện đúng hướng
- Kích thước máy bay trên màn hình phải thay đổi theo độ cao
- Ứng dụng phải mang cảm giác vui nhộn, kiểu đồ chơi retro phù hợp cho trẻ dùng
- Phần xử lý hướng dùng heading update của Core Location API trên iOS
- Nhận hướng thiết bị từ
didUpdateHeadingcủaCLLocationManagerDelegate - Truyền giá trị hướng sang SwiftUI view bằng
CurrentValueSubjectcủa Combine
- Nhận hướng thiết bị từ
- Dữ liệu hàng không dùng OpenSky Network
- Đây là REST API đơn giản cung cấp dữ liệu chuyến bay thời gian thực trong một vùng vĩ độ·kinh độ nhất định
- Miễn phí cho mục đích phi thương mại
- Kế hoạch là gọi endpoint mỗi vài giây để tạo hiệu ứng quét radar chân thực
- Dữ liệu vị trí lấy từ Core Location và truy vấn vùng vĩ độ·kinh độ quanh vị trí người dùng
- Ở ý tưởng ban đầu, tác giả muốn làm mờ vị trí người dùng đủ nhiều bằng cách dùng vĩ độ ±1 độ và độ chính xác vị trí 0,1 độ, khoảng 10 km
- Trong mã ví dụ API thực tế, phạm vi 0,5 độ vĩ độ và 1 độ kinh độ được dùng để gần giống hình vuông ở vĩ độ của Anh
- Vì OpenSky Network API bị giới hạn cho phi thương mại và ứng dụng cũng không phải dịch vụ thương mại, phía SwiftUI dùng cấu trúc MV đơn giản và chỉ tách riêng các dịch vụ lõi như API·Location
PoC: hướng, dữ liệu hàng không và kiểm chứng bằng bản đồ
- PoC cho hướng dùng singleton
LocationManagerđể xin quyền vị trí, cấu hình delegate và bắt đầu cập nhật heading - SwiftUI view đăng ký
rotationAngleSubjectbằng.onReceive, rồi xếp các hình chữ nhật theo vòng tròn để tạo hiệu ứng xoay như la bàn - Trên thiết bị thử nghiệm, nó phản ứng tốt với thay đổi hướng thật, nhưng do animation coi 0 độ và 360 độ là hai giá trị khác nhau nên khi đi qua hướng bắc thật, các hình chữ nhật bị quay trọn một vòng gây lỗi hiển thị
- OpenSky REST API trả về mảng chuyến bay theo khu vực bằng request
GETchỉ định phạm vi vĩ độ·kinh độ- Endpoint ví dụ dùng
states/allvới các tham sốlamin,lamax,lomin,lomax - Tài liệu REST API khá tốt, nhưng response không có key nên phải parse trường JSON theo đúng thứ tự
- Dùng
UnkeyedContainercủa Swift để decodeicao24,callsign,origin_country,time_position,last_contact,longitude,latitude...
- Endpoint ví dụ dùng
- Cách vẽ máy bay đầu tiên là lấy chênh lệch vĩ độ·kinh độ giữa người dùng và máy bay, nhân với một tỉ lệ màn hình cố định rồi đặt ảnh SF Symbol
airplane- Khoảng cách thật của 1 độ vĩ độ·kinh độ thay đổi theo vị trí nên cách này không thể chính xác hoàn toàn
- Dù vậy nó vẫn hữu ích như điểm khởi đầu
- Để kiểm chứng độ chính xác, tác giả đặt
Mapcủa SwiftUI làm nền rồi chồng máy bay và la bàn lên trên- So với phép chiếu kiểu FlightRadar, số lượng máy bay và các cụm tương đối khớp nhưng vị trí lệch khá nhiều
- Sau đó cách làm được đổi sang vẽ máy bay trực tiếp thành annotation trên bản đồ
MVP: annotation của MapKit và thử nghiệm với người dùng
- Nhắm tới iOS 17, tác giả dùng tính năng annotation bản đồ của MapKit để hiển thị marker hình máy bay tại đúng tọa độ địa lý thật
FlightMapViewvẽ annotation máy bay trênMap(position:), và chặn tương tác với bản đồ bằng.allowsHitTesting(false)để phù hợp cho chế độ radar- Việc scale theo độ cao ban đầu áp dụng log scale để máy bay bay cao trông lớn hơn
- Hướng của icon máy bay được căn bằng cách kết hợp
true_trackcủa máy bay với hướng người dùng từ Core Location
- Hướng của icon máy bay được căn bằng cách kết hợp
- Trong thử nghiệm MVP đầu tiên, tác giả đã thực sự cùng con gái tìm máy bay và xác nhận chiếc máy bay hiển thị trong ứng dụng cũng nhìn thấy được trên bầu trời thật
- Sau thử nghiệm, có hai điểm cần sửa
- Việc scale đang bị ngược: nếu mục tiêu là hiển thị máy bay dễ thấy bằng mắt thường trên trời thì máy bay bay thấp phải lớn hơn
- Bé không cần bản đồ mà chỉ cần máy bay, nên cần bỏ bản đồ để giảm nhiễu
- Công thức scale sau khi sửa là
min(2, max(4.7 - log10(flight.geo_altitude + 1), 0.7))- Khi quét vùng trời địa phương, giá trị thu được từ 0,7 đến 2,0 và phân bố trên màn hình khá hợp lý
Tạo giao diện radar
- OpenSky API thường trả về timeout,
502 Bad Gatewayvà cả response 200 chứa dữ liệu null- Vì đây là API miễn phí phi thương mại và không có SLA, phía client đã thêm logic retry cơ bản
- Sang ngày hôm sau, ứng dụng chạy tốt cả ngày và nhìn chung ổn, trừ một số khung giờ có lưu lượng rất cao
- Để ẩn bản đồ thật, tác giả dùng overlay
MapPolygon- Vốn dĩ đây là overlay để nhấn mạnh một vùng trên bản đồ, nhưng ở đây được tận dụng để che bản đồ và chỉ giữ lại annotation máy bay
- Overlay của Apple được vẽ phía trên bản đồ nhưng dưới annotation nên cho đúng kết quả mong muốn
- Màn hình radar được dựng bằng các đường thẳng, vòng tròn đồng tâm và một angular gradient quay có độ rộng 20 độ
- Sau đó hiệu ứng vệt radar được đơn giản hóa thành angular gradient 360 độ
- Gradient chuyển từ xanh lá sang trong suốt rồi đen được quay để trông như hiệu ứng quét và lưu ảnh
- Tác giả đánh giá cách làm “grug-brained” này lại thường hiệu quả hơn
- Khi xoay thiết bị nhanh, có artifact khiến các mảnh bản đồ lộ ra ở góc màn hình, và vấn đề được giải quyết bằng reverse mask phủ màu đen ra ngoài radar view
Hoàn thiện sản phẩm: hiệu ứng CRT, âm thanh, thiết lập, App Store
- Để tạo cảm giác đồ chơi retro, tác giả thêm hiệu ứng màn hình CRT
- Dùng hỗ trợ Metal shader tích hợp trong
colorEffectcủa iOS 17 - Shader áp dụng giá trị scanline cho những pixel không phải màu đen
- Thêm tham số
timeđể các scanline chạy nhanh lên trên, tạo cảm giác động
- Dùng hỗ trợ Metal shader tích hợp trong
- Tác giả đã hỏi OpenSky Network xem việc đưa ứng dụng lên App Store có ổn theo chính sách của họ không, và nhận được phản hồi trong vòng 20 phút
- Để tăng trải nghiệm radar và khả năng tiếp cận, tác giả thêm âm thanh hệ thống và haptic mỗi khi danh sách chuyến bay được cập nhật
- Phát âm thanh beep-boop bằng
AudioServicesPlaySystemSound(1052) - Dùng
sensoryFeedback(.levelChange, trigger:)của SwiftUI
- Phát âm thanh beep-boop bằng
- Vì âm thanh có thể gây khó chịu, tác giả thêm phần cài đặt dựa trên
@AppStoragesilent: chế độ im lặngshowMap: có thể tắt overlay radar để xem bản đồ phía dướiuserColor: chọn màu radar bằng SwiftUI color picker
- Do đã hủy gói Midjourney, tác giả dùng trình tạo miễn phí phi thương mại của Gencraft để tạo mascot Aviator mang cảm giác như cô con gái đội mũ phi công
- Khi đăng ký lại Apple Developer Program, tác giả trả £79 và chuẩn bị phát hành lên App Store
- Dù chỉ nhắm tới iOS 17, vẫn phải nộp screenshot iPhone 6,5 inch và 5,5 inch
- Vì iPhone 8 Plus, mẫu iPhone 5,5 inch mới nhất, chỉ hỗ trợ tối đa iOS 16, tác giả dùng AppScreens để xuất screenshot ở cả hai kích thước
- Ứng dụng đã lên App Store, với liên kết tải về là Aviator — Radar on your Phone
Các ứng viên cho phiên bản tiếp theo
- Tác giả hài lòng với kết quả của side project làm trong vài buổi tối, và việc code ra một món đồ chơi cho con gái là trải nghiệm vui nhất sau một thời gian dài
- Các tính năng dự kiến cho bản phát hành tiếp theo gồm
- Thêm mức zoom bản đồ để radar chỉ giới hạn ở các máy bay gần hơn
- Dùng API nâng cao của OpenSky Network để hiển thị trực thăng, vệ tinh và phân hạng kích thước máy bay
- Thêm tùy chọn bật/tắt hiển thị quốc gia khởi hành·điểm đến của máy bay
- Cải thiện hiệu ứng màn hình CRT bằng Metal shader nâng cao hơn
- Refactor phần điều khiển thành progressive-disclosure pull-out modal có thể thay đổi kích thước
- Triển khai bộ lọc slider theo khoảng cách·độ cao để ẩn các máy bay bay thấp nhưng ở xa, v.v.
- Triển khai zany mode để render UFO, côn trùng khổng lồ và người ngoài hành tinh trên radar
1 bình luận
Ý kiến trên Hacker News
Công trình tuyệt vời và bài viết cũng hay. Thật thú vị khi thấy thiết kế của màn hình hiển thị radar gốc đã chi phối dự án này mạnh đến mức nào
Đứa trẻ có thể sẽ chẳng bao giờ được chạm vào một chiếc ASR-9 có màn hình PPI, chứ chưa nói đến CRT thật, vậy mà tác giả đã bỏ ra khá nhiều công sức để mô phỏng nó
Có lẽ một phần vì thích skeuomorphism, nhưng cũng có vẻ vì màn hình radar nguyên bản đã cô đọng rất xuất sắc câu hỏi “trên trời có gì không, và nó ở đâu so với mình?”
Các UI quan sát bầu trời ngày nay có cảm giác như đã đánh mất khả năng trả lời câu hỏi đó một cách dễ dàng khi chuyển sang thông tin ngữ cảnh, liên kết tới dịch vụ bên ngoài và không gian quảng cáo
Khoảng cách tới mục tiêu được biểu diễn bằng khoảng cách từ tâm, còn phương vị bằng góc quanh chu vi hình tròn
Tuy vậy, phần mô phỏng quét có thể là một lựa chọn thiết kế đáng nghi ngờ, còn màu sắc sáng và nền không có bản đồ thì trông hoàn toàn không vấn đề gì vì chúng giúp tập trung vào ý đồ của màn hình
Cũng thấy rằng khi cầm điện thoại xoay vòng, tính năng theo dõi la bàn đã phá vỡ ảo giác quét
https://github.com/sky-map-team/stardroid
Tôi hiểu tất cả các yếu tố kỹ thuật liên quan và còn tự vận hành một trạm ADSB, nhưng việc chĩa camera lên trời là thấy metadata của máy bay trong tầm nhìn vẫn thật đáng kinh ngạc
Tuyệt vời. Đã đưa vào danh sách những thứ một ngày nào đó sẽ làm
Tương tự, tôi đã làm một sổ nhật ký bay cho con trai 5 tuổi, bắt đầu từ khi cháu còn bé. Mỗi lần bay, tôi bảo cháu hỏi tiếp viên xem cơ trưởng có thể ghi vào đó không
Sổ ghi lại chặng bay, loại máy bay và những chuyện đã xảy ra; các tiếp viên thật sự rất thích những thứ như vậy. Cháu cũng được tham quan buồng lái và khu nghỉ của phi hành đoàn, còn các lời nhắn họ viết lúc nào cũng rất dễ thương
Mỗi lần nhận lại sổ, con tôi đều cười rạng rỡ, nên tôi rất khuyến nghị cho các fan nhí mê máy bay
Tôi thích việc đổi màu hiển thị được đưa vào như một tính năng bắt buộc của bản phát hành đầu tiên. Điều đó cho thấy họ hiểu rất chắc nhu cầu của nhóm người dùng mục tiêu
Trẻ 3 tuổi cũng thích bất kỳ món đồ chơi nào có bộ chọn màu
Cứ tưởng tượng cảm giác phấn khích phi lý khi vượt 250 karma trên HN là được
Cho trẻ xem ngay hôm nay thì việc đầu tiên chúng quan tâm cũng là chọn màu, và nó còn tốt cho phát triển vận động tinh
Đoạn “chúng tôi tô hồng chuyện đi máy bay quá mức nên con tôi ngạc nhiên khi biết phải đi taxi ra sân bay. Cháu tưởng có thể đi bộ thẳng từ nhà lên máy bay” thật sự đáng yêu, buồn cười mà cũng dễ hiểu
https://www.architecturendesign.net/john-travoltas-house-is-...
Ở đoạn nói rằng khi tiếp viên thấy bạn đi cùng một bé mê máy bay dễ thương thì họ sẽ cho xem buồng lái, việc mặc đồng bộ jumpsuit màu cam của NASA chính là hộ chiếu để vào mọi buồng lái
Các tiếp viên còn phấn khích hơn cả bọn trẻ, và ở sân bay cũng rất dễ tìm ra con mình
Cũng liên quan là bài An app can be a home cooked meal, kể về việc sao chép ứng dụng mạng xã hội thất bại Tapstack chỉ để dùng trong gia đình
https://www.robinsloan.com/notes/home-cooked-app/
Tất nhiên, viral được thì cũng không tệ
Có hai điểm nhỏ hơi tiếc
Khi các chấm máy bay đã được vẽ rồi mà vẫn di chuyển, cảm giác nhập vai hơi bị phá vỡ. Sẽ chân thực hơn nếu các chấm được “tô” bởi vòng quét và đứng yên cho tới lần quét kế tiếp
Nếu muốn tăng độ chân thực, cũng có thể ngoại suy từ điểm dữ liệu trước đó để mỗi máy bay di chuyển nhất quán qua từng lần quét
Điểm thứ hai có thể còn đơn giản hơn. Vì API trả về tốc độ bay, nên chỉ với một điểm dữ liệu cũng có thể tính được
Một hoạt động rất hợp để làm cùng con gái là đến con đường, bãi đỗ xe hoặc công viên nằm ở đầu hướng hạ cánh của đường băng tại sân bay quốc tế gần nhất
Thường thì hướng này thay đổi theo chiều gió, nên hãy canh cho đúng rồi đến đó; chỉ cần đỗ xe và xem máy bay bay vào để hạ cánh
Đứa trẻ có thể dùng radar của mình để xem máy bay nào đang đến, còn chúng tôi dùng flightradar24
Bọn trẻ hoàn toàn mê mẩn khi thấy những chiếc phản lực lớn đang tiếp cận hạ thấp xuống gần mặt đất đến mức nào, và có thể dễ dàng vui vẻ hàng giờ. Chúng không muốn rời đi vì cứ muốn xem chuyến tiếp theo có lớn hơn hay gần hơn không, nên chúng tôi phải thương lượng
Trong số các máy bay cánh cố định nhiều động cơ có kích thước không lớn hơn Airbus A318, chỉ những chiếc có chứng nhận đặc biệt cho cả máy bay và phi hành đoàn để thực hiện tiếp cận 5,5° mới được phép khai thác
https://www.youtube.com/watch?v=yZIDFgpT0-o
https://en.wikipedia.org/wiki/London_City_Airport
Ứng dụng này có một vấn đề. Trên trang ứng dụng ghi rõ là từ 4 tuổi trở lên, nhưng người dùng mục tiêu lại mới 2 tuổi
Làm rất tốt
Tuyệt vời
Một câu hỏi nhỏ về chi tiết: ống ngắm radar CRT có thật sự có đường quét không? Tôi cứ nghĩ nó là màn hình vector
Liên quan đến danh sách tính năng bổ sung, vì đây là ứng dụng để quan sát máy bay, nếu chạm vào một điểm mà nó hiển thị loại máy bay trong vài giây thì sẽ rất hay
Nhờ dư quang của phosphor, các phản xạ radar mạnh sẽ lưu lại trên màn hình trong chốc lát. Nếu trong khoảng thời gian đó radar quay được một vòng trở lên, cùng một máy bay sẽ xuất hiện như một điểm mới đã dịch chuyển một chút, tức một “plot”
Cũng có thể dùng bút dạ đánh dấu các plot trên màn hình để tạo thành “track”
Còn có những màn hình đặc biệt đặt máy quay phim hướng vào màn hình radar để ghi lại vài giờ phản xạ radar rồi phát lại sau
Ví dụ có bản ghi phát nhanh các máy bay của Khối Warszawa trong Cách mạng Tiệp Khắc năm 1968: https://youtu.be/rAUodXI4LPw?t=622
Màn hình radar ban đầu quét theo hướng xuyên tâm từ tâm ra ngoài. Thời điểm quét được định sẵn để chia tỷ lệ khoảng cách, còn tín hiệu cường độ chùm tia chính là tín hiệu phản xạ radar đã được khuếch đại
Vì vậy tín hiệu phản xạ mạnh hơn sẽ hiện thành “blip” dễ thấy hơn trên màn hình dư quang dài
Điểm thú vị là để chùm tia radar quét quanh màn hình CRT, toàn bộ cụm phát xạ ống tia âm cực được dẫn động bằng một động cơ đồng bộ với đĩa radar đang quay
Chuyển động quay này luôn phải khớp với tốc độ và hướng của đĩa radar, nếu không blip sẽ xuất hiện sai vị trí
Các đường phương vị và đường khoảng cách cố định được in trên chính ống CRT hoặc trên nắp trong suốt. Những màn hình như vậy đã được dùng trong nhiều thập kỷ, có lẽ thậm chí đến những năm 1980 hoặc đầu những năm 1990
Các phiên bản mới hơn có thể quét độc lập theo hướng X/Y bằng mạch điện tử đơn giản để tránh cụm phát xạ chùm tia quay phức tạp
Chi tiết: https://en.wikipedia.org/wiki/Radar_display#Plan_position_in...
Góc có thể được tạo bằng cách xoay vật lý các bản lệch hướng, hoặc tính sin/cos bằng điện tử rồi áp dụng lên các bản lệch hướng X/Y
Biên độ tín hiệu hồi đáp trực tiếp điều khiển dòng chùm tia, nên mục tiêu có phản xạ lớn hơn sẽ hiện sáng hơn
Ở đây còn có hiệu ứng ống lưu trữ, nhưng nó không hoạt động như chế độ lưỡng ổn định trên màn hình vector X-Y dạng ống lưu trữ của các máy tính đời đầu, mà giống chế độ lưu ảnh biến thiên trên một số máy hiện sóng hơn
Đặc tính này cũng dẫn đến một số hệ quả liên quan đến các nỗ lực thiết kế máy bay tàng hình thời kỳ đầu như SR-71 hay XB-70
Ống radar được đặt dòng chùm tia sao cho “blip” tích lũy qua nhiều lần quét, và thông thường blip di chuyển giữa các lần quét ít hơn đường kính của chính nó
Nhưng nếu diện tích phản xạ radar của máy bay vốn thấp và nó bay quá nhanh đến mức blip di chuyển xa hơn giữa các lần quét, người vận hành radar có thể diễn giải các blip ma nhỏ đó là nhiễu, đặc biệt trong tình huống bận rộn có nhiều mục tiêu