terça-feira, 31 de janeiro de 2012

Informações sobre rotas com a versão 3 da API do Google Maps

No ano passado eu publiquei dois posts mostrando como traçar rotas usando a versão 3 da API do Google Maps. O primeiro tratava de um cenário básico onde consideramos um endereço de origem e outro de destino; o segundo avançava um passo ao permitir a inclusão de um ou mais pontos intermediários ao trajeto. Por razões distintas, várias pessoas me perguntaram como é possível obter a distância total entre a origem e o destino em uma rota, independendo se vamos efetivamente exibir a rota traçada.

Pra refrescar a memória, o quadro a seguir ilustra os comandos e estruturas básicos necessários pra trabalharmos com uma rota.
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

A linha directionsRenderer.setDirections (response) usa o objeto response para traçar automaticamente a rota no mapa. Esse objeto é do tipo DirectionsResult e é montado pela função route antes de ser repassado como response à nossa função de callback. No entanto, você pode usar manualmente as informações contidas em response sem que a rota precise ser exibida.

De acordo com a documentação, o DirectionsResult possui como única propriedade um Array chamado routes contendo a lista de rotas encontradas pelo serviço para atender a requisição feita. Cada elemento dessa lista traz detalhes de uma rota específica, descrita na forma de um objeto DirectionsRoute, o que inclui detalhes de cada trecho do trajeto, como a distância de cada um (em metros) e o tempo estimado usando o tipo de locomoção escolhido.

Com isso, podemos facilmente obter a distância a ser percorrida para chegar ao destino usando a rota em questão:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia;
var rota = response.routes[0]; /* Primeira rota */
var etapa = rota.legs[0]; /* única etapa dessa rota */

distancia = etapa.distance.value;

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

A propriedade legs destrincha cada etapa do trajeto usando objetos do tipo DirectionsLeg para descrevê-las. Por etapa entende-se o deslocamento necessário entre dois endereços. Assim, se você estipulou apenas a origem e o destino, haverá uma única etapa e, por conseguinte, uma única posição em legs. Por outro lado, se você adicionou pontos de referência usando o waypoints na requisição (veja esse post para mais detalhes), haverá uma nova etapa descrevendo como chegar a cada ponto. Nesse caso, será preciso percorrer todas as posições, somando a distância de cada etapa para se obter a distância total da rota:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia = 0;
var rota = response.routes[0]; /* Primeira rota */
var etapa, i;

for (i = 0; i < rota.legs.length; i++)
{
/* Obtem cada etapa */
etapa = rota.legs[i];
distancia += etapa.distance.value;
}

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

O detalhamento do caminho a ser seguido dentro de cada etapa do trajeto pode ser acessado através da propriedade steps. Cada passo também possui sua distância mas a soma delas já está consolidada na própria etapa (leg). Tenha em mente que, para certos trajetos, pode acontecer de as informações de distância e duração não estarem disponíveis. Por questão de simplicidade, os scripts acima não levam isso em conta. Um exemplo completo dos recursos mostrados até agora pode ser visto na página abaixo, cujo código fonte está neste link:

Um outro ponto importante a ser lembrado é que a função route é assíncrona. Isto significa que a função de callback que passamos a ela poderá ser executada depois da linha de código imediatamente seguinte ao route. Assim, se tiver que comparar as distâncias de várias rotas, terá que montar uma forma de sincronizar os cálculos para que a comparação só ocorra depois da execução de todos os callbacks envolvidos. Esse efeito normalmente é conseguido através da função javascript setinterval, verificando a cada segundo, por exemplo, se as rotas já estão prontas.

Mais Informações
Traçando rotas com a versão 3 da API do Google Maps - Parte I e Parte II, Google Maps JavaScript API V3

segunda-feira, 23 de janeiro de 2012

Trabalhando com Ações em Delphi e C++ Builder

As operações que um usuário pode executar em programas com interface visual podem estar disponíveis de diversas maneiras. As formas mais óbvias e mais utilizadas são os menus, barras de ferramentas, menus de contexto, botões espalhados pela interface e, mais recentemente, os ribbons introduzidos pela interface do Microsoft Office. Com essa variedade de formas de se acessar uma operação, é bastante comum que esse acesso esteja disponível em mais de um lugar.

Do ponto de vista do programador surge então uma questão: qual a melhor maneira de se gerenciar as operações em uma aplicação, notadamente aquelas que estarão acessíveis por mais de uma via ? A solução para quem desenvolve com Delphi ou C++ Builder é um componente que já existe há um bom tempo nesses ambientes, chamado TActionManager.

Como diz o nome, o objetivo do TActionManager é gerenciar as operações que estarão disponíveis para o usuário em uma aplicação, centralizando a configuração básica de cada operação. O componente permite organizar as operações por afinidade (relativas a arquivo, edição, visualizar ou ajuda, por exemplo); informar textos e imagens descritivos da operação a fim de manter a uniformidade visual independente da forma de acesso escolhida; sinalizar se a operação está ativa ou mesmo visível.

E como é que isso tudo se conecta ? Alimentar o TActionManager com operações faz com que o IDE crie instâncias de um outro objeto, o TAction. Cada TAction corresponde a uma operação - ou ação - isolada, independente da interação necessária para ativá-la. Essa ação pode então ser associada à propriedade Action existente em diversos componentes - itens de menu, botões, itens em barras de ferramenta, etc.

Entretanto, certos componentes - como o TActionMainMenuBar e o TRibbon - são configurados diretamente com uma instância do ActionManager e não com ações individuais.

Para dar manutenção nas ações gerenciadas pelo ActionManager, dê um duplo clique nele. Será exibida uma caixa de diálogo como a reproduzida abaixo, onde é possível criar novas ações, excluí-las e rearranjá-las. Selecionar uma ação fará com que o Object Inspector apresente as propriedades relativas a ela.
Configuração do Action Manager
Há umas poucas propriedades no ActionManager e elas não são obrigatórias; destaco abaixo aquelas que são mais utilizadas:
Images e DisabledImages são TImageList contendo, respectivamente, a imagem que deve ser vinculada a uma ação quando ela está ativa e quando está desabilitada. Em ambos os casos, as imagens devem ser pequenas - 16 x 16 pixels. Uma das propriedades de uma ação é o índice da imagem a ser usada; como só há um índice, a imagem correspondente à ação deve estar exatamente na mesma posição em todas as listas de imagens configuradas.
LargeImages e LargeDisabledImages são os TImageList que contem imagens grandes - 32 por 32 pixels - para as ações configuradas nesse gerenciador. A primeira lista vincula imagens a ações ativas enquanto a segunda traz as imagens grandes para ações desabilitadas. Lembre-se que as imagens relativas a uma ação devem estar na mesma posição, conforme dito no tópico anterior.
State: Permite desativar todas as ações de um única vez, impedindo-as de serem disparadas.
Use a propriedade ActionBars para criar barras de ferramentas associadas ao gerenciador.
FileName indica o caminho completo para um arquivo que armazernará a configuração dinâmica feita pelo usuário final em tempo de execução.

Como se depreende do exposto acima, o gerenciador de ações pode ser usado em conjunto com o TCustomizeDlg para permitir ao usuário final reorganizar as barras de ferramentas.

Uma vez que o gerenciador está preparado, podemos acrescentar-lhe as ações e configurá-las. A lista abaixo mostra as principais propriedades do TAction:
Use Caption para informar o texto padrão para a ação. É esse texto que será usado num item de menu ou como texto de um botão onde a ação for vinculada.
Use Category para organizar as ações, agrupando-as por afinidade, o que facilita a manuntenção de ações tanto para o programador quanto para o usuário final - caso a funcionalidade de configuração seja disponibilizada a ele.
ImageIndex é a posição das imagens a serem usadas para representar a ação em diversos contextos. Cada ação terá à sua disposição o mesmo índice em cada uma das 4 listas existentes no gerenciador de ações.
Uma combinação de teclas pode ser atribuída em Shortcut. Essa combinação é um método alternativo para executar a ação associada a ela.
O evento OnExecute é usado para centralizar a execução da ação. Assim, não é preciso acrescentar código em nenhum outro ponto do programa nem responder a outros eventos; basta configurar a propriedade Action existente no componente que desejar disparar a ação - menus e botões, por exemplo.
Enabled permite habilitar ou desabilitar uma ação individual de forma centralizada. Isto é, altere essa propriedade para que todas as possibilidades de se executar a ação sejam afetadas de uma só vez.
Modifique a propriedade Visible para esconder ou mostrar uma ação de uma só vez em todos os contextos nos quais ela estiver disponível.


sexta-feira, 30 de dezembro de 2011

Preparando a execução de um programa em desktop remoto

A adoção maciça da internet e os benefícios associados a ela têm levado as empresas a optar cada vez com mais frequência por programas que possam ser executados nessa infraestrutura. O principal benefício aqui está relacionado aos custos já que os desktops não precisam ser tão potentes - basta que tenham acesso à internet para executarem esses programas remotos - e a manutenção dos equipamentos passa ser centralizada.

Por outro lado, nem todos os programas em uso pelas empresas operam nativamente na internet. Seja por que são programas legados ou por que o fabricamente ainda não os preparou para o ambiente web ou simplesmente porque há muitos programas e não houve tempo hábil para uma conversão em massa. Por isso, programas como o RDS (Remote Desktop Services) da Microsoft e as soluções da Citrix para virtualização de desktops têm sido bastante adotados.

Essas soluções permitem que um usuário execute remotamente os programas que necessita, através de um desktop virtual acessível via rede local ou internet. Com isso, o hardware mais robusto fica centralizado num servidor e pode ser compartilhado por diversos usuários simultâneos.

Nesse ambiente, diversos usuários podem estar executando sua aplicação no mesmo computador remoto, compartilhando recursos como a pasta de trabalho, memória mapeada, disco, etc. Desse modo, as diferentes execuções simultâneas do programa podem conflitar entre si. Decisões baseadas no nome do computador, seu endereço IP ou outra informação de hardware também podem causar problemas já que todos os usuários remotos lerão os mesmos valores. A ABC71, por exemplo, usa o nome do computador onde nosso ERP está em execução como parte do controle de licenças.

Em Windows, normalmente as soluções de desktop virtual se baseiam nos serviços de Remote Desktop (antes chamado de Terminal Services), razão pela qual podemos usar a Remote Desktop Services API para preparar nossas aplicações para que rodem nesse ambiente. Essa API permite resgatar e modificar informações sobre um usuário específico, além de executar tarefas relativas à administração do serviço.

Para começar, temos que determinar se o programa está executando num desktop convencional ou num virtual. Isso pode ser conseguido com uma chamada à função GetSystemMetrics. Saber que estou numa sessão remota me permite, por exemplo, ajustar uma pasta de trabalho coerente para cada execução distinta do meu programa.
function TForm1.IsRemoteSession : boolean;
var res : integer;
begin
res := GetSystemMetrics (SM_REMOTESESSION);
Result := (res <> 0);
end;
A função GetSystemMetrics é um curinga da API do Windows; com ela é possível recuperar uma porção de parâmetros do ambiente operacional. Chamada com SM_REMOTESESSION, ela retorna um valor diferente de zero se o programa estiver num desktop virtual.

Muitas informações valiosas sobre a sessão remota em andamento podem ser obtidas através da função WTSQuerySessionInformation. No exemplo abaixo, eu a utilizo para recuperar o nome do computador que está acessando o desktop remoto:
procedure TForm1.GetSessionInfo;
var lSessionId: DWORD;
lBuffer : PChar;
lBytesReturned : DWORD;
lStationName : String;
begin
lSessionId := 0;

{ Descobre a identificação da sessão do usuário com base na identificação do programa no Windows }
if not ProcessIdToSessionId (GetCurrentProcessId (), DWORD(@lSessionId)) then
raise Exception.Create ('Não foi possível obter Remote SessoinId');

lBuffer := Nil;
lBytesReturned := 0;
lStationName := '';

{ Obtem nome da máquina Client }
if (WTSQuerySessionInformation (WTS_CURRENT_SERVER_HANDLE,
lSessionId,
WTSClientName,
lBuffer,
lBytesReturned))
then
lStationName := String(lBuffer)
else
raise Exception.Create ('Não foi possível obter o nome da estação');

{ Libera a memória alocada automaticamente }
WTSFreeMemory (lBuffer);

{ ... }
end;
Vamos por partes. Para recuperar as informações de uma sessão remota, primeiro temos que identificar essa sessão. A função ProcessIdToSessionId da API do Windows nos fornece isso, mapeando o Process ID de nosso programa para a correspondente identificação da sessão remota onde ele está executando.

O passo seguinte é chamar a função WTSQuerySessionInformation para levantar as informações desejadas. Essa função recebe 5 parâmetros. O primeiro é um handle para o servidor da sessão remota. No exemplo, passei a constante WTS_CURRENT_SERVER_HANDLE para indicar que quero informações sobre o servidor atual. Poderia ter usado WTSOpenServer para abrir outro servidor remoto.

O segundo parâmetro é a identificação da sessão que obtivemos no primeiro passo. O parâmetro seguinte é o que determina qual informação será recuperada. Os valores permitidos são os listados no enumerado WTS_INFO_CLASS. No exemplo, usei WTSClientName para obter o nome do computador do usuário que diparou o acesso remoto; a lista de informações recuperáveis inclui o endereço IP desse mesmo computador, a pasta de trabalho remota (no servidor), o nome do usuário e informações sobre o uso da sessão e sobre a Client, dentre outras.

Os dois últimos parâmetros são, respectivamente, um ponteiro para a área que receberá a informação solicitada e a quantidade de bytes que essa informação está ocupando. Como ambos são calculados pela função WTSQuerySessionInformation, não é preciso alocar previamente a memória para eles. No entanto, é nossa responsabilidade liberar a memória alocada usando a função WTSFreeMemory, como mostra o passo final do código do exemplo.

Se tivesse usado GetComputerName, eu obteria o nome do computador remoto, isto é, o servidor onde o programa está efetivamente sendo executado. A questão é que o mesmo nome seria retornado para qualquer usuário que acesse um desktop remoto nesse servidor ...

A lista de funções da Remote Desktop Services API inclui ainda formas de administrar o RDS tais como iniciar e encerrar sessões, inventariar as sessões ativas, modificar configurações de uma sessão, etc.