MapoGPS

6 minuto(s) de leitura

Utilitário para a transformação de coordenadas geográficas para posições no Guia Mapograf.

Se você está em São Paulo, ative a localização e se possível, ligue o GPS. Confia, vai ser legal!

Github: https://github.com/lucasoshiro/MapoGPS

É… o quê?

Até há não tantos anos atrás, não existia Waze e nem Google Maps. O que todo mundo usava, era o bom e velho guia de ruas.

Pessoalmente, eu não gosto de uma voz dizendo onde eu tenho que virar enquanto estou dirigindo, e tampouco fico confortável em acreditar cegamente no caminho que um app decide para mim. Também não gosto da forma como as ruas são exibidas nesses apps, prefiro a forma como elas são apresentadas no guia. Então sim, apesar de minha não muita idade (sou de 1996), ainda carrego um guia Mapograf comigo.

Então, fiz este pequeno utilitário para converter coordenadas geográficas de um GPS na posição em um guia, pelos seguintes motivos:

  • eu estava entediado;
  • eu pude trazer um pouco de tecnologia a algo mais tradicional.

Como isso é feito?

Sistema de coordenadas do guia

As páginas que contém os mapas são numeradas de A1 a A99, de B1 a B27 e de 1 a 432. Para o posicionamento dentro de cada página, há uma numeração de 1 a 30 representando as linhas, e de A a Z (com algumas letras faltando…) representando as colunas.

Overview da conversão

A conversão é feita em três etapas:

  1. Conversão da latitude e longitude em coordenadas em um plano, cuja origem é o Marco Zero de São Paulo, e usando metros como unidade;

  2. Conversão desse plano intermediário em um outro plano, cujas unidades são a largura e a altura de uma página do guia, e a origem é o encontro do meridiano mais a oeste com o paralelo mais ao norte do guia;

  3. Conversão para páginas e posições do guia, a partir do plano definido na etapa 2.

Parece um cachorro, mas na verdade é o pior mapa de São Paulo que você vai ver na vida. No ponto vermelho, um local e suas coordenadas geográficas.
O mesmo local, mas agora com a posição relativa ao marco zero da cidade, após a etapa 1.
Sobreposição da grade das páginas do guia (em azul) sobre São Paulo. Em preto, a posição em (altura da página, largura da página) relativa à origem da grade, após a etapa 2. Em laranja, os números das páginas, após a etapa 3.
Localização dentro da página.

O desenvolvimento, porém, eu fiz na seguinte ordem:

A etapa 3

A etapa 3 é simples: no guia, antes das páginas referentes ao mapa em si no há um pequeno mapa mostrando sua a abrangência. Nele, é mostrada em que página cada porção da área abrangida é mostrada. É possível ver, assim, qual o posicionamento das páginas, e qual página dá continuidade a outra.

A partir dele, foi feito a matriz que está no arquivo pages.py. Note que nessa matriz pages há também páginas sem nome, e que representam áreas que o guia não apresenta, mas que estão dentro dos limites de abrangência. A partir da matriz pages, defini um plano cartesiano, em que a origem é o canto superior esquerdo do que seria a primeira página da primeira linha da matriz (e que é uma página que não existe). O eixo das ordenadas desse plano tem como unidade a altura de uma página, e o eixo das abscissas tem como unidade a largura de uma página.

Desta forma, é simples fazer a transformação de uma posição no guia para esse plano cartesiano e vice-versa: a Catedral da Sé, por exemplo, fica em 151 Z 3. Como a página 151 fica na linha 13 e na coluna 15, sabemos que a parte inteira da posição no plano é (13, 15). Já a parte decimal é calculada a partir da posição dentro da página, Z 3, ou seja, a posição no plano é (13.0834, 15.975).

A etapa 1

Converter coordenadas geográficas para metros apresenta alguns problemas, e para facilitá-los tive que fazer algumas suposições.

Supondo que a terra é uma esfera perfeita, um grau de latitude tem o mesmo comprimento em qualquer local da superfície do planeta: aproximadamente 10001.965729 km.

O comprimento de um grau de longitude, porém, varia de acordo com a latitude. A fórmula para o cálculo do tamanho em metros de um radiano de longitude é dada pela seguinte função:

m(lat) = cos(lat) * R

sendo R o raio da Terra em metros, e lat a latidude em radianos.

Para calcular o tamanho médio de um radiano de longitude entre dois pontos de latitudes diferentes, basta dividir a integral de m entre as latitudes e dividí-la pela diferença entre as latitudes. A partir dele, podemos calcular a o tamanho médio de um grau, ou seja:

(π * R / 180) * (sen(π * b/180) - sen(π * a / 180)) / (b - a)

sendo a e b as duas latitudes, em graus.

Uma vez calculado o tamanho médio de um grau de longitude, basta multiplicá-lo pela variação de longitude para obter a variação em metros no sentido norte-sul. E basta multiplicar o tamanho de um grau de latitude pela variação de latitude para obter a variação em metros no sentido oeste-leste. Ufa!

Agora faço uma segunda suposição: a Terra ser plana na cidade de São Paulo. Da mesma forma que a suposição feita anteriormente, isso vai produzir erros, mas que serão de ordem irrelevante para esta aplicação.

A partir dessa suposição, é calculada a posição relativa do ponto em relação ao Marco Zero de São Paulo, em metros. Desta forma, podemos posicioná-lo em um plano cartesiano que toma como origem o Marco Zero de São Paulo, adotando o metro como unidade.

A etapa 2

Na etapa 3 foi definido um plano cartesiano que representa o guia, e na etapa 1 foi definido um plano no sistema métrico. Esta etapa consiste em converter um plano em outro, e é, portanto, a parte principal da conversão.

A conversão entre os dois planos é feita através de uma transformação linear. A matriz de transformação, porém, é desconhecida.

Para descobrir a matriz de transformação, foram amostrados vários pontos no guia Mapograf, e tomadas suas coordenadas geográficas. Esses pontos estão no arquivo pointset.py. A posição desses pontos nos planos foi calculada, de acordo com as definições descritas anteriormente. Tendo em mãos as posições em ambos os planos, é feita uma regressão linear, que nos dá uma matriz de transformação que minimiza o erro da transformação de um plano em outro. Eba!

Execução

Dependências

  • Python 3
  • Numpy

Uso

python3 mapo_gps.py

As coordenadas geográficas devem ser passadas na forma decimal através da stdin, e serão devolvidas as páginas e posições de página correspondentes.

Erros?

Este programa foi feito para meu uso pessoal, e estou compartilhando ele aqui. Não sou especialista em GIS, nem em álgebra linear, e nem em machine learning.

Se você encontrar algo errado no código ou mesmo neste README, ficarei feliz em ouví-lo em uma issue ou um pull request.

Update 2023

Três anos se passaram desde que eu escrevi este programa. Alguns meses depois de quando eu escrevi a primeira versão deste texto, eu comecei a trabalhar no backend de uma empresa de entregas. E por pura coincidência, eu fiz parte da equipe que cuidava do serviço de geocodificação interno! Lidar com mapas e coordenadas geográficas, que aqui tinha sido diversão, em seguida se tornou rotina por dois anos!

Por algum motivo, na etapa 1 eu resolvi reinventar a roda e tentar deduzir como calcular distâncias de coordenadas geograficas. Hoje vejo que perdi muito tempo fazendo isso, sendo que poderia ter usado a fórmula Haversine que faz exatamente isso.

No meio desses três anos, eu escrevi um app para Android baseado neste script. Como esse app é para uso pessoal, eu não tenho intenção de publicá-lo na Play Store. Não fiz nada nele além de pegar as coordenadas geográficas do GPS, passar pela transformação que descrevi aqui, e mostrar na tela os resultados. Deixei o código-fonte dele no meu Github, caso alguém se interesse: https://github.com/lucasoshiro/MapoGPSAndroid.

Atualizado em: