domingo, 10 de agosto de 2014

Usando Graphviz para Visualização e Gerenciamento de Dependências

O Gerenciamento de Dependências (Dependency Management) é uma atividade muito útil não apenas em programação e projeto de software, mas em qualquer atividade minimamente complexa em que se deva construir ou executar alguma coisa.

Especificamente em se tratando de software, inúmeras características de qualidade em arquitetura, como modularidade, arquitetura em camadas, information hiding e gerenciamento da complexidade em geral são, em sua essência, atividades de detecção e organização de dependências.

Para que a discussão que se segue seja baseada em um exemplo concreto, vou considerar uma situação real que me aconteceu hoje:

Sou cadastrado no site Coursera, que é um site agregador de cursos online para educação à distância (a ideia me agrada, embora ainda não tenha me engajado em acompanhar de fato algum curso, até agora). Sendo assim, recebi de forma inesperada um email oferecendo vários cursos, e me interessei pelo curso chamado "Data Scientist's Toolbox", da Universidade Johns Hopkins.

Rapidamente descobri que existem cursos relacionados a esse, mas logo em seguida descobri que os cursos não só eram relacionados, como faziam parte de uma série de cursos, e que havia relações de dependência entre eles. Ou seja, era sugerido, ou mesmo requerido, que para cursar determinados cursos, os pré-requisitos já tivessem sido cumpridos.

As dependências são apresentadas em um PDF, que não é exatamente uma forma legível de apresentar uma lista exaustiva de dependências:

Cada curso pode ter dependências "hard"
ou "soft" com relação a outros cursos.


Impaciente para fazer o malabarismo mental - e receoso de esquecer alguma dependência por "erro de leitura" - decidi fazer um gráfico das dependências.

A forma mais fácil de fazer um gráfico é desenhar o gráfico, mas essa é a forma que eu menos gosto de usar, evitando fazer qualquer coisa à mão sempre que possível. Em especial, não só o melhor layout é difícil de aceitar, como qualquer modificação posterior pode envolver redesenhar tudo novamente. Como um bom nerd, me recuso a fazer isso, e depois de muito pesquisar descobri a ferramenta Graphviz, que transforma a descrição de um grafo, sob a forma de um arquivo de texto simples em alguma linguagem de descrição de grafos (a mais comum é a linguagem DOT), na imagem de um grafo propriamente dito, com layout automático configurável.

Bom, o gráfico gerado pelo Graphviz ficou assim:

Dependências "hard" mostradas em preto;
Dependências "soft" mostradas em cinza;
A seta se origina na dependência em direção ao elemento dependente

Se o leitor quiser saber como esse gráfico foi gerado (com qual programa, e a partir de qual arquivo-fonte), isso será explicado no final da postagem. Agora vou comentar sobre como a visualização desse gráfico ajuda na análise do problema, ou seja, no gerenciamento de dependências propriamente dito.

A primeira coisa que chama atenção é que TODOS os elementos dependem de "Data Scientist's Toolbox", e também de "R Programming". Isso polui muito o gráfico, dificultando ver as outras dependências entre os módulos. Antes de começar a reorganizar graficamente essas dependências, convém fazer duas considerações:
  1. Muitas vezes, se A depende de B e B depende de C, não necessariamente precisamos concluir que A depende diretamente de C. No gráfico, não é possível afirmar que os módulos que dependem de "R Programming" dependem direta ou indiretamente de "Data Scientist's Toolbox";
  2. No caso da dependência entre "R Programming" e "Data Scientist's Toolbox", ela é qualitativamente diferente das outras (porque é "soft"), portanto temos mais um motivo para não colapsar inconsequentemente as dependências entre " ' Toolbox" e o restante dos módulos.
Ainda assim, como casualmente TODOS os módulos que dependem de um também dependem de outro, vamos colapsá-los em um sub-grafo chamado "Basics":

Com a criação do elemento "Basics", o gráfico fica muito mais limpo e legível.

Bom, agora ficou bem mais fácil perceber as "dependências herdadas" mencionadas acima. Essas dependências são da forma "A depende de B que depende de C, mas A também depende de C", conforme visualizado no gráfico por uma seta "pulando" diretamente de um elemento a outro, mas também "passando" por um elemento intermediário. A conclusão correta é que, se A depende de B que depende de C, não é necessário afirmar de forma explícita - e redundante - que A depende de C. Portanto essas dependências redundantes podem ser e serão removidas:


 Agora sim, podemos dizer que o gráfico está em sua forma "canônica", com todas as redundâncias agrupadas, colapsadas ou removidas. Podemos então, tirar algumas conclusões diretas a partir da visualização do gráfico:
  • O módulo mais avançado é "Practical Machine Learning", requerendo no mínimo outros três módulos sequenciais ("R Programming", "Statistical Inference" e "Regression Models"), mas preferivelmente outros cinco módulos (os já citados, mais " ' Toolbox" que é soft e "Exploratory..." que além de soft pode ser cursado em paralelo com "Statistical..." ou "Regression...");
  • "Getting and Cleaning Data" não depende nem é pré-requisito dos outros módulos exceto o básico. Portanto, se meu interesse maior for esse, eu posso ver que vai dar pouco trabalho e que não preciso me preocupar em cursar ou não os outros módulos;
  • Não existe uma ordem pré-definida para cursar os módulos que dependem apenas do módulo básico. Essa é uma característica benéfica de descrever um processo em forma de dependências, ao invés de "etapas" (este, depois este, depois este, etc.).
Uma coisa fundamental que deve ser dita, e que diferencia entre a visualização de dependências e o verdadeiro gerenciamento de dependências, é que, ao contrário deste caso em que as dependências já vieram prontas, a maior utilidade do método é quando estamos analisando as dependências de algo que nós estamos desenvolvendo: ao detectarmos dependências espúrias, podemos intervir para eliminá-las, melhorando a qualidade do desenvolvimento.

Como gerar os gráficos

O primeiro passo é criar um arquivo na linguagem DOT. O arquivo que gerou o penúltimo gráfico encontra-se abaixo, com comentários no formato // destacando os pontos chave:
 
digraph g {
rankdir = "LR"   // faz com que as dependências ocorram "left to right"
label = "Data Science Course Series\nJohns Hopkins University"

node [shape="rectangle"]  // faz com que o formato padrão dos nós seja um retângulo

// definição de variáveis do tipo "node", com as respectivas labels
toolbox     [label="Data Scientist's\nToolbox"]
r           [label="R Programming"]
getting     [label="Getting and\nCleaning Data"]
explore     [label="Exploratory\nData Analysis"]
reproduce   [label="Reproducible\nResearch"]
statistical [label="Statistical\nInference"]
regression  [label="Regression\nModels"]
machine     [label="Practical\nMachine Learning"]
products    [label="Developing\nData Products"]


// clusters são definidos com subgrafos cujo nome têm o prefixo "cluster"
subgraph cluster_basics {
    style=filled
    fillcolor="#ffffff"
    
    label="Basics"
    
    toolbox -> r [color="#cccccc"]   // cor cinza simbolizando "soft"
}

explore -> machine [color="#cccccc"]  // "soft"
explore -> products [color="#cccccc"]  // "soft"


// hard dependencies
subgraph hard {  

    // "ltail" (logic tail) posiciona explicitamente a origem da seta

    r -> getting [ltail=cluster_basics]
    r -> explore [ltail=cluster_basics]
    r -> reproduce [ltail=cluster_basics]
    r -> statistical [ltail=cluster_basics]
    
    r -> regression [ltail=cluster_basics]
    statistical -> regression
    
    r -> machine [ltail=cluster_basics]
    regression -> machine
    
    r -> products [ltail=cluster_basics]
    reproduce -> products
}


}

Esse arquivo precisa ser interpretado por algum programa que vai gerar a imagem propriamente dita. No site do Graphviz há instaladores para todas as plataformas. No Linux, usei a linha de comando da seguinte forma:

dot -T svg -o "graph.svg" "graph.gv" 

No Windows, tenho usado a interface gráfica GVEdit. Tudo isso baixado e instalado a partir do site de forma bastante tranquila.

WYSIWYG com três truques malandros

Evidentemente a construção de um arquivo desses é muito melhor quando é interativa: a cada elemento adicionado, geramos um novo grafo e vemos se ele está crescendo da forma como desejamos. Que eu saiba, não há um editor que faça isso de forma contínua, tipo uma "IDE" para a linguagem DOT, mas é possível fazer algo bem parecido da seguinte forma:
  1. Configurar alguma mini-IDE (editor de texto que tenha o comando "Run" para executar scripts, eu uso o Geany) para rodar a linha de comando acima com um atalho de teclado;
  2. Gerar uma saída em formato .SVG, para que a qualidade seja independente de zoom;
  3. Criar um HTML estático que mostre o SVG em um elemento de imagem (largura e altura proporcionais ao tamanho da janela), e que se auto-atualize usando uma tag
    <meta http-equiv="refresh" content="1">
     
Assim, posso trabalhar com a tela dividida ao meio, e cada vez que aperto F5 o arquivo DOT é salvo, o SVG é gerado, e sua exibição é, em no máximo um segundo, atualizada na janela do navegador:

Se isso não é WYSIWYG, então nada é, não é mesmo?

sábado, 12 de julho de 2014

Purismo vs. Pragmatismo: A definição de Testes como melhor forma de especificação de um sistema

Hoje li (pela segunda vez) esse trecho do livro Object Oriented Software Construction, de Bertrand Meyer (leitura altamente recomendável):

"Em Tecnologia Orientada a Objetos, os objetos descritos por uma classe são definidos apenas pelo que é possível fazer com eles: operações, e propriedades formais dessas operações (os contratos).
(...)
A tradição na modelagem dos sistemas de informação geralmente pressupõe uma "realidade externa" que antecede qualquer programa que a utilize; para um desenvolvedor orientado a objetos, essa noção não tem sentido, pois a realidade não existe independentemente daquilo que se pretende fazer com ela. (Mais precisamente, é irrelevante se ela existe ou não, pois nós só conhecemos aquilo que podemos usar, e o que sabemos a respeito de alguma coisa é definido inteiramente pela forma como podemos usá-la)."

Essa ideia reflete bem duas diferentes posturas com relação à modelagem e ao desenvolvimento de software: uma purista, e a outra pragmática.

A visão purista busca modelar os objetos de forma platônica, em que as propriedades e operações características derivam de sua própria natureza. São os tipos abstratos de que fala Meyer. Objetos que existem na realidade tenderiam a ser modelados em sua completude, e de forma que cada propriedade ou operação computacional fosse a contrapartida de uma propriedade ou operação real.

A outra visão, pragmática, é aquela proposta pelo último parágrafo citado: a definição de uma classe é a definição de suas operações, ou seja, deve ser representado aquilo que é conveniente representar, pois essa é a realidade relevante. Essa visão se mostra aparentemente mais saudável para orientar o desenvolvimento de software, especialmente após algumas leituras sobre Test Drivem Development.

São perspectivas opostas sobre o mesmo tema, como duas faces da mesma moeda: na primeira, aquilo que se pode fazer com o objeto é consequência da definição de sua natureza canônica, que vem primeiro e é tratada como sendo mais importante; na segunda, a declaração das operações possíveis é que de fato acaba por definir o objeto.

A tentativa de esgotar a definição de um objeto a partir de sua natureza intrínseca, independente de suas potenciais aplicações, pode facilmente levar à paralisia por análise (analysis paralysis), que foi um sintoma já observado para algumas classes e propriedades do Modelo de Dados de GPS, tratado em postagens anteriores.

Portanto, o plano daqui por diante, no desenvolvimento do Modelo de Dados de GPS, é inverter - mesmo que temporariamente -  essa abordagem bottom-up na definição de um modelo de domínio e modelo de dados, e passar a utilizar algumas práticas da metodologia agile, as quais seriam principalmente:
  • Desenvolvimento Orientado a Testes (test-driven development, TDD). Nessa metodologia, o teste é definido antes, e a implementação é levada adiante apenas para satisfazer o teste. Assim, temos a descrição do teste como a especificação definitiva do sistema (pois descreve como ele deve se comportar), e o código-fonte como a descrição definitiva do sistema propriamente dito.
  • Desenvolvimento Orientado por Comportamento (behavior-driven development, BDD), uma vertente do TDD, que pode ser equiparado (de forma generosamente relaxada) ao Design Dirigido por Objetivos (goal-directed design, Alan Cooper). Ambas as propostas buscam orientar qualquer desenvolvimento de software à satisfação dos objetivos do usuário. Assim sendo, cada linha de código, cada método, cada classe deve ter sua existência à satisfação de alguma necessidade real do usuário. Para isso, o software deve ter um conjunto de funções, que deriva de um conjunto (lista) de requisitos, que por sua vez deriva (ou deveria derivar) de um conjunto de casos de uso. Esses casos de uso (associados a personas e cenários) são justamente a definição dos testes do BDD.
  • YAGNI: you ain't gonna need it. Implementar somente o que é necessário. Isso se opõe ao anti-pattern "big design up-front", que é o que eu (inspirado, é verdade, na ISO) vinha tentando fazer. Nas palavras de Ron Jeffries: "implemente funcionalidade apenas quando você realmente precisar, e não quando você apenas pensa que vai precisar".
Portanto, o plano é deixar um pouco de lado a super-especificação de implementação, e abordar o lado oposto do desenvolvimento, que são os usuários e as aplicações pretendidas, e ao invés de ter um modelo de implementação exaustivo, ter um sistema que surja simples porém operacional (atendendo a alguma necessidade inicial básica), podendo a partir de então evoluir, ser refatorado, e ser expandido.

sábado, 28 de junho de 2014

Modelando entidades geográficas lineares: a classe Track

Uma vez que já foi modelada a classe Position, que armazena as coordenadas de um ponto no espaço geográfico, um próximo passo é modelar alguma das entidades que já vimos na análise dos formatos GPX e KML.

As duas entidades mais marcantes são as entidades pontuais (Waypoints, ou simplesmente Points), e também as entidades lineares (LineString, Track, TrackSegment, Polyline, etc.).

Optei por usar o termo Track porque, além de ser um termo comumente usado (GPS Trackmaker, Google My Tracks, objeto gx:Track do KML), a palavra track em inglês representa "pista", ou "rastro", e se presta bem para uma linha que pode ter sido tanto o caminho que algo ou alguém percorreu - ou pretende percorrer - quanto algum elemento linear no espaço terrestre, como um rio, uma estrada, uma ferrovia ou uma linha de transmissão.

Matematicamente, qualquer linha no espaço geográfico é uma sequência contínua de pontos, que pode ser definida por uma função paramétrica, onde a posição varia em função de um parâmetro (tempo, distância ao longo da linha, etc.). Como em geral as entidades têm complexidade arbitrária, com ângulos e reentrâncias, é conveniente decompor uma linha em uma sequência de segmentos. Assim, a linha é descrita pela sequência de segmentos, onde cada segmento por sua vez é descrito por seus pontos de controle.

Os segmentos podem ser de vários tipos: retas, arcos, geodésicas, seções cônicas, curvas de Bézier, splines ou NURBS. Assim, uma ferrovia provavelmente terá muitos trechos retos longos e curvas suaves, enquanto uma estrada na montanha terá uma alternância entre curvas abertas e fechadas, e um rio terá formas mais orgânicas e complexas, por exemplo.

Uma característica importante desses segmentos é que, em sua definição, eles contém um número finito de pontos de controle. Um segmento de reta, por exemplo, contém um ponto inicial e final. Uma curva de Bézier possui, além dos pontos inicial e final, também dois pontos de controle. Qualquer que seja o tipo de segmento, deve ser possível interpolar uma posição intermediária, de acordo com alguma fórmula que depende do tipo de segmento. No segmento de reta, que é o mais simples, o método usado é a interpolação linear.

Interpolação linear (à esquerda) e interpolação por curvas (à direita). A representação por segmentos de reta provoca uma perda de informação que é dependente da quantidade de pontos de controle, bem como do posicionamento dos mesmos ao longo da linha.
Tanto o formato KML quanto o GPX representam as linhas geográficas como "LineStrings", ou seja, linhas compostas por segmentos de reta concatenados, em que a posição final de um segmento é sempre a posição inicial do segmento seguinte. Portanto, e por uma questão de simplicidade, é essa a estrutura que adotaremos na classe Track.

É importante lembrar que, do ponto de vista conceitual, essa simplificação faz com que haja uma incerteza sobre a linha, já que não há informação nesses segmentos. Para isso, parte-se do pressuposto que a amostragem da linha foi feita com uma resolução adequada, e que portanto os detalhes e reentrâncias da linha estão mantidos na representação.

Por exemplo, para representar uma ferrovia com longos trechos retos, pode ser que seja adequada uma resolução de um quilômetro ou mais entre os pontos. Já para representar uma trilha sinuosa que sobe a encosta de um morro, uma resolução de 5 metros ou menos parece mais adequada. O fato é que, entre um ponto e outro, não há certeza sobre a posição real da linha, exceto pela convenção de que ela não sofra variações abruptas. Portanto, calculam-se os pontos intermediários via interpolação linear, com a suposição de que o erro será pouco significativo.

Já de posse de uma boa representação para os dados da classe, vejamos quais são as operações desejáveis, baseado nos usos frequentes das Tracks em aplicativos. São elas:
  • Comprimento dos Segmentos: é uma sequência que mapeia cada posição da linha para a respectiva distância com relação à posição anterior, ou seja, é uma sequência dos comprimentos de cada segmento da linha. Por convenção, o primeiro valor é sempre zero;
  • Comprimento Total: é a soma dos Comprimentos dos Segmentos;
  • Perfil Altimétrico: é uma sequência de pares numéricos, onde cada par associa uma distância a uma elevação correspondente;

Por ora, temos uma modelagem inicial dessa classe, a ser implementada em postagens futuras. Os passos seguintes de desenvolvimento serão a implementação de leitores de arquivo para que se extraiam objetos "Track" a partir dos formatos KML e GPX, e possivelmente alguma forma simples de visualização (plotagem).


segunda-feira, 16 de junho de 2014

Refactoring das classes Position em Python e C#

Na postagem anterior, foram criadas as classes Position nas linguagens Python e C# (que são as linguagens que eu costumo trabalhar), como forma de testar o quanto nossa modelagem é independente de plataforma.

Pretendo manter a interface pública das classes o mais parecida possível entre todas as linguagens, a não ser que isso viole de forma muito óbvia alguma característica de cada linguagem.

Logo depois de publicar a postagem, me dei conta de alguns esquecimentos, algumas inadequações, e algums escolhas de design "suspeitas", de forma que, alguma reflexão e alguma pesquisa depois, venho apresentar as classes refatoradas.

Em primeiro lugar, movi o código para o GitHub, de forma que o leitor sempre terá onde encontrar o código atualizado, bem como o histórico de modificações. As postagens antigas não vão ser editadas para refletir as mudanças que forem ocorrendo, mas as mudanças mais significativas serão discutidas em postagens específicas, se for o caso.

Para este refactoring, as mudanças de API (que impactam a implementação de ambas as linguagens) foram as seguintes:
  • Implementação (conforme previamente modelado) do valor nulo para elevação, permitindo que haja um construtor com dois argumentos (latitude e longitude, nessa ordem), e um argumento opcional elevação cujo valor padrão é nulo;
  • Eliminação do construtor que usava uma sequência como argumento, pois isso enfraquecia a semântica, e dava margem a erros lógicos (pois não havia garantia de que os itens da sequência viriam na ordem correta), e de tempo de execução. Agora, a classe possui apenas um construtor, e esse construtor recebe dois argumentos numéricos, ou três argumentos numéricos. Uma discussão sobre essa decisão pode ser vista nesta pergunta do StackOverflow;
  • Apenas a Elevação possui setter. Latitude e a Longitude são somente leitura (e portanto possuem apenas getter);


Versão atual em C#:

using System;
using System.Collections.Generic;
using System.Linq;

namespace GpsDataModel
{
    public struct Position {

        private readonly double _lat, _lon;
        private double? _elev;

        public Position(double lat, double lon, double? elev = null) : this() {
            
            _lat = Math.Abs(((lat-90) % 360) - 180) - 90;
            _lon = ((lon-180) % 360) - 180;

            _elev = validateElevation(elev);
        }
        

        public double Latitude { get { return _lat; } }
        public double Longitude { get { return _lon; } }

        public double? Elevation { 
            get { return _elev; }
            set { _elev = validateElevation(value); }
        }


        private double? validateElevation(double? elev) {
            if (elev < -WGS84.SEMI_MAJOR_AXIS)
                throw new ArgumentOutOfRangeException(
                    String.Format("Elevation {0:0.00} " +
                           "is deeper than the center of the Earth!", elev));
            else
                return elev;
        }        


        public override string ToString() {
            return String.Format("Position({0:0.000}, {1:0.000}, {2:0.0})",
                                      _lat, _lon, _elev);
        }
    }
}

Comentários específicos sobre a implementação C#:
  • O argumento opcional é apresentado com um valor padrão, representado sintaticamente pelo operador "=";
  • Os tipos numéricos nullable são sufixados por um ponto de interrogação - no exemplo, "double?";
  • A lógica de validação do setter da Elevação, que deve ser usada também no construtor, foi movida para a função validateElevation;

Versão atual em Python:

#!/usr/bin/env python
# coding: utf-8

import WGS84

class Position(object):

    __slots__ = ('_lat', '_lon', '_elev')

    def __init__ (self, lat, lon, elev=None):

        self._lat = abs(((float(lat)-90) % 360) - 180) - 90
        self._lon = ((float(lon)-180) % 360) - 180

        self.elevation = elev

           
    
    @property
    def latitude(self):
        return self._lat
        
    @property
    def longitude(self):
        return self._lon
        
    @property
    def elevation(self):
        return self._elev

    @elevation.setter
    def elevation(self, elev):
        if elev > (-WGS84.SEMI_MAJOR_AXIS):
            self._elev = float(elev)
        else:
            raise ValueError ("Elevation %.2f " + "
                is deeper than the center of the Earth!" % elev)
 
 

    def __str__ (self):
        return ("Position(%.3f, %.3f, %.1f)"
                          % (self._lat, self._lon, self._elev))

quarta-feira, 11 de junho de 2014

Posição geográfica com a classe Position: Implementações em Python e C#

Anteriormente, foi discutido como representar simbolicamente a posição geográfica, dentro do sistema de coordenadas WGS84, em um contexto independente de plataforma, constituindo-se em uma classe com três propriedades numéricas (uma para cada coordenada tridimensional), todas somente leitura, respeitando certos limites:
  • Latitude, em graus, no intervalo { -90 <= Latitude <= 90 };
  • Longitude, em graus, no intervalo { -180 <= Longitude < 180 };
  • Elevação, em metros, no intervalo { -6378137 < Elevação };
 Daí decorre o seguinte modelo de design:
  • Cada instância de Position deve ser os três valores inicializados, contando para isso com um construtor com três parâmetros numéricos, e também um construtor com um parâmetro que seja uma sequência de três valores numéricos, na ordem "Latitude, Longitude, Elevação";
  • Considerando que Latitude e Longitude, ao longo da superfície da Terra, são circulares, entende-se que valores além dos limites devem ser "continuados", das seguintes formas:
    • Longitudes crescentes passam de 180 para -180, e decrescentes passam de -180 para 180, lembrando que uma posição exatamente sobre o meridiano 180, de acordo com a ISO TC 211, deve ser representada com valor negativo (ou seja, -180);
    • Latitudes crescentes, chegando a 90, começam a decrescer, conforme a sequência de exemplo [87, 88, 89, 90, 89, 88, 87...], enquanto decrescentes se comportam ao contrário: [-87, -88, -89, -90, -89, -88 ...]. Isso representaria alguém atravessando o pólo de um lado a outro.
    • Elevações podem ser negativas, desde que não ultrapassem o centro da Terra. Não se considera a continuação dessa linha em direção ao outro lado porque isso mudaria a latitude e a longitude, violando o pressuposto de independência entre as coordenadas.
A seguir, implementações comentadas em C# e Python. O código contém comentários numerados que são discutidos abaixo.

Classe Position em C#

using System;
using System.Collections.Generic;
using System.Linq;

namespace GpsDataModel
{

    public static class WGS84 {
        public static double SEMI_MAJOR_AXIS { get { return 6378137; } }
    }

    public struct Position {    // (1)

        private readonly double _lat, _lon, _elev;    // (2)


        public Position(double lat, double lon, double elev) {
            
            // (3) //
            _lat = Math.Abs(((lat-90) % 360) - 180) - 90;
            _lon = ((lon-180) % 360) - 180;
            
            
            if (elev > -WGS84.SEMI_MAJOR_AXIS)
                _elev = elev;
            else
                // (4) //
                throw new ArgumentOutOfRangeException(
                    String.Format("Elevation {0:0.00} is deeper than the center of the Earth!", elev));    
        }

        public Position(IEnumerable coords) : this(    // (5)
            coords.ElementAt(0),
            coords.ElementAt(1),
            coords.ElementAt(2)
        ) {}    // (6)
        
        
        // (7) //
        public double Latitude { get { return _lat; } }
        public double Longitude { get { return _lat; } }
        public double Elevation { get { return _lat; } }



        // (8) //
        public override string ToString() {
            return String.Format("Position({0:0.000}, {1:0.000}, {2:0.0})", _lat, _lon, _elev);
        }

    }


    // (9) //
    class Program {
        static void Main(string[] args) {
            Console.WriteLine(new Position(91, 180, 100));
            Console.WriteLine(new Position(-91, -180, 100));
            Console.WriteLine(new Position(0, -190, 100));
            Console.WriteLine(new Position(new double[]{91, 180, 100}));
            
            // (10) //
            //try {
                //new Position(0,0,0).Elevation = 20;
            //} catch (ArgumentOutOfRangeException e) {
                //Console.WriteLine(e.Message);
            //}
        }
    }

}


  1. Uso de struct ao invés de class: de acordo com a documentação do C#/.NET, structs são recomendadas para objetos pequenos que representam valor e que não serão modificados. A própria documentação sugere "pontos em um sistema de coordenadas" são bons exemplos de struct;
  2. Uso de readonly: variáveis readonly não podem ser modificadas após sua inicialização. A inicialização ocorre no construtor, obrigatoriamente;
  3. Uso de "fórmulas de validação": como foi dito, desejamos que os valores de latitude e longitude sejam "contínuos", portanto foi atribuída a fórmula da onda triangular para a latitude, e a fórmula da onda dente-de-serra para a longitude. Assim, pode-se fornecer valores de entrada fora do intervalo, e a fórmula vai convertê-los para valores válidos dentro do intervalo de cada coordenada;
  4. Tratamento de erro na validação da Elevação: Como foi dito, valores de elevação que estão abaixo do centro da Terra não são válidos. Em C#, a forma recomendável de tratar esse erro é gerar manualmente uma exceção (no caso, do tipo ArgumentOutOfRangeException), com a sentença throw;
  5. Construtor alternativo: desejamos que, além de usar no construtor os três argumentos numéricos separados, seja possível usar como argumento uma sequência com os três valores. Assim, qualquer argumento do tipo "IEnumerable" pode ser usado, o qual vai ser repassado ao construtor padrão por encadeamento, usando a sintaxe ": this";
  6. Bloco vazio após o construtor: a sintaxe do C# exige que haja um bloco após um construtor, mesmo que esse bloco esteja vazio;
  7. Propriedades públicas somente com getter: recomenda-se expor os campos privados através de propriedades públicas que tenham somente getter. No caso da classe Position, isso é (mais) uma forma de garantir a imutabilidade dos valores após criados;
  8. Representação amigável (human-readable) com o método ToString: de acordo com o livro Effective C#, recomenda-se dar um override no método ToString (herdado de Object), para que qualquer tentativa de conversão do objeto em string retorne uma string legível, que traga alguma informação sobre os valores daquela instância da classe;
  9. Programa simples de teste: para o propósito desta postagem, incluí um programa que demonstra o uso dos construtores e do método ToString. O recomendável seria ter um teste unitário para a classe, mas isso fica para uma próxima oportunidade.

Classe Position em Python

#!/usr/bin/env python
# coding: utf-8

class WGS84(object):
    SEMI_MAJOR_AXIS = 6378137

class Position(object):    # (1)

    __slots__ = ('_lat', '_lon', '_elev')    # (2)

    def __init__ (self, *args):    # (3)

        if len(args) == 1:
            lat, lon, elev = args[0]
        elif len(args) == 3:
            lat, lon, elev = args
        else:    # (4)
            raise ValueError ("Position constructor takes 3 arguments or sequence with length = 3")

        self.latitude = lat
        self.longitude = lon
        self.elevation = elev



    ## (5) ##
    @property
    def latitude(self):
        return self._lat

    @latitude.setter
    def latitude(self, value):
        self._lat = abs(((float(value)-90) % 360) - 180) - 90



    ## (6) ##
    def get_longitude(self):
        return self._lon

    def set_longitude(self, value):
        self._lon = ((float(value)-180) % 360) - 180

    longitude = property(get_longitude, set_longitude)



    @property
    def elevation(self):
        return self._elev

    @elevation.setter
    def elevation(self, value):
        if value > (-WGS84.SEMI_MAJOR_AXIS):
            self._elev = float(value)
        else:
            raise ValueError ("Elevation %.2f is deeper than the center of the Earth!" % value)


    ## (7) ##
    def __str__ (self):
        return "Position(%.3f, %.3f, %.1f)" % (self._lat, self._lon, self._elev)


## (8) ##
if __name__ == "__main__":

    print Position(0,0,0)
    print Position(90, 180, 10)
    print Position(-90, -180, 20)
    print Position(91, 181, 30)
    print Position(-91, -181, 40)
    print Position ((-30, -51, 50)) 
     
  1. Herdando de object: em Python "tudo são objects", e uma recomendação é que todas as classes herdem do tipo object;
  2. Usando __slots__ para diminuir uso de memória e evitar o uso de setattr: como nossa classe é para ser leve - o que justificou o uso de struct em C# - estamos usando a propriedade __slots__ para definir que a classe Position vai ter somente três atributos. Caso isso não fosse feito, vale a situação padrão em Python, que é a existência da propridade __dict__, que é um dicionário dinâmico existente em cada instância. Isso permite adicionar e remover atributos, e é um comportamento que também não desejamos (ao menos não nesta classe);
  3. Uso de *args como parâmetro: em Python, o asterisco antes de um parâmetro significa que os argumentos passados serão transformados em uma sequência, disponível no corpo da função como a variável args. Assim, podemos passar os três argumentos numéricos, ou uma sequência de comprimento três, tratando condicionalmente cada caso no corpo do construtor;
  4. Tratamento de erro na validação da Elevação: assim como em C#, geramos manualmente uma exceção, neste caso com a sentença raise;
  5. Getters e setters com a função property: a função property é usada para que atributos sejam acessados através de funções, que podem incluir validação, cálculo, etc.
    1. Neste caso, a classe apresentará a propriedade "latitude", por exemplo, mas não será possível atribuir um valor a ela, pois ela não possui setter, apenas getter;
    2. Os nomes das funções devem ser os mesmos dos decoradores;
    3. No caso da latitude, é usada a sintaxe "@", e a funçao property é aplicada como um decorador;
    4. Nada impede o usuário de setar diretamente a propriedade "_lat", mas a convenção de nomenclatura usa o underline no início do nome para indicar uma variável interna que não deve ser diretamente setada;
  6. Função property declarada diretamente: Neste caso, a função property é usada de forma explícita, ao invés de usar decoradores, e podemos escolher nomes diferentes para as funções se quisermos;
  7. Representação amigável com o método __str__: em Python, a conversão para string chama automaticamente o método __str__, que deve pode ser substituído conforme a conveniência;
  8. Auto-teste: por convenção, cada módulo (ou seja, cada arquivo) contém um bloco condicional que roda quando o script é chamado diretamente, mas não roda quando o módulo é importado por outro script. Isso é detectado porque a variável __name__ assume o valor "__main__" rodando diretamente. Isso costuma ser aproveitado para inserir alguns testes simples, ou algum código que permita executar uma função específica, mas também permita o reuso das funções declaradas no arquivo, através de importação.

Muito provavelmente, ambas as implementações são passíveis de melhorias. Quando e se isso for feito, haverá postagens com discussão apropriada.

Nas postagens que virão, pretendo subir na escala de abstração, entrando no mundo das geometrias definidas por posições, como Pontos, Linhas e Superfícies.

sexta-feira, 6 de junho de 2014

Implementando a posição geográfica com a classe Position

 As primeiras postagens deste blog se mantiveram bastante abstratas, mas algumas modelagens prévias foram necessárias para construir um modelo composto por elementos bem fundamentados.

Já foi argumentado que, para aplicações de uso pessoal (não profissionais), o datum WGS84, usado pelo sistema GPS, é a escolha óbvia, e que no caso de visualização em mapas, a Projeção Esférica de Mercator (usada pelo Google Maps e similares) é o padrão de facto.

Ambos os sistemas de referência (datum 3D e projeção 2D) descrevem o espaço em que os fenômenos geográficos são posicionados, e portanto a partir daqui podemos nos ocupar da definição da posição propriamente dita.

A importância da posição na informação geográfica é muito bem discutida no documento "Abstract Specification, Topic 5 - Features", onde lemos o seguinte:
  • "As coordenadas de uma entidade geográfica de interesse (feature) são o conjunto de pontos suficientes para a construção geométrica da extensão geo-espacial dessa entidade";
  • "O Mundo Dimensional (aquele definido por coordenadas) é o último nível de abstração geo-espacial "genérica" do Mundo Real. O próximo nível é chamado Mundo do Domínio, já dependente de um domínio de implementação";
  • "Cada instância de uma entidade pode ser considerada a materialização de um modelo (classe) da entidade, tendo seus atributos preenchidos com os valores específicos daquela instância".
  • "Recomenda-se que entidades com extensão espacial sejam modeladas por formas geométricas primitivas simples".
  • "Entidades geográficas são definidas por seus atributos, um dos quais consiste em alguma Geometria. Por sua vez, geometrias são constituídas por um conjunto de Pontos dispostos e conectados de determinada forma".
Vemos então que o Ponto, na forma de uma coordenada em um sistema de referência, é a unidade elementar de descrição das propriedades espaciais de qualquer entidade geográfica.

Em outro documento, o "Abstract Specification, Topic 1 - Feature Geometry", temos um extenso modelo UML que trata da representação geométrica de entidades. Pretendo abordar maiores detalhes sobre outras entidades no futuro, mas no contexto de posições representadas por pontos, o esquema geométrico define três classes importantes:
  1. A classe DirectPosition: tem a responsabilidade de armazenar os valores de coordenadas de uma posição dentro de determinado sistema de coordenadas. Apresenta três propriedades: coordinate, que é uma sequência ordenada de valores numéricos; dimensionque retorna o número de dimensões do sistema de coordenadas; e coordinate_system, que retorna o sistema de coordenadas ao qual a posição pertence;
  2. A classe GM_Point: é uma sub-classe de GM_Object, contendo uma instância de DirectPosition;
  3. A classe GM_PointRef: é uma referência a algum outro ponto pré-existente, no sentido de permitir que um mesmo GM_Point possa participar de mais de uma feature, evitando redundâncias.

Coordenada: um tipo diferente de "par ordenado"

Uma consideração importante na definição de uma "posição geográfica" é a forma como os valores que compõem a coordenada são definidos. Um exemplo pode ser extraído dos próprios formatos KML e GPX, vistos anteriormente: a diferença com que a informação é armazenada em cada um dos formatos:
  • No formato KML, a coordenada é uma string de caracteres, sem espaços, onde cada valor é separado por vírgula, especificamente na ordem "longitude,latitude,[elevação]", onde a elevação é opcional;
  • No formato GPX, não há "ordenada", pois cada Trackpoint possui um atributo chamado Latitude, e outro atributo chamado Longitude, não importante a ordem com que eles são declarados. Além disso, a elevação é um elemento adicional, que pode ou não estar presente.
Assim sendo, temos um caso (KML) em que os valores de cada eixo do sistema de coordenadas são definidos somente pela ordem. O que chama a atenção é que, no formato KML, a longitude vem antes da latitude (GML Coordinate Reference System, LonLat84_5773), o que é o contrário do que costuma ocorrer em vários outros esquemas (e aliás, cria dificuldades enormes para "copiar e colar" strings representando coordenadas...).

Quanto a isso, diversas normas ISO estabelecem a forma recomendada de uso de uma coordenada:
  • Entende-se por coordenada uma sequência de n valores escalares representando um único ponto em um espaço n-dimensional;
  • A coordenada é equivalente ao que na matemática e também na informática recebe a denominação vetor: um elemento que não é escalar, mas sim formado por componentes, e portanto tem propriedades e operações algébricas, aritméticas e geométricas vetoriais, que são qualitativamente diferentes das mesmas propriedades e operações escalares.
  • Os valores devem ser mutuamente independentes entre si;
  • O número de valores deve ser igual ao número de dimensões do Sistema de Referência;
  • A ordem dos valores é definida pelo Sistema de Referência utilizado;
Vejam que no caso do GPX a posição não é exatamente uma coordenada, pois latitude e longitude são definidos nominalmente, sem uma ordem especial.

Latitude e Longitude são qualitativamente diferentes

Quando pensamos em espaço tridimensional, geralmente imaginamos coordenadas XYZ de um sistema retangular onde cada eixo pode assumir valores que variam de -infinito a +infinito, e onde as unidades de medida em qualquer eixo são simétricas.

Já no caso das coordenadas esféricas, isso não ocorre, pelos seguintes motivos:
  • Os valores de Latitude não são infinitos, eles variam entre a latitude do pólo norte e a latitude do pólo sul, respectivamente -90 e 90 graus;
  • Os valores de Longitude não são "exatamente" infinitos, mas sim cíclicos ao redor do eixo terrestre. Se um objeto se deslocar continuamente ao longo do Equador, em algum momento ele ultrapassará o limite e "aparecerá" do outro lado de um intervalo que varia entre -180 e 180 graus;
  • Os valores de elevação podem ser positivamente infinitos (embora isso signifique praticamente trocar a geografia pela astronomia), mas se tivermos uma elevação negativa com relação ao nível do mar, e o valor dessa elevação for excedendo o raio terrestre, estaremos já criando uma elevação crescente em direção ao outro lado do globo.
Assim sendo, há restrições naturais ao intervalo válido para cada um dos valores de uma coordenada geográfica, que devem ser considerados ao definir uma classe Position.

Elevação: definir ou não definir?

Outro "tema polêmico" diz respeito à elevação. Até muito recentemente, os softwares e sites de mapas (TrackMaker, Google Maps) apresentavam a informação em 2D. Trajetos desenhados com o mouse envolviam clicar no mapa, definindo uma sequência de coordenadas 2D que constituíam uma Track. A informação tridimensional só era disponível se tivéssemos um arquivo GPS, do qual era possível plotar a altimetria em um gráfico de altitude vs distância, por exemplo.

Com a chegada do Google Earth, um imenso ganho foi obtido em termos de "sentir" o terreno durante a visualização, com a possibilidade de imaginar a dificuldade de passar por um ou por outro caminho, e desenhar o caminho preferido na forma de um Track. Para a imensa decepção, entretanto, mesmo os arquivos KML salvos no Google Earth continham "0.000" no valor de altitude...

Atualmente, existem várias fontes de dados de elevação online, derivadas do projeto Shuttle Radar Topography Mission (SRTM), e diversos aplicativos e sites já exibem informação de altimetria automaticamente, sob demanda, enquanto o usuário desenha sua rota sobre o mapa. Como em geral a elevação do terreno é função da posição geográfica, visto que cada ponto da superfície da Terra tem uma única altitude (exceto nos casos raros de penhascos verticais ou com inclinação negativa), é conceitualmente possível extrair a altitude de uma coordenada do tipo latitude,longitude, usando para isso um Modelo de Elevação Digital (Digital Elevation Model - DEM).

É importante salientar que a premissa acima - elevação como função de latitude e longitude - vale apenas para a superfície terrestre, não sendo suficiente para descrever trajetórias de objetos voadores, marinhos ou subterrâneos.

Se considerarmos que o espaço geográfico é tridimensional, e que o sistema de coordenadas que escolhemos (WGS84) é também tridimensional, sendo os dados de mapa apenas o resultado de uma projeção (ou seja, uma visualização bidimensional de entidades cuja natureza é tridimensional), então somos obrigados a afirmar que o valor de elevação faz parte de qualquer instância de uma classe Position, mesmo quando não é conhecido.

O Google Earth opta por inserir o valor "0,000" (zero) em pontos cuja elevação não é conhecida.

O KML define como opcional o terceiro valor de coordenada, assim como o GPX trata a elevação como algo que é "medido", ou seja, pode ou não estar presente. Isso contempla bem o caso em que um TrackSegment apresenta elevação na maioria dos pontos, mas por razões quaisquer possa conter pontos onde o valor de elevação é ausente.

Assim, me parece (e a discussão a esse respeito é muito bem-vinda) que inserir o valor "0,000" no lugar de "nulo" é um erro, pois zero é um valor numericamente válido, porque ele pode de fato ser zero (por exemplo no nível do mar), e é impossível distinguir, em uma aplicação se uma instância com elevação zero possui ou não possui alguma elevação. Em especial, no KML, o elemento <altitudeMode> determina como uma feature deve ser renderizada, possibilitanto ao próprio Google Earth (ou qualquer outra aplicação compatível) renderizar corretamente as entidades junto ao solo quando a elevação for zero e o modo for clampToGround.

A definição da classe Position

Depois de tudo que foi exposto, chegamos na hora de propor uma classe para a representação da posição geográfica. A seguir, as características dessa classe:
  • A classe se chamará Position. O idioma inglês foi escolhido porque é a linguagem da tecnologia e de todos os padrões utilizados até agora (ISO, KML, GPX) e das linguagens em que o sistema possa vir a ser implementado. Outros nomes seriam possíveis. A ISO propõe DirectPosition, mas não apresenta nenhum outro tipo Position que não seja "direct", e não justifica o uso de "direct" neste contexto. Aplicações web (Google, Strava, etc.) usam latlon ou latlng, mas esses nomes são pouco específicos. Também, o nome Point não me parece ser o mais adequado, pois refere-se à primitiva geométrica e/ou topológica, que contém a posição como um de seus atributos. Também foi evitado o prefixo "geo", como em "GeoPoint" ou "GeoPosition", devido à confusão entre as disciplinas Geometria e Geografia. Dado que a classe Position pertencerá a algum namespace situado em um pacote voltado à informação geográfica, o contexto de Position como uma classe geográfica fica implícito, e deverá também estar explícito em alguma documentação;
  • O sistema de coordenadas é o WGS84. Qualquer projeção 2D deverá utilizar a latitude e a longitude nesse sistema, simplesmente ignorando a elevação;
  • As propriedades (atributos) da classe se chamam latitude, longitude e elevation. O termo elevação é preferível para representar valores absolutos em relação ao geóide ou elipsóide de referência, enquanto altitude se refere à altura com relação ao solo - por exemplo, de um avião durante o vôo;
  • As propriedades podem ter seu nome iniciando com letra maiúscula ou minúscula, de acordo com a convenção da linguagem em que a classe for implementada;
  • A propriedade latitude deve respeitar a restrição $\begin{aligned}\{ -90 < latitude < 90 \}\end{aligned}$, a propriedade longitude deve respeitar a restrição $\begin{aligned}\{ -180 < longitude < 180 \}\end{aligned}$, e a propriedade elevation deve respeitar a restrição $\begin{aligned}\{ elevation < -EARTH\_RADIUS \}\end{aligned}$, onde EARTH_RADIUS deve ser uma constante definida em um namespace acessível à classe Position;
  • Nenhum construtor da classe Position deve ser capaz de gerar uma instância de Position com valores inválidos (fora do intervalo);
  • Uma posição não é mutável. Se algum código cliente quiser alterar uma posição, deve criar uma nova instância de Position com os novos valores desejados;
  • O valor de elevação, quando não conhecido, deve receber o valor nulo (NaN, None, null, ou o que for mais apropriado na linguagem de implementação).
Um comportamento que definitivamente merece debate é como validar os valores de latitude, longitude e elevação passados como argumentos ao construtor, quando esses valores excedem os limites. Como já foi argumentado, o valor de longitude muda subidamente de 180 para -180 se girarmos ao redor do equador. Por outro lado, se formos seguindo um meridiano, a latitude aumenta até 90 e passa imediatamente a diminuir (pelo meridiano oposto) até -90, e então passa a aumentar novamente. Um gráfico do primeiro deslocamento mostraria a longitude variando como um dente de serra, enquanto o gráfico do segundo seria um formato triangular (desculpem pela ausência da figura, ainda não domino a arte de produzi-las para as postagens). Se o construtor deveria limitar os valores ao máximo, ou calcular sua correspondência "podando" o excesso (provavelmente usando o operador de divisão em módulo "%"), ou simplesmente gerar um erro, ainda não tenho certeza, mas me parece que a segunda opção é matematicamente válida.

No momento, a classe não possui nenhum método, já que sua responsabilidade é somente definir uma posição geográfica válida.

Na próxima postagem, pretendo propor implementações em alguma(s) linguagem(ns), considerando que as linguagens alvo que pretendo trabalhar são Python, C# e Javascript.

terça-feira, 3 de junho de 2014

Representando a posição geográfica: Sistemas de Coordenadas, Datums e Projeções

A modelagem de entidades geográficas, assim como sua representação computacional, têm como necessidade elementar a representação de sua posição.

Em termos abstratos, a posição de um ponto, por exemplo, é determinada por um conjunto de coordenadas, devendo haver uma coordenada para cada dimensão do espaço em que o ponto existe.

No caso da geografia, temos dois espaços principais onde as posições são definidas:
  1. O espaço real, tridimensional, habitado pelos seres.
  2. O espaço cartográfico, em geral bidimensional, voltado à elaboração de mapas planos (em papel ou meio eletrônico).
A correspondência entre o mesmo ponto definido nos dois espaços é feita através de uma projeção cartográfica, que consiste em uma função matemática que transforma a posição tridimensional em posição bidimensional.
O espaço real é o espaço em que habitamos, e inclui todo o universo. No contexto da representação geográfica, temos o planeta Terra como principal objeto de referência, e em geral a Terra serve como origem para um sistema de coordenadas.

Sistema de coordenadas esférico (latitude, longitude e elevação),
com origem no centro da Terra

Na ilustração acima, vemos um sistema de coordenadas esférico, onde a letra Φ representa a latitude em graus a partir da linha do equador, e a letra λ representa a longitude em graus a partir de um meridiano de referência.

Caso a Terra fosse uma esfera perfeita, qualquer ponto em sua superfície teria a mesma coordenada de elevação, cujo valor seria igual ao raio da Terra, representado pelo segmento vermelho na figura.

O fato de vivermos em um espaço tridimensional requer obrigatoriamente que uma posição seja representada por três coordenadas, seja qual for o sistema utilizado (retangular, cilíndrico ou esférico, por exemplo), mas o sistema de coordenadas esférico apresenta a grande vantagem de termos uma coordenada de elevação que é ortogonal às outras, ou seja, cujo eixo é coincidente com o campo gravitacional.

Já o fato de representarmos informação geográfica frequentemente na forma de mapas planos (folhas de papel, telas de computador) também torna benéfico que o eixo que representa a elevação seja ortogonal aos eixos de posição horizontal, já que a gravidade faz com que o terreno natural e mesmo os espaços artificiais tenham uma estrutura predominantemente "plana".

Escolhendo um Sistema de Referência

Existem inúmeros sistemas de referência por coordenadas que permitem localizar com precisão cada posição geográfica na terra. Em geral esses sistemas foram criados em épocas e países diferentes para suprir demandas específicas relacionadas à cartografia, ou seja, à representação do espaço geográfico através de mapas de papel.

Atualmente, com a consolidação da cartografia eletrônica e da criação de informação geográfica nativamente digital, há uma facilidade muito grande de converter a posição entre dois sistemas de coordenadas quaisquer, desde que os parâmetros do datum de cada sistema de referência estejam bem documentados.

Quando falamos em datum, estamos nos referindo à descrição matemática da forma aproximada da terra, que é achatada nos pólos, dessa forma possuindo um elipsóide de referência. Esse elipsóide é complementado por um geóide de referência, que considera as variações locais na distribuição do campo gravitacional da terra (causada pela assimetria das massas dos continentes) e serve para determinar a altitude com relação ao nível do mar. Mais informações e ilustrações podem ser encontradas nesta página, de onde veio esta figura:

À esquerda, cálculo da elevação de uma montanha com relação ao elipsóide de referência, definido matematicamente. À direita, cálculo da elevação da mesma montanha com relação ao geóide, que acompanha variações locais do campo gravitacional terrestre.
A figura ajuda a explicar a razão de existirem tantos datums locais para diferentes partes do mundo: embora exista um elipsóide que se adequa melhor ao geóide global, quando a intenção é mapear uma região restrita é mais conveniente usar um elipsóide que se adeque bem à região do geóide restrita à vizinhança local (em geral, o território de algum país). Nas cartas topográficas do Exército Brasileiro, por exemplo, o datum utilizado é o de Córrego Alegre.

No caso do usuário doméstico, todos os dados oriundos de GPS utilizam como datum o datum WGS84 (World Geodetic System).

Projeções

Até agora, a discussão se manteve ao espaço tridimensional, e às definições de sistemas de coordenadas tridimensionais, sua origem e seus eixos. Mesmo que consideremos apenas a superfície terrestre, essa superfície ocupa um espaço tridimensional.

A representação visual das entidades geográficas, entretanto, é feita de forma geralmente bidimensional. Quando calculamos a distância entre pontos, ou ao longo de um caminho, ou a forma e o tamanho relativos de superfícies em um mapa, não fazemos isso em um modelo sólido, em 3D de verdade, mas sim em um mapa plano, de papel ou "de pixels", referenciado por uma grade em geral retangular.

É muito bem conhecido o problema de representar a superfície terrestre em um meio plano, visto que não podemos simplesmente "achatar" uma superfície esférica de forma que ela fique plana sem provocar algum tipo de distorção. Por isso, inúmeras projeções foram inventadas ao longo do tempo para que a distorção de cada mapa fosse minimizada para a aplicação pretendida.

Papel versus Pixels

Até há pouco tempo atrás, os mapas de papel eram usados como o próprio meio onde se realizavam medições e cálculos, pois não havia ferramentas computacionais como as de hoje. Algumas projeções distorcem menos as distâncias, outras distorcem menos os ângulos, outras preservam as áreas relativas, etc., mas todas envolvem algum tipo de distorção que precisa ser considerada caso se deseje realizar medições diretamente no mapa.

Com a migração massiva da cartografia em papel para a cartografia digital, duas características passam a ser relevantes para a definição de uma projeção preferível ao usuário não-profissional:
  • A praticidade computacional em converter coordenadas tridimensionais em coordenadas de mapa;
  • A facilidade de calcular automaticamente superfícies, distâncias, ângulos, sem necessidade de fazer cálculos e medições visuais no mapa.
Assim, por exemplo, quando o usuário clica dois pontos no Google Maps para saber a distância entre eles ao longo de uma estrada, a distorção cartográfica é irrelevante, pois o cálculo da distância é feito usando o sistema de coordenadas tridimensional (que considera a distância geodésica entre os pontos).

Ou seja, o software "sabe" que as distâncias e os tamanhos aparentes das coisas, na projeção do mapa, não são proporcionais.

Mais que isso: o mapa que o usuário vê na tela é apenas uma representação, uma visualização bidimensional de um modelo que é intrinsecamente tridimensional, e é sobre esse modelo que os cálculos são de fato realizados. Isso faz com que a projeção possa ser escolhida com muito mais flexibilidade, e de acordo com a conveniência computacional.

Qual a projeção usada nos mapas da internet?

A principal entidade a organizar e sistematizar as diferentes projeções foi o Grupo Europeu de Prospecção de Petróleo (EPSG - European Petrol Survey Group).

Uma forma computacionalmente natural de planificar o globo é simplesmente atribuir longitude e latitude aos eixos X e Y do mapa. Essa é a projeção chamada equi-retangular, e no caso em que a latitude principal é o equador, é chamada de plate carré (EPSG:32663):

Projeção equi-retangular com gradícula de 10° (fonte: NASA)
Essa projeção preserva a uniformidade de distâncias ao longo dos meridianos, mas distorce os paralelos, fazendo com que estes fiquem muito mais longos do que quanto maior a latitude. Regiões como Antártida, Groenlândia, Rússia e Canadá apresentam grande deformação de formas e ângulos.

Uma outra projeção bastante comum que busca minimizar esse problema é a Projeção de Mercator. Nessa projeção, a "dilatação" dos paralelos é compensada por uma "dilatação" correspondente dos meridianos, fazendo com que as regiões perto dos pólos tenham uma área aparentemente maior, porém preservando as proporções geométricas nas vizinhanças de qualquer ponto do mapa. Além disso, a linha mais curta entre dois pontos quaisquer aparece no mapa como uma linha reta, o que é importante para a navegação. Abaixo, a projeção de Mercator (EPSG:3857):

Essa projeção foi escolhida pelo Google para o Google Maps, e foi na época denominada "EPSG:900913" (a palavra "google" escrita em números...), mas foi depois renumerada pelo EPSG para 3857. Outros nomes são "Spherical Mercator" ou "Web Mercator", mas todas são a mesma projeção.

Praticamente todos os serviços de mapas na internet (Google, Bing, Yahoo, Nokia, OpenStreetMap, ArcGIS) utilizam essa projeção para exibir mapas e também para calcular distâncias, por isso é importante salientar que existe um pequeno erro conhecido no cálculo das distâncias, porque nesse sistema os pontos são projetados sobre um esferóide de referência, e não sobre um elipsóide, que daria resultados mais precisos (porém levemente mais complexos de serem calculados). O esferóide considera o raio terrestre no Equador como se fosse o raio da Terra inteira. Isso causa um aumento das distâncias calculadas em latitudes maiores, mas com erros tipicamente menores que 0,3%.

Conclusão

Para representar e comunicar corretamente a posição geográfica, é necessária a definição de um sistema de referência por coordenadas. Como o espaço é tridimensional, o sistema contém três coordenadas.

Devido à forma esférica da Terra, cujo campo gravitacional é orientado radialmente, torna-se prático o uso de um sistema de coordenadas esférico, composto por latitude, longitude e elevação.

A elevação necessita de uma referência de "zero", e a referência natural é o nível do mar. Devido a variações na gravidade terrestre devido à distribuição assimétrica dos continentes, esse nível do mar descreve um geóide que é irregular. Esse geóide é aproximado por um elipsóide, que considera dois raios e uma excentricidade, devido ao achatamento dos pólos. Por fim, em aplicações cartográficas onde a precisão absoluta não é necessária, é possível optar por um esferóide, que representa a Terra como sendo esférica.

Além da representação tridimensional, que pode e deve ser usada para cálculos, a visualização em geral é feita em 2D, e para isso deve ser escolhida uma projeção, que consiste em uma fórmula que mapeia (projeta) determinado ponto terrestre em um determinado ponto no mapa.

Devido à proliferação dos mapas na internet, que tornou acessível a informação geográfica para o usuário entusiasta, qualquer aplicação de mapas se beneficia de usar o mesmo sistema de coordenadas e a mesma projeção dos serviços de mapa online.

Esse sistema de coordenadas é o WGS84, e essa projeção é a Esférica de Mercator.

Daqui por diante, todas as discussões a respeito de posição geográfica e sua projeção feitas neste blog considerarão por padrão esses sistemas, exceto quando explicitamente afirmado o contrário.

A partir desta discussão, podemos também começar a tratar da modelagem e da representação computacional da posição geográfica, coisa que pretendo fazer na próxima postagem.

sábado, 31 de maio de 2014

Esquema de Aplicação, Modelo de Domínio e Universo de Discurso em aplicações GPS para uso pessoal

Em postegens anteriores, foram feitas considerações sobre usos de GPS, e especificamente de arquivos de GPS, para aplicações pessoais de planejamento e registro de viagens ou atividades esportivas envolvendo deslocamentos (ciclismo, por exemplo).

Atualmente, temos uma situação em que duas plataformas (GPS + Internet) tornou possível a muitas pessoas, mesmo sem grandes conhecimentos técnicos,  pesquisar, planejar, executar e analisar essas viagens e atividades. E isso ocorre graças à existência de aplicações que fazem justamente a mediação entre os usos pretendidos pelo usuário, e a estrutura computacional dos dados propriamente ditos.


Como já foi comentado previamente, o grupo que realizou a mais completa análise dssa problemática foi o ISO/TC 211, e o resultado desse trabalho foi o conjunto de normas ISO 191xx, que será usado também como referência para esta discussão.

Uma coisa que precisa ficar clara, entretanto, é que esse trabalho da ISO tem um escoplo exaustivamente amplo e formal, focado principalmente em Sistemas de Informação Geográfica para uso profissional.

O que se busca aqui, como um resultado final da discussão, são orientações que permitam definir os requisitos de uma aplicação em nível de usuário final, sem treinamento especializado, para que este possa criar e extrair informação relevante, usando seus próprios dados pessoais em conjunto com dados de outras pessoas e também dados geográficos publicamente disponíveis.

A melhor fonte de referência para modelagem de dados voltada a aplicações (principalmente, mas não necessariamente geográficas) é a ISO - 103 - Conceptual Schema Language (Linguagem de Esquema Conceitual).

Alguns conceitos relevantes são os seguintes (comentários meus):
  • Aplicação: "Manipulação e processamento de dados em suporte aos requisitos do usuário". Neste contexto, fica entendido que o usuário necessita atender uma necessidade pessoal sua, sendo que essa necessidade envolve manipulação da informação. Em geral, entendemos aplicação como um software a ser executado em um computador, manipulando informação de natureza digital / computacional, mediante comandos interativos do usuário.
  • Esquema de Aplicação: "Esquema conceitual para os dados requeridos por uma ou mais aplicações". Isso significa que, para que a aplicação tenha condições de gerar informação relevante ao usuário, é necessária a existência de um modelo que contenha os conceitos necessários à manipulação e ao processamento.
  • Modelo Conceitual: "Modelo que define os conceitos de um universo de discurso". Uma aplicação depende de um modelo de aplicação, que por sua vez depende de um modelo conceitual. A diferença entre ambos é que o modelo conceitual é autônomo, dependendo apenas da natureza da realidade representada, e da seleção de um domínio ou universo de discurso, que consiste num subconjunto das propriedades dessa realidade.
  • Universo de Discurso: "Parte do mundo real (ou de um mundo hipotético) que um ser humano deseja descrever em um modelo". Todo modelo envolve uma simplificação, constituindo uma representação limitada do fenômeno real que ele descreve. Portanto, em geral apenas os aspectos da realidade que são relevantes a um determinado domínio de conhecimento são selecionados para participar do universo de discurso.
  • Entidades ("features"): "Abstração de uma entidade ou fenômeno do mundo real". Uma entidade geográfica seria a representação de uma entidade ou fenômeno natural associado a uma determinada localização no Planeta Terra.

No caso da informação geográfica, a realidade seria o Planeta Terra, sua atmosfera, seus rios, florestas, desertos, montanhas, vegetação, população, cidades, e todos os outros ambientes naturais ou artificiais.

Um universo de discurso para a informação geográfica, e em especial no contexto de informações pessoais do usuário, seriam estradas, terrenos, relevos, cidades, ruas, pontes, túneis, postos de gasolina e locais de comércio, pontos turísticos, pontos de interesse em geral, bem como trajetos previamente desenhados ou percorridos por outras pessoas.

Um modelo conceitual consistiria na representação abstrata dessas entidades selecionadas pelo universo de discurso. Um modelo de terreno seria representado por uma superfície geométrica, uma estrada seria representada por uma linha, um local turístico seria representado por um ponto, e de um modo geral, a forma e a posição dessas entidades poderiam ser descritas através de coordenadas numéricas dentro de um determinado sistema de referência. Além da estrutura, também os comportamentos de cada tipo de entidade poderiam ser descritos, a fim de possibilitar que as aplicações realizem operações sobre esses objetos, com a intenção de extrair ou criar informação relavante sobre as entidades.

Por fim, a aplicação propriamente dita - e concretamente implementada em uma plataforma computacional - converte o modelo conceitual em um esquema de aplicação, com classes, métodos, estruturas de dados, formatos de arquivo e tipos numéricos apropriados à plataforma escolhida, afim de satisfazer os casos de uso adequados às necessidades do usuário.

As normas ISO determinam (ISO 19109 - Rules for Application Schema) que a informação geográfica seja representada usando o UML (Unified Modeling Language), o que é conveniente quando se pensa em implementar uma aplicação orientada a objeto.

Em postagens futuras, veremos que o esquema KML corresponde bastante ao modelo UML proposto pela ISO para as entidades geográficas (ponto, linha, região, superfície, embora não necessariamente com o mesmo nome), mas algumas operações (na forma de métodos) não têm como ser descritas em KML, necessitando de classes instanciadas dentro de uma aplicação sendo executada.

sábado, 24 de maio de 2014

Formatos KML e GPX: reflexões sobre a natureza da informação geográfica - Modelo de dados de GPS, parte 2

Conforme comentado na postagem anterior, um componente importante da atividade "bicicleta + mapa + GPS" consiste na criação, edição e gerenciamento de arquivos contendo os dados que se pretende visualizar, armazenar e transferir entre o computador e o dispositivo GPS. Portanto, se considerarmos que os arquivos, enquanto forma de persistência, são um modelo de representação dos dados geográficos, que teoricamente permite reconstituir as características desses dados, uma forma possível de análise é, a partir da estrutura dos arquivos, inferir a natureza abstrata dessa informação. É o que me proponho a fazer nesta postagem.

"Faz sentido a conversão entre GPX e KML, ou vice-versa?"

 

Em se tratando de arquivos, de longe os mais difundidos são os formatos KML (Keyhole Markup Language) e GPX (GPs eXchange format). Mas algumas perguntas se fazem necessárias, especialmente considerando uma operação comumente oferecida por vários, senão todos, os softwares e sites de GPS: a conversão entre ambos os formatos. Essas perguntas são:
  • Qual a natureza da informação armazenada no formato GPX?
  • Qual a natureza da informação armazenada no formato KML?
  • Será que ambos os formatos armazenam o mesmo tipo de informação? 
  • Será que faz sentido falar em "converter" um formato em outro, ou então salvar um conjunto de dados geográficos em um ou outro formato?
  • (e a pergunta que realmente importa) Considerando que ambos os formatos apenas representam informação, com finalidade de persistência e transferência, qual é a natureza primitiva dessa informação?

O formato GPX

Não consegui encontrar informações sobre a história do formato, mas de acordo com a home page, ele se propõe a ser um container justamente para o tipo de informação utilizada pelos aparelhos de GPS, seja a informação que se cria ou envia para o aparelho ao planejar a atividade, ou aquela que o aparelho cria durante a execução da atividade.

Ainda segundo a home-page:

"O formato GPX é um formato XML leve para transferência de dados de GPS (waypoints, rotas e tracks) entre aplicativos e serviços na internet."

O formato GPX tem três tipos fundamentais de informação: Waypoint, Track e Rota. Esses têm equivalência direta com as entidades de trabalho dos aparelhos de GPS da Garmin, e com a maioria dos outros aparelhos:
  • Um Waypoint representa um ponto de interesse, contendo ao mínimo as propriedades de Latitude e Longitude, podendo conter Elevação (Altitude) e Estampa de Tempo (timestamp, datetime), e podendo ainda, dependendo do contexto, conter Nome, Descrição, Símbolo, e outras propriedades. Tipicamente, um Waypoint pode ser criado pelo usuário a partir de sua localização atual, ou então criado no próprio aparelho ou em um computador a partir de uma outra localização desejada em um mapa de referência;
  • Um Track representa uma sequência de Trackpoints. Cada trackpoint é um sub-tipo de Waypoint (ou vice-versa), de forma que o Track representa a trajetória percorrida, ou seja, é um registro de eventos que foram ocorrendo ao longo do tempo;
  • Uma Rota (Route) é uma sequência de Waypoints, servindo conceitualmente como uma sugestão de trajetória que o usuário poderá percorrer, utilizando a rota como um auxílio para a navegação sequencial entre seus waypoints. Ao contrário de um Track, que representa o fato passado, a Rota representa a possibilidade futura.
Quando eu disse acima que Trackpoints são subtipos de Waypoints, "ou vice-versa", isso traz logo uma primeira "dúvida": devem os tipos-base servir como definições de propriedades básicas, que deverão ser extendidas por sub-tipos - e nesse caso os subtipos fariam MAIS do que os tipos-base - ou pelo contrário, devem os tipos-base descrever completamente todas as propriedades possíveis, e os sub-tipos então restringiriam o comportamento?

Tomando o exemplo entre as duas entidades, a impressão que tenho é a seguinte: tipicamente, o Waypoint é um ponto avulso, ou seja, um elemento em si mesmo, com Nome, Descrição, Estilo, e outros metadados. Ele foi criado como ponto, e é visto como ponto e manipulado como ponto, dimensionalmente falando. Um conjunto de Waypoints não é necessariamente ordenado, mas se for, constitui uma Rota.

Por outro lado, um Trackpoint raramente será identificado de forma isolada, pois ele só "conta" como Trackpoint se for pertencente a uma Track. A Track, nesse caso, é que possuiria as propriedades Nome, Descrição, Estilo, e uma determinada propriedade seria a sequência de Trackpoints (o que, em XML, pode ser representado por um elemento do tipo "trackpoint collection", ou diretamente colocando os trackpoints em ordem sequencial entre as tags do elemento Track).

Seja como for, a vocação - e a própria capacidade-limite - do formato GPX são entidades relacionadas aos aparelhos de GPS: pontos, linhas, sequências de pontos.

O formato KML

Enquanto o GPX aparentemente é um formato que surgiu de forma despretensiosa e "foi ficando" até se tornar o padrão de-facto para dados GPS, praticamente sem modificações, o KML é um formato que evoluiu de forma bem mais consistente e "encorpada".

O Keyhole Markup Language foi criado pela empresa Keyhole, Inc. para uso em seu produto Keyhole Earth Viewer. A empresa foi fundada em 2001 e comprada pelo Google em 2004, e o Keyhole Earth Viewer acabou rapidamente virando o Google Earth, lançado em 2005. O formato foi tão bem sucedido que acabou sendo enviado ao ISO/OGC (Open Geospatial Consortium) em 2008, e atualmente a especificação oficial é dada pelo ISO/OGC, que adotou o formato como um de seus padrões, mantendo o compromisso de trabalhar cooperativamente com o Google e com a comunidade de usuários para que o formato evolua de forma consistente, padronizada e pública.

Abaixo, podemos ver o diagrama do formato KML. Em preto estão os elementos padrão definidos pelo ISO/OGC, e em vermelho estão as extensões utilizadas pelo Google Earth.

Esquema do formato KML (fonte: Google KML Reference)

Uma análise dessa estrutura permite intuir a natureza da informação que o KML se propõe a representar, que corresponde à descrição encontrada na página do ISO/OGC:

"KML é uma linguagem XML focada em visualização geográfica, incluindo anotações de mapas e imagens. Visualização geográfica inclui não apenas a apresentação de dados geográficos no globo, mas também o controle da navegação do usuário no sentido de para onde ele deve ir e para onde ele deve olhar."

Assim sendo, a própria definição acima já cria um marcante contraste entre os superficialmente similares formatos GPX e KML. Enquanto o primeiro é somente um container para uns poucos tipos de elementos geográficos associados à navegação por GPS, esse último é, além de um container, uma definição sobre a forma como esses dados devem ser visualizados, e potencialmente até um registro sobre a intenção de uso e visualização dos dados contidos no arquivo.

Voltando à análise da estrutura do KML, vejamos de que forma podemos particionar alguns dos elementos por categoria:
  • Estruturação do arquivo: Document, Folder;
  • Definições de aparência dos elementos: Style, SubStyle, StyleSelector, etc.;
  • Definições de elementos vetoriais: Geometry (e todos os subtipos);
  • Definições de intenção de visualização: Tour, TourPrimitive e subtipos, PlayList;
  • Definições de elementos raster: Overlay e subtipos;
  • Referências externas: NetworkLink
Dos elementos citados acima, os únicos que contêm uma correspondência direta com o GPS são os tipos Geometry:
  • gpx:Track -> gx:Track (e em menor grau kml:LineString, que não possui timestamp);
  • gpx:Waypoint ->  kml:Point;
  • gpx:Route -> não tem correspondência em KML.

Discussão

Algumas das perguntas feitas no início já parecem ter uma resposta após essa análise dos tipos de informação que cada arquivo se propõe a representar.

Diria que, de modo geral (ou seja, para as aplicações mais comuns dos arquivos pela comunidade de ciclistas e aventureiros em geral), NÃO faz sentido falar na conversão de um formato em outro, ao menos não se quisermos uma conversão sem perdas. Acho problemático que isso não seja explicado com mais clareza nas inúmeras documentações de software, postagens e conversas a respeito que já encontrei pela internet afora, e também usando os aplicativos e plataformas.

De modo geral, também, me parece que ambos os formatos têm tido um uso qualitativamente distindo, embora essa distinção não seja explícita.
  • O KML tem mais vocação para representar informação estática, e de modo geral uma informação sobre o terreno, na forma de estradas, pontos de referência, opcionalmente imagens (overlays), e representação / visualização de elementos que fazem parte do globo terrestre, de sua estrutura e de sua paisagem. Seriam os elementos típicos estudados pela cartografia.
  • Já o GPX é orientado a objetos móveis, sejam veículos, pessoas, animais, ou qualquer outra coisa que se movimente enquanto tem sua trajetória registrada por um GPS. Mesmo os elementos prescritivos, como Rotas ou Waypoints, são orientados à navegação, e não à cartografia.
O Google, através de suas extensões ao KML, define o tipo gx:Track, que é um precedente no sentido de representação de trajetória, mas com uma estrutura bem diferente do elemento gpx:Track. Embora isso aumente a compatibilidade entre ambos os formatos, no sentido de permitir ao KML armazenar informações de trajetória, essa funcionalidade ainda não é oficial (não foi incorporada pelo ISO/OGC, existindo somente como extensão do Google), e não há sinais de que venha a substituir o GPX em vocação ou em popularidade de uso, pelo menos não nos próximos anos.

Por outro lado, o elemento <time> nos Trackpoints do GPX é opcional, o que permite armazenar apenas a geometria de um trajeto (que dessa forma se torna uma descrição geométrica estática), mas o KML parece ser uma melhor opção para isso, e provavelmente vai continuar sendo.

Implicações para usuários de GPS (em ciclismo ou outras atividades)

Para quem utiliza a combinação GPS + Mapas Online = Passeios Divertidos, seja em nível básico ou avançado (incluindo programação), as recomendações seriam as seguinte:
  • Use o KML para criar dados geográficos sobre o terreno, suas estradas, pontos de referência, dados esses que servirão para o planejamento de um passeio, ou para servir como referência dos aspectos geográficos de uma região. Em geral, a forma de criação dessas informações é a edição direta com o mouse sobre um mapa de referência, ou a seleção de entidades já prontas em um mapa geral contendo entidades geográficas, para que sejam armazenadas no arquivo KML;
  • Use o GPX para salvar e transferir o registro de uma atividade, ou seja, para descrever a trajetória do deslocamento do veículo ou da pessoa, da forma como ela ocorreu no espaço e no tempo, tendo sido registrada pelo aparelho GPS. Opcionalmente, o GPX pode ser usado para transferir dados no PC de volta para o GPS, embora haja um número crescente de dispositivos (especialmente smartphones) que reconhecem e carregam informações de arquivos KML para exibição como mapas de fundo durante a navegação. Também é relevante lembrar que, no caso de atividades esportivas em que a intenção é reproduzir um desempenho anterior na forma de um "virtual partner", o GPX é um formato conveniente para enviar uma atividade previamente realizada de volta ao aparelho (embora em geral isso seja feito em algum outro formato de arquivo proprietário).

Extrapolação do modelo de dados: além dos formatos de arquivo

 O raciocínio seguido nesta postagem foi semelhante a uma "engenharia reversa", tentando intuir a estrutura da informação geográfica a partir da análise da estrutura de dois formatos de arquivo populares.

Seria ingênuo pensar que essas duas estruturas de dados descrevem completamente tudo que há para descrever. Seria ingênuo pensar que a descrição pura e simples de algo seja suficiente para revelar sua natureza característica, e seria ingênuo tentar identificar qual formato de arquivo é "o melhor", ou "o mais completo".

O trabalho com arquivos, e mesmo o trabalho em aplicativos ou sites ou bancos de dados, pressupõe um modelo de dados, e por definição "o modelo" é apenas uma simplificação do "objeto" propriamente dito. Formatos de arquivo são formas de representar entidades geográficas para persistência e transferência da informação. Modelos de objeto (não tratados nesta postagem) são formas de representar a estrutura e o comportamento dessas entidades em software, de forma que sobre elas se possam realizar operações e produzir novas informações, que sejam interessantes e úteis a um determinado contexto de aplicação.

Na próxima postagem, a ideia é debater sobre a natureza da informação geográfica, principalmente no contexto da atividade ciclística, mas com ideias e considerações aplicáveis a qualquer tipo de deslocamento geográfico. Me parece que apenas com um modelo conceitual que seja consistente, relevante, robusto e intencional, pode-se garantir um modelo de dados - necessariamente uma representação simplificada - que permita a qualquer aplicação um tratamento adequado dessa informação, através de operações e visualizações que capturem a essência não só dos elementos geográficos, mas também de seu significado para as pessoas que por eles se referenciam.

sexta-feira, 23 de maio de 2014

Por um modelo de dados para GPS aplicado ao ciclismo - Parte 1: Uma breve história baseada em experiência pessoal

Eu, como ciclista de longa data, peguei toda a evolução do uso de GPS em bicicleta, assim como a evolução dos serviços de mapa online (Google Earth e depois Google Maps), e mais recentemente dos sites de aglomeração exponencial de dados de GPS, cujo exemplo de maior destaque é o Strava.

No início, aventuras pelo mundo afora eram acompanhadas de mapas rodoviários de papel, ou planilhas de quilometragem baseadas nesses, provavelmente feitas à mão. Em regiões mais remotas, muita utilidade tiveram, para mim e para outros, os mapas topográficos do exército. Tanto por um meio quanto por outro, a incerteza era um fator sempre presente, incluindo estradas que haviam sido modificadas, placas na estrada que indicavam distâncias erradas, etc.

Mapa topográfico da região de Riozinho - RS, escala 1:50.000


O primeiro passo no sentido de eliminação de incerteza foi começar a usar GPS para pedalar. A simples ideia de ter um aparelho que soubesse SEMPRE onde eu estava, e principalmente se eu havia voltado pelo mesmo caminho, era revolucionária. Mas não tão revolucionária, pois nos modelos mais primitivos não havia mapa de fundo, e portanto era necessário enviar uma rota ao aparelho antes, e nem sempre foi fácil criar essa rota antecipadamente.

Meu primeiro GPS, que me acompanhou em dezenas de pedaladas,
era um Garmin eTrex Venture como o da foto acima


O passo seguinte ocorreu logo em seguida, quando fui apresentado mais diretamente ao software GPS Trackmaker, que é simplesmente GENIAL, e curiosamente é produzido até hoje pela empresa de mesmo nome, situada em Minas Gerais. Esse software faz algo que considero crucial para a atividade de "navegar no mundo de bicicleta usando um GPS": ele vincula o MAPA, e no caso do mapa embutido que já vem com o software, um mapa feito para uso eletrônico, com o APARELHO de GPS. Explico melhor por que isso é importante:
  • O mapa eletrônico, em uso no PC, é o momento em que o usuário está em contato com uma fonte difusa de dados geográficos, na confortável situação em que pesquisas, reflexões, comparações, síntese de dados a partir de outras fontes, tudo isso pode ser feito sem um limite crítico de tempo ou "cansaço". É nesse contexto que a ação intelectual do usuário, subsidiada por fontes potencialmente ilimitadas de informação geográfica (mapas, sites, trajetos percorridos por outros usuários de GPS que tenham compartilhado online, etc.) podem ser processados e sintetizados na forma de uma rota que é salva em arquivo e enviada ao aparelho.
  • O aparelho de GPS, por sua vez, é como se fosse uma extensão móvel de todo esse planejamento. A diferença é que ele é levado a campo, e em campo é que o plano é executado. Para muitos planejadores de viagens, a transformação gradual da rota planejada (desconhecida, imaginada) em rota percorrida (confirmada, ratificada, assimilada) é um componente importante do próprio prazer de fazer a viagem por locais até então desconhecidos. O próprio fato de o GPS tornar, em tempo real, um lugar desconhecido em um lugar, de certa forma, "conhecido", é a razão de ser possível fazer viagens por lugares desconhecidos, coisa que não seria possível de outra forma - ao menos não com a mesma eficácia.
Software GPS Trackmaker com mapa de fundo
Durante muito tempo, essa era uma atividade que atraiu um nicho de participantes, em geral adeptos de atividades de aventura, que percorriam diversos lugares com seus GPS e iam compartilhando essas rotas, que num segundo momento começaram a alimentar mapas feitos a mão, já em formato eletrônico que era então compilado e transformado em mapa base para uso tanto nos softwares de edição, quanto nos mapas de fundo dos aparelhos mais sofisticados que tivessem essa funcionalidade. Um exemplo clássico de uma comunidade brasileira dedicada é o Projeto Tracksource. Iniciativa similar, mas com um foco diferente, é o Open Street Maps (OSM). Ambos envolvem a produção de conteúdo geográfico público por uma comunidade de voluntários que criam, editam e aperfeiçoam um grande mapa digital.

Um fator que foi definitivo - na minha opinião - e que teve tão grande sucesso que hoje chega a ser banalizado, é o "boom" de serviços de mapa online, do qual o maior exemplo é o Google Maps. Embora no início as fotos de satélite não fossem em alta resolução em todos os lugares, nem houvese estradas roteáveis no mapa inteiro, a cada ano pôde-se perceber uma absurda evolução, ao ponto de hoje até mesmo as estradas de terra estarem TODAS identificadas e roteáveis (ao menos no Rio Grande do Sul, que é onde moro e costumo pedalar), de forma que não seria exagero dizer que a edição manual de mapas para envio ao GPS se tornou obsoleta: vale mais a pena clicar no ponto A, depois no ponto B, e mover os pontos intermediários para onde se queira passar, exportando a rota gerada para um arquivo .KML que pode facilmente ser enviado aos aparelhos GPS por meio de algum software.

Dando sequência à série de revoluções (lembrando que essa é minha perspectiva pessoal, outras pessoas provavelmente terão impressões bem diferentes), uma "dupla revolução" marca a mudança de um aspecto conceitual importante na relação Bicicleta + GPS: a popularização dos smartphones com GPS, e a popularização de um determinado site de armazenamento de atividades esportivas gravadas com GPS: o Strava.

Essa mudança conceitual de que falo consiste na mudança do foco de atenção com relação ao dado que o GPS produz: ao invés de extrair do trajeto a informação geográfica (distância, altimetria, lugares percorridos, mapa, ou seja, informação sobre o terreno), agora a informação mais importante, é a informação sobre o ciclista! Em um contexto em que as trilhas já foram trilhadas, os terrenos já foram explorados, e principalmente e todo o planeta já foi fotografado e mapeado, podendo ser visto na internet a qualquer momento, o que passou a contar mais é algo que secretamente era o sonho de todos aqueles que já tiveram um velocímetro na bicicleta: gravar TODO o trajeto. Saber em que velocidade eu estava, e qual foi a hora em que eu passei, em TODOS OS PONTOS do trajeto. Agora já não é mais necessário dar start, stop ou mesmo zerar o velocímetro. Grava-se tudo, e depois se analisa, seleciona, segmenta, compara, se realizam todas as operações de análise em casa, com ferramentas computacionais avançadas diponíveis publicamente no browser, e em constante evolução.

Painel de análise de atividade do Strava, que permite navegar nos gráficos, selecionar determinados segmentos, e saber médias e máximas parciais, diretamente no browser


Assim sendo, passamos de uma situação de escassez e hipervalorização da informação geográfica (a glória era achar um arquivo, compartilhado por alguém, que contivesse a trajetória para o acesso a determinado ponto turístico - pois do contrário seria impossível ir até ele com certeza), para uma situação de abundância que beira a banalização, onde a gente chega ao lugar já sabendo até quais as placas vai encontrar na estrada (pois já analisou o caminho no Google Street View, por exemplo).

No campo da análise de movimento, também passamos de uma situação de escassez (onde as únicas informações que restavam eram o tempo, a distância, a velocidade média e a velocidade máxima, marcadas no velocímetro), para uma situação de abundância, onde dia a dia se acumulam trajetos e mais trajetos, nossos, dos nossos amigos e de desconhecidos, que contêm informação suficiente para reconstituir as atividades ciclísticas praticamente como elas aconteceram de fato, como é o caso do Strava Activity Playback.

As questões que surgem com tudo isso são:
  • Estamos de fato aproveitando todos os benefícios que essa avalanche de informação, que é tão recente e tão inédita, é capaz de nos proporcionar?
  • O que é possível fazer com conjuntos de dados de GPS ciclísticos tão vastos como o do Strava? Ou, num nível pessoal, com sua própria coleção de trajetos?
  • Que tipo de software essa "nova geração" de dados requer para que possa mostrar todo seu valor? Que algoritmos, operações, entidades, objetos, devem ser criados para corretamente representar e manipular essa informação?
  • Os formatos de arquivo disponíveis atualmente (.GPX, .KML, etc.) são adequados para representar essas entidades? Especificamente esses dois formatos representam o mesmo TIPO de elementos?
Enfim, essas e outras perguntas eu pretendo aprofundar com novas reflexões em postagens que seguirão.