2 pontos por GN⁺ 2023-11-28 | 1 comentários | Compartilhar no WhatsApp
  • Depois que a filha de dois anos ficou obcecada por aviões, ele criou em algumas noites, como projeto paralelo, o app iOS Aviator, que mostra aeronaves próximas como um radar no celular
  • O FlightRadar24 mostra a posição dos aviões no mapa, mas ainda era preciso decidir para que direção olhar no céu real e quais aviões seriam mais fáceis para uma criança encontrar
  • O app combina informações de direção do Core Location, a API REST não comercial da OpenSky Network, anotações do MapKit e a UI em SwiftUI para mostrar os aviões de acordo com a orientação do aparelho
  • Depois de confirmar no teste do MVP que os aviões reais batiam com o que o app mostrava, ele adicionou ocultação do mapa, destaque para aeronaves em baixa altitude, animação de radar, efeito CRT, sons e feedback tátil, além de configuração de cores
  • O app final foi publicado na App Store e, como possíveis melhorias futuras, ainda restam nível de zoom, API avançada da OpenSky, exibição de país de partida e chegada, melhorias nos shaders Metal, filtros de distância e altitude e o “zany mode”

Da observação de aviões aos requisitos do app

  • A filha estava animada com uma viagem internacional de verão que teria um voo de 3 horas e, depois de visitar a cabine de comando com a ajuda da tripulação, ficou ainda mais fascinada por aviões
  • Depois disso, ela passou a pedir com frequência para procurar aviões no céu, chegando ao ponto de ele ficar uma hora no jardim com a filha nos ombros procurando aeronaves no céu da tarde
  • O FlightRadar24 mostrava a posição dos aviões no mapa, mas o usuário ainda precisava descobrir sozinho para que lado olhar no céu real
  • Num mapa 2D, um Learjet a 40.000 pés e um AirBus que acabou de decolar do London City Airport podem parecer semelhantes, mas no céu real uma aeronave grande em baixa altitude é muito mais fácil de encontrar
  • O que uma criança de dois anos precisava não era interpretar um mapa, e sim simplesmente encontrar aviões
  • O problema inicial foi resumido em três pontos
    • Alinhamento de direção: a posição da aeronave precisava corresponder à direção real no céu conforme a orientação do aparelho
    • Representação de tamanho: o tamanho na tela precisava variar de acordo com a altitude da aeronave
    • Usabilidade: em vez de um app profissional de rastreamento de voos, precisava parecer um brinquedo retrô

O design inicial do Aviator

  • A ideia do app era “mostrar voos próximos em um radar”, e o nome do projeto foi definido como Aviator
  • Havia três requisitos principais
    • Quando o aparelho girasse, a tela também giraria para mostrar os aviões na direção correta
    • O tamanho na tela mudaria conforme a altitude da aeronave
    • O app teria uma sensação divertida de brinquedo retrô, adequada para crianças
  • O tratamento da direção usou as atualizações de heading da API Core Location do iOS
    • Em didUpdateHeading de CLLocationManagerDelegate, ele recebia a direção do aparelho
    • O valor da direção era passado para a view SwiftUI com CurrentValueSubject do Combine
  • Para os dados de voo, foi usada a OpenSky Network
    • A API REST simples fornece dados de voos em tempo real para uma faixa específica de latitude e longitude
    • O uso não comercial é gratuito
    • A ideia era chamar o endpoint a cada poucos segundos para criar uma varredura de radar realista
  • Os dados de localização vinham do Core Location, e o app consultava uma faixa de latitude e longitude ao redor da posição do usuário
    • No plano inicial, a latitude seria ±1 grau, com precisão de localização de 0,1 grau, cerca de 10 km, para desfocar suficientemente a posição do usuário
    • No código de exemplo real da API, foi usada uma faixa de 0,5 grau de latitude e 1 grau de longitude para formar algo aproximadamente quadrado nas latitudes do Reino Unido
  • Como a API OpenSky Network é limitada a uso não comercial e o app também não era um serviço comercial, em SwiftUI foi usada uma estrutura simples de MV, separando apenas serviços principais como API e Location

PoC: direção, dados de voo e validação no mapa

  • A PoC de direção usou um singleton LocationManager para solicitar permissão de localização, configurar o delegate e iniciar as atualizações de heading
  • A view SwiftUI assinava rotationAngleSubject com .onReceive e implementava um efeito de rotação semelhante a uma bússola, posicionando retângulos em círculo
  • No aparelho de teste, a resposta à mudança real de posição funcionou bem, mas, por causa da animação que tratava 0 e 360 graus como valores diferentes, surgiu um glitch visual em que os retângulos davam uma volta completa ao passar pelo norte verdadeiro
  • A API REST da OpenSky retorna um array de voos regionais por meio de uma requisição GET com faixa de latitude e longitude
    • O endpoint de exemplo usa states/all com os parâmetros lamin, lamax, lomin e lomax
    • A documentação da REST API é boa, mas a resposta não tem chaves, então foi preciso fazer o parse dos campos JSON pela ordem
    • Com UnkeyedContainer do Swift, ele decodificou icao24, callsign, origin_country, time_position, last_contact, longitude, latitude e outros campos
  • A primeira tentativa de desenhar os aviões foi posicionar a imagem SF Symbol airplane multiplicando a diferença entre a latitude/longitude do usuário e da aeronave por uma escala fixa de tela
    • A distância real de 1 grau de latitude ou longitude varia conforme a localização, então isso não poderia ser preciso
    • Ainda assim, foi útil como ponto de partida
  • Para verificar a precisão, ele colocou um Map do SwiftUI como fundo e sobrepôs os aviões e a bússola por cima
    • Em comparação com a projeção do FlightRadar, a quantidade de aviões e os agrupamentos batiam aproximadamente, mas as posições estavam bem deslocadas
    • Depois disso, ele mudou para desenhar os aviões diretamente no mapa como anotações (annotations)

MVP: anotações do MapKit e testes com usuários

  • Ao mirar no iOS 17, ele usou os recursos de anotações de mapa do MapKit para mostrar marcadores em forma de avião nas coordenadas geográficas reais
  • FlightMapView desenha anotações de aeronaves sobre Map(position:) e bloqueia a interação com o mapa usando .allowsHitTesting(false) para adequá-lo ao uso como radar
  • O ajuste de tamanho por altitude inicialmente aplicava uma escala logarítmica para fazer as aeronaves em altitudes maiores parecerem maiores
    • A direção do ícone do avião era alinhada combinando o true_track da aeronave com a direção do usuário fornecida pelo Core Location
  • No primeiro teste de usuário do MVP, ele procurou aviões de verdade com a filha e confirmou que as aeronaves mostradas no app também eram visíveis no céu real
  • Depois do teste, apareceram dois pontos a corrigir
    • O ajuste de tamanho estava invertido: para mostrar os aviões mais fáceis de localizar a olho nu no céu, as aeronaves em baixa altitude precisavam parecer maiores
    • A filha não queria o mapa, só os aviões, então era preciso remover o mapa para reduzir o ruído visual
  • A fórmula de escala corrigida foi min(2, max(4.7 - log10(flight.geo_altitude + 1), 0.7))
    • Nas varreduras do espaço aéreo local, isso gerou valores de 0,7 a 2,0, com uma boa distribuição na tela

Criando a UI de radar

  • A API da OpenSky frequentemente retornava timeout, 502 Bad Gateway e respostas 200 com dados nulos
    • Como é uma API gratuita e não comercial sem SLA, ele adicionou uma lógica básica de retry no cliente
    • No dia seguinte, ela funcionou bem o dia todo e, fora de alguns horários específicos de tráfego alto, pareceu aceitável na maior parte do tempo
  • Para esconder o mapa real, ele usou um overlay MapPolygon
    • Embora o overlay seja originalmente para destacar áreas do mapa, ele o aproveitou para cobrir o mapa e deixar visíveis apenas as anotações das aeronaves
    • Como os overlays da Apple são desenhados acima do mapa e abaixo das anotações, isso produziu o resultado desejado
  • A tela de radar foi implementada com linhas, círculos concêntricos e um angular gradient rotativo de 20 graus de largura
  • Depois, o efeito da linha do radar foi simplificado para um angular gradient de 360 graus
    • Um gradiente que vai do verde ao transparente e ao preto é rotacionado para parecer uma varredura com rastro
    • Ele avaliou que a abordagem “grug-brained” às vezes funciona melhor
  • Ao girar o aparelho rapidamente, surgiam artefatos visuais com pedaços do mapa aparecendo nos cantos da tela, e isso foi resolvido com uma reverse mask que cobria de preto a área fora da view do radar

Produto final: efeito CRT, som, configurações e App Store

  • Para reforçar a sensação de brinquedo retrô, ele adicionou um efeito de tela CRT
    • Usou o suporte embutido a shaders Metal em colorEffect do iOS 17
    • O shader aplica um valor de scanline a pixels que não são pretos
    • Ao passar um parâmetro time, as scanlines se movem rapidamente para cima, dando uma sensação dinâmica
  • Para confirmar se o cadastro na App Store era aceitável segundo a política da OpenSky Network, ele entrou em contato e recebeu resposta em menos de 20 minutos
  • Para melhorar a experiência de radar e a acessibilidade, ele adicionou som do sistema e feedback tátil quando os voos eram atualizados
    • AudioServicesPlaySystemSound(1052) reproduz o som beep-boop
    • Foi usado sensoryFeedback(.levelChange, trigger:) do SwiftUI
  • Como o som poderia incomodar, ele adicionou configurações com base em @AppStorage
    • silent: modo silencioso
    • showMap: permite desligar o overlay de radar e ver o mapa abaixo
    • userColor: escolhe a cor do radar com o color picker do SwiftUI
  • Como havia cancelado a assinatura do Midjourney, ele usou o gerador gratuito não comercial da Gencraft para criar o mascote do Aviator, com aparência de sua filha usando um boné de aviador
  • Ao se reinscrever no Apple Developer Program, ele pagou £79 e preparou o app para distribuição na App Store
    • Embora o alvo fosse apenas iOS 17, ele ainda precisava enviar screenshots de iPhone de 6,5 e 5,5 polegadas
    • Como o iPhone 8 Plus, o iPhone mais recente de 5,5 polegadas, só vai até o iOS 16, ele usou o AppScreens para exportar screenshots nos dois tamanhos
  • O app foi publicado na App Store, e o link de download é Aviator — Radar on your Phone

Possíveis candidatos para a próxima versão

  • Ele ficou satisfeito com o resultado desse projeto paralelo feito em algumas noites e disse que criar um brinquedo para a filha foi a experiência de programação mais divertida que teve em muito tempo
  • Os recursos candidatos para a próxima versão são os seguintes
    • Adicionar nível de zoom do mapa para limitar o radar a aeronaves mais próximas
    • Usar a API avançada da OpenSky Network para mostrar helicópteros, satélites e classes de tamanho de aeronaves
    • Adicionar um toggle para mostrar os países de origem e destino das aeronaves
    • Melhorar o efeito de tela CRT com shaders Metal mais avançados
    • Refatorar os controles em um modal pull-out com progressive disclosure e tamanho ajustável
    • Implementar filtros deslizantes de distância e altitude para ocultar, por exemplo, aeronaves baixas e distantes
    • Implementar um zany mode que renderiza OVNIs, insetos gigantes e alienígenas no radar

1 comentários

 
GN⁺ 2023-11-28
Opiniões no Hacker News
  • Trabalho incrível, e o texto também é ótimo. É interessante ver o quanto o design do display de radar original influencia fortemente este projeto
    Talvez a criança nunca tenha a chance de mexer em um ASR-9 com display PPI, muito menos em um CRT de verdade, mas houve bastante esforço para simular isso
    Pode ser também por gostar de esqueumorfismo, mas parece ser porque a tela de radar original sintetiza muito bem a pergunta: “há algo no céu e onde isso está em relação a mim?”
    As UIs atuais para observação do céu parecem ter caminhado para informações de contexto, links para serviços externos e espaço publicitário, e perdido a capacidade de responder facilmente a essa pergunta

    • Gosto do fato de a tela de RADAR ser produto da tecnologia que a criou, isto é, a varredura, e ao mesmo tempo uma representação intencional para mostrar claramente o objeto observado
      A distância do alvo era representada pela distância a partir do centro, e o rumo pelo ângulo ao redor da circunferência
      Ainda assim, a parte que imita a varredura talvez tenha sido uma decisão de design questionável; já as cores vivas e o fundo sem mapa parecem totalmente ok, pois mantêm o foco na intenção da tela
      Também dava para ver que, ao girar segurando o telefone, o recurso de rastreamento pela bússola quebra a ilusão da varredura
    • Nem toda UI de observação do céu foi nessa direção. O SkyMap também vale a pena conferir
      https://github.com/sky-map-team/stardroid
    • Um serviço que ainda captura bem “há algo no céu e onde isso está em relação a mim?” é o modo AR do FlightRadar24
      Entendo todos os elementos técnicos envolvidos e também opero minha própria estação ADSB, mas ainda é incrível apontar a câmera para o céu e ver os metadados dos aviões no campo de visão
    • Acho que foi legal porque o ponto de partida era muito puro. Começou com “para qual direção devo olhar para encontrar o avião?” e foi construído a partir daí
  • Excelente. Coloquei na minha lista de coisas para fazer algum dia
    De forma parecida, fiz um diário de voos para meu filho de 5 anos e comecei quando ele era bebê. Em cada voo, faço ele perguntar à tripulação se o comandante poderia escrever algo
    Registramos a rota, o tipo de aeronave e acontecimentos; a tripulação realmente adora esse tipo de coisa. Já visitamos a cabine e a área de descanso da tripulação, e as mensagens que escreveram sempre foram carinhosas
    Toda vez que ele recebe o diário de volta, abre um sorriso enorme; recomendo muito para um pequeno fã de aviões

    • Meu pai começou um diário de bordo para mim quando eu era bem pequeno, e continuo atualizando até hoje, já na casa dos 30. Ele tem todos os voos que já fiz até agora, então é uma ideia realmente muito legal
    • Mais um voto para o diário de voos. Quando minha filha tinha cerca de 8 semanas, fizemos London - New York, e ainda ficou uma ótima foto dela usando o boné do comandante na cabine
    • Genial. Mesmo já adulto, ser convidado para a cabine foi algo muito legal, então com certeza vou começar isso no próximo voo
  • Gostei de alterar a cor do display estar entre os recursos essenciais da versão inicial. Isso mostra uma compreensão bem sólida das necessidades do público-alvo
    Qualquer brinquedo que tenha seletor de cores já agrada uma criança de 3 anos

    • Eu, homem adulto, também fico empolgado quando há um seletor de cores
      Basta imaginar a empolgação irracional de passar de 250 de karma no HN
    • É um dos componentes mais subestimados do SwiftUI
      Mesmo se eu mostrar hoje, a primeira coisa com que a criança vai se importar é escolher a cor, e isso também é bom para o desenvolvimento da coordenação motora fina
  • A parte “exagerei tanto a empolgação com viajar de avião que a criança ficou surpresa ao descobrir que precisava pegar um táxi até o aeroporto. Ela achava que sairia de casa e entraria direto no avião a pé” é realmente adorável, engraçada e compreensível

  • Na parte em que a tripulação vê você com uma criança fofa fã de aviões e deixa visitar a cabine, usar macacões laranja da NASA combinando é um passaporte para qualquer cabine
    A tripulação fica mais animada que as crianças, e também fica muito fácil encontrar a criança no aeroporto

    • Genial. Então só falta eu ou minha esposa entrarmos na NASA. Será que precisam muito de desenvolvedores iOS no espaço?
  • Também tem relação o texto An app can be a home cooked meal, sobre clonar o app de rede social fracassado Tapstack apenas para uso familiar
    https://www.robinsloan.com/notes/home-cooked-app/

    • Marquei nos favoritos porque é um texto lovely. Concordo que, mesmo com 0 downloads, o processo em si deve ter valido a pena
      Claro, viralizar também não seria ruim
  • Tenho duas pequenas ressalvas
    Quando os pontos dos aviões se movem depois de serem desenhados, isso quebra um pouco a imersão. Acho que pareceria mais realista se os pontos fossem “pintados” pela varredura e ficassem fixos até a próxima varredura
    Para aumentar o realismo, também daria para extrapolar a partir dos pontos de dados anteriores e fazer cada avião se mover de forma consistente a cada varredura

    • Ambos são pontos válidos. Se o timing e o ângulo forem bem ajustados, o primeiro parece totalmente viável
      O segundo pode até ser mais simples. Como a API retorna a velocidade do voo, dá para calcular mesmo com apenas um ponto de dados
  • Uma boa atividade para fazer com a filha é ir a uma rua, estacionamento ou parque que fique no fim da direção de pouso da pista do aeroporto internacional mais próximo
    Normalmente isso varia conforme a direção do vento, então é só ir de acordo e parar o carro para ver os aviões chegando e pousando
    A criança pode ver no próprio radar qual avião está vindo; nós usamos o flightradar24
    As crianças ficaram completamente hipnotizadas ao ver grandes jatos em aproximação descendo tão perto do solo, e isso entretém facilmente por horas. Tivemos que negociar, porque elas não queriam ir embora, dizendo que queriam ver se o próximo avião seria maior ou passaria mais perto

    • Para o autor, acho que seria o London City Airport (LCY), onde o ângulo de aproximação é bem íngreme
      Entre aeronaves multimotoras de asa fixa até o porte do Airbus A318, só podem operar ali aquelas com certificação especial de aeronave e tripulação para realizar uma aproximação de 5,5°
      https://www.youtube.com/watch?v=yZIDFgpT0-o
      https://en.wikipedia.org/wiki/London_City_Airport
    • Perto de D.C., Gravely Point é exatamente esse lugar. Seja na decolagem ou no pouso, dá a impressão de que você consegue contar até os rebites do avião
    • É bom levar protetores auriculares
    • No ensino médio, havia um terreno de cascalho logo do lado de fora da cerca do aeroporto, no fim da pista. Quando a direção do vento estava certa e os aviões decolavam passando por cima daquele terreno, era um bom lugar para parar o carro e namorar
    • Não é exatamente igual à sugestão, mas no SFO há o Sky Terrace na área antes da inspeção de segurança, de onde dá para observar aviões
  • Há um problema com este app. A página do app diz claramente 4 anos ou mais, mas o usuário-alvo tem 2 anos
    Ficou bem feito

    • Espero que ele consiga ficar abaixo do radar da polícia da App Store
  • Excelente
    Uma perguntinha sobre um detalhe: os escopos de radar CRT realmente tinham linhas de varredura? Eu imaginava que fossem displays vetoriais
    Sobre a lista de recursos adicionais, como é um app para observar aviões, seria legal se, ao tocar em um ponto, ele mostrasse o modelo da aeronave por alguns segundos

    • Correto. As telas de radar antigas eram basicamente como osciloscópios, em que a posição X/Y era controlada pelo ângulo do radar e pela distância atual do reflexo de radar. Era uma espécie de plotagem em coordenadas polares
      Graças à persistência do fósforo, reflexos de radar fortes permaneciam por um instante na tela. Se, nesse intervalo, o radar completasse uma ou mais voltas, o mesmo avião aparecia como um novo ponto ligeiramente deslocado, ou seja, um “plot”
      Também era possível marcar os plots na tela com caneta hidrográfica para formar uma “track”
      Havia ainda telas especiais em que uma câmera de cinema ficava apontada para a tela do radar, gravando horas de reflexos de radar para reprodução posterior
      Por exemplo, há um registro em reprodução acelerada de aeronaves do Pacto de Varsóvia durante a Revolução da Tchecoslováquia de 1968: https://youtu.be/rAUodXI4LPw?t=622
    • Era um display vetorial, sim, mas talvez de um jeito um pouco diferente do que você está imaginando
      As telas de radar originais varriam radialmente, do centro para fora. O tempo da varredura era predeterminado para escalar a distância, e o sinal de intensidade do feixe era o próprio sinal amplificado do reflexo de radar
      Assim, sinais refletidos mais fortes apareciam como “blips” mais visíveis em uma tela de longa persistência
      A parte interessante é que, para fazer o feixe do radar varrer ao redor da tela CRT, todo o conjunto emissor do tubo de raios catódicos era acionado por um motor sincronizado com a antena parabólica giratória do radar
      Essa rotação precisava sempre corresponder à velocidade e à direção da antena do radar; caso contrário, os blips apareciam no lugar errado
      Linhas fixas de azimute e distância eram impressas no próprio tubo CRT ou em uma cobertura transparente. Esse tipo de tela foi usado por décadas, talvez até os anos 1980 ou início dos anos 1990
      Versões mais recentes podiam fazer a varredura independente nas direções X/Y com circuitos eletrônicos simples, para evitar o complexo conjunto emissor de feixe rotativo
      Mais detalhes: https://en.wikipedia.org/wiki/Radar_display#Plan_position_in...
    • Ampliando um pouco o que as outras respostas apontaram, o feixe de fato varre radialmente do centro para fora
      O ângulo pode ser produzido girando fisicamente as placas de deflexão, ou calculando eletronicamente sin/cos e aplicando isso às placas de deflexão X/Y
      Como a amplitude do sinal de retorno aciona diretamente a corrente do feixe, alvos com reflexos maiores aparecem mais brilhantes
      Também há aqui um efeito de tubo de armazenamento, que não funciona como o modo biestável dos displays vetoriais X-Y com tubo de armazenamento dos primeiros computadores, mas sim como o modo de persistência variável de alguns osciloscópios
      Essa característica também gerou consequências relacionadas a tentativas iniciais de projeto de aeronaves stealth, como o SR-71 ou o XB-70
      Nos tubos de radar, a corrente do feixe era ajustada para que os “blips” se acumulassem ao longo de várias varreduras, e normalmente um blip se deslocava menos do que o próprio diâmetro entre uma varredura e outra
      Mas, se a seção transversal de radar do avião fosse inerentemente baixa e ele fosse rápido demais, de modo que o blip se movesse uma distância maior entre as varreduras, o operador do radar poderia interpretar os pequenos blips fantasma como ruído, especialmente em uma situação caótica com muitos alvos