Nova ferramenta publicada : Ruas atingidas por enchentes

by Cássio R Eskelsen 24. fevereiro 2010 21:35

Dando continuidade à série de ferramentas que estou criando para acompanhamento de crises climáticas, publiquei recentemente o site Cotas Enchentes Blumenau. Esse site mostra quais as ruas são atingidas a cada nível de excesso no principal rio que cruza a cidade. O site permite simular vários níveis diferentes.

Um detalhe importante: esse site foi desenvolvido utilizando Java no back-end! Apesar de já ter contato com o Java de uma forma ou de outra desde 1998, foi o primeiro sistema sério que desenvolvi utilizando ele.

A relação de ruas com suas respectivas posições /cotas está hospedada no Google Fusion Tables. Esse é um serviço recente do Google que me deixou muito surpreso em termos de flexibilidade e performance.

O site está hospedado na estrutura de Cloud Computing da Google, o Google App Engine. Confesso que no começo apanhei um pouco para entender sua filosofia, mas passada a dificuldade inicial, estou extremamente satisfeito com o serviço.

O serviço está pronto para mostrar o nível do rio no momento, baseando no sistema SIBI/Furb, mas deixo isso desativado nos momentos em que não estamos passando por um enchente (espero nunca precisar ativar!).

Espero que o serviço seja útil.

Gestão/Acompanhamento de crises é um assunto de meu interesse. Se você tem alguma idéia de aplicativo nessa área, por favor, sinta-se a vontade para sugerir!

Tags:

Geo | Gestão de Crises | Google Maps

Usando o Google Maps para exibir o clima no Brasil

by Cássio R Eskelsen 5. dezembro 2009 18:00

Por indicação do Djonatas Tenfen achei esse link que descreve como exibir a previsão do tempo na Europa usando o Bing Maps.

Com esse artigo pretendo mostrar como fazer para exibir o clima no Brasil, obviamente usando o Google Maps!

A primeira dificuldade é encontrar um serviço com o clima em todo o Brasil e que seja relativamente fácil de ser utilizado (sem necessidade de fazer engenharia reversa de páginas web). Por sorte acabei encontrando o serviço do INPE (instituto Nacional de Pesquisas Espaciais) que traz a previsão em formato RSS.

1. Criando a Aplicação

Vou criar um projeto ASP.NET MVC utilizando o Visual Studio 2008. Utilizarei um template para substituir o layout default e não perder muito tempo com layout.

O foco desse artigo é o Google Maps e não o C#/.Net então não entrarei em muitos detalhes em relação à parte server do projeto (a não ser o tratamento do RSS)

2. Inserindo o Google Maps

Para cada projeto Google Maps você precisa solicitar uma chave para utilizar a API do Google Maps (a partir da versão 3 da API isso não será mais necessário). Essa chave é relativa à URL do servidor, o que significa que você precisa de uma chave para o desenvolvimento local e outra para a publicação.

Para facilitar a utilização da chave correta criei uma classe helper que analisando a URL do servidor pega a chave correta do web.config:

using System.Configuration;
using System.Web;

namespace Weather.Helpers
{
    public class GMapsApi
    {
        public static string GetMapsAPI()
        {
            if (HttpContext.Current.Request.Url.Host.ToLower().Contains("localhost"))
            {
                return ConfigurationManager.AppSettings["GMapsLocalKey"];
            }
            else
            {
                return ConfigurationManager.AppSettings["GMapsPublicKey"];
            } 
        }
    }
}

Obviamente no web.config preciso configurar essas duas chaves:

<appSettings>
  <add key="GMapsLocalKey" value="sua chave para localhost"/>
  <add key="GMapsPublicKey" value="sua chave para o servidor onde irá publicar"/>
</appSettings>

O primeiro passo para inserir um mapa do Google Maps é chamar o javascript com a API do Google Maps. Isso deve ser feito colocando a seguinte chamada na tag <head> da página:

<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;
key=
<%=Weather.Helpers.GMapsApi.GetMapsAPI() %>&sensor=true" type="text/javascript"></script>
Os parâmetros são:

v=2: indica que estamos usando a versão 2 da API do Google Maps;
key: sua chave da API. Perceba que estou chamando aqui a minha função que determina qual a chave a ser utilizada (na versão 3 do Google Maps ela não é mais necessária)
sensor=true: indica que o Google Maps deve tentar determinar a posição do usuário (indicado para aplicações mobile)

Procure chamar a API apenas onde ela será utilizada. Por isso não indico colocar no site.master de suas aplicações ASP.NET MVC. O mais indicado é usar o placeholder HeadContent.

Irei criar uma função javascript de nome initialize() que deve instanciar o objeto mapa e definir qual elemento DOM que conterá o mapa. Normalmente o elemento DOM é uma DIV.

Dentro do place holder MainContent irei adicionar um elemento div:

<div id="map" style="width: 100%; height: 750px;"></div> 
Colocar uma div ocupando 100% da altura merece um post a parte, então irei fixar aqui em 750 pixels de altura!

Agora já podemos definir a função initialize():

<script type="text/javascript" >
var mapa; function
initialize() { var map = new GMap2(document.getElementById("map")); mapa.setCenter(new GLatLng(-16.684185, -50.28125), 5); mapa.addControl(new GLargeMapControl()); mapa.enableScrollWheelZoom(); mapa.setMapType(G_DEFAULT_MAP_TYPES[2]); } </script>
Agora a brincadeira começou a ficar interessante!

A 2a. linha cria uma variável de nome “mapa”. Criamos ela fora de initialize para que tenhamos escopo na página
A 4a. linha inicializa um objeto mapa dentro da div “map”
A 5a. define a posição central inicial (calculei as coordenadas para que sejam exibidas a maioria das capitais brasileiras em uma resolução de 1024x768). O valor “5” define o zoom inicial.
A 6a. linha insere no mapa o controle que permite alterar o zoom e os botões que fazem o “pan” para os lados e para cima e para baixo.
A 7a. linha informa que deve ser habilitado o zoom in/ou com a rodinha do mouse.
A 8a. linha define que o tipo inicial do mapa deve ser o híbrido.

Quando iremos chamar a função initialize? Usaremos o jQuery para chamá-la apenas quando toda a página estiver montada.

No final, a página ficará com esse código:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server"> Tempo no Brasil </asp:Content> <asp:Content ID="head" ContentPlaceHolderID="HeadContent" runat="server"> <script src="../../Scripts/jquery-1.3.2.js" type="text/javascript"></script> <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;
key=
<%=Weather.Helpers.GMapsApi.GetMapsAPI() %>&sensor=true" type="text/javascript"></script> <script type="text/javascript"> var mapa; function initialize() { mapa = new GMap2(document.getElementById("map")); mapa.setCenter(new GLatLng(-16.684185, -50.28125), 5); mapa.addControl(new GLargeMapControl()); mapa.enableScrollWheelZoom(); mapa.setMapType(G_DEFAULT_MAP_TYPES[1]); } </script> </asp:Content> <asp:Content ID="body" ContentPlaceHolderID="BodyPlaceHolder" runat="server"> onUnload="GUnload()" </asp:Content> <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server"> <div id="map" style="width: 100%; height: 780px;"> </div> <script type="text/javascript"> $(document).ready(function() { initialize(); }); </script> </asp:Content>

Perceba que estou chamando a função GUnload() da própria API do Google Maps que tem como objetivo limpar da memória todo o mapa assim que o usuário sair da página, diminuindo dessa forma a chance de eventuais problemas com gasto excessivo de memória.

O resultado será esse:

image

3. Construindo o “Banco de Dados” de cidades

Como o objetivo desse post é mostrar a construção do mapa, vou usar um modelo de dados bem simples, colocando em uma lista de memória as capitais dos estados do Brasil.

Obviamente você pode estender esse modelo colocando as maiores cidades, gravando os dados em um banco de dados, etc.

Para o exemplo irei usar a classe abaixo, contendo o ID da cidade no INPE, o nome da capital, a sigla do estado e as coordenadas geográficas da cidade.

using System.Collections.Generic;

namespace Weather.Models
{
    public class City
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public decimal Latitude { get; set; }

        public decimal Longitude { get; set; }

        public string StateAcronym { get; set; }

        public static List<City> GetCities()
        {
            List<City> cities = new List<City>();

            cities.Add(new City() { Id = 238, Name = "Porto Velho", StateAcronym = "RO",
                Latitude = -8.7624674766M, Longitude = -63.9044876278M });
            cities.Add(new City() { Id = 240, Name = "Rio Branco", StateAcronym = "AC",
                Latitude = -9.9754648208M, Longitude = -67.8105087280M });
            cities.Add(new City() { Id = 234, Name = "Manaus", StateAcronym = "AM",
                Latitude = -3.1024484634M, Longitude = -60.0254669189M });
            cities.Add(new City() { Id = 223, Name = "Boa Vista", StateAcronym = "RR",
                Latitude = 2.8195800781M, Longitude = -60.6734657287M });
            cities.Add(new City() { Id = 221, Name = "Belém", StateAcronym = "PA",
                Latitude = -1.4564432713M, Longitude = -48.5043983304M });
            cities.Add(new City() { Id = 232, Name = "Macapá", StateAcronym = "AP",
                Latitude = 0.0385661366M, Longitude = -51.0664177261M });
            cities.Add(new City() { Id = 236, Name = "Palmas", StateAcronym = "TO",
                Latitude = -10.1674923853M, Longitude = -48.3334037018M });
            cities.Add(new City() { Id = 243, Name = "São Luís", StateAcronym = "MA",
                Latitude = -2.5304510629M, Longitude = -44.3033714168M });
            cities.Add(new City() { Id = 245, Name = "Teresina", StateAcronym = "PI"
                , Latitude = -5.0894684783M, Longitude = -42.8023605331M });
            cities.Add(new City() { Id = 229, Name = "Fortaleza", StateAcronym = "CE",
                Latitude = -3.7174611763M, Longitude = -38.5433273026M });
            cities.Add(new City() { Id = 235, Name = "Natal", StateAcronym = "RN",
                Latitude = -5.7954773774M, Longitude = -35.2093048098M });
            cities.Add(new City() { Id = 231, Name = "João Pessoa", StateAcronym = "PB",
                Latitude = -7.1154866001M, Longitude = -34.8633003854M });
            cities.Add(new City() { Id = 239, Name = "Recife", StateAcronym = "PE",
                Latitude = -8.0544929496M, Longitude = -34.8813018808M });
            cities.Add(new City() { Id = 233, Name = "Maceió", StateAcronym = "AL",
                Latitude = -9.6665029515M, Longitude = -35.7353096042M });
            cities.Add(new City() { Id = 220, Name = "Aracaju", StateAcronym = "SE",
                Latitude = -10.9115095226M, Longitude = -37.0723190324M });
            cities.Add(new City() { Id = 242, Name = "Salvador", StateAcronym = "BA",
                Latitude = -12.9715194702M, Longitude = -38.5113372802M });
            cities.Add(new City() { Id = 222, Name = "Belo Horizonte", StateAcronym = "MG",
                Latitude = -19.8175430307M, Longitude = -43.9563903769M });
            cities.Add(new City() { Id = 246, Name = "Vitória", StateAcronym = "ES",
                Latitude = -20.3195533734M, Longitude = -40.3383636479M });
            cities.Add(new City() { Id = 241, Name = "Rio de Janeiro", StateAcronym = "RJ",
                Latitude = -22.9035564806M, Longitude = -43.2083928627M });
            cities.Add(new City() { Id = 244, Name = "São Paulo", StateAcronym = "SP",
                Latitude = -23.5485496519M, Longitude = -46.6364212029M });
            cities.Add(new City() { Id = 227, Name = "Curitiba", StateAcronym = "PR",
                Latitude = -25.4285487993M, Longitude = -49.2734451079M });
            cities.Add(new City() { Id = 228, Name = "Florianópolis", StateAcronym = "SC",
                Latitude = -27.5975551455M, Longitude = -48.5494461504M });
            cities.Add(new City() { Id = 237, Name = "Porto Alegre", StateAcronym = "RS",
                Latitude = -30.0335502624M, Longitude = -51.2304801940M });
            cities.Add(new City() { Id = 225, Name = "Campo Grande", StateAcronym = "MS",
                Latitude = -20.4435195922M, Longitude = -54.6464691162M });
            cities.Add(new City() { Id = 226, Name = "Cuiabá", StateAcronym = "MT",
                Latitude = -15.5965022580M, Longitude = -56.0974616760M });
            cities.Add(new City() { Id = 230, Name = "Goiânia", StateAcronym = "GO",
                Latitude = -16.6795196708M, Longitude = -49.2544251117M });
            cities.Add(new City() { Id = 224, Name = "Brasília", StateAcronym = "DF",
                Latitude = -15.7805194860M, Longitude = -47.9304084804M });

            return cities;
        }
    }
}
4. Buscando as informações do INPE

A partir daqui entramos no território sem lei dos XMLs.
Temos que buscar o RSS das cidades desejadas e salvar localmente. Depois temos que ler os XMLs e extrair as informações desejadas.

Criei um helper para efetuar esse download (\Helpers\INPEHelper.cs). A parte principal do helper é esta:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Web;
using Weather.Models;
using System.Text;

namespace Weather.Helpers
{
    public class INPEHelper
    {
        private string URL = "http://servicos.cptec.inpe.br/RSS/cidade/{0}/previsao.xml";
        public INPEHelper()
        {
        }

        public List<Forecast> GetForecasts(List<City> cities)
        {
            List<Forecast> forecs = new List<Forecast>();

            foreach (City city in cities)
            {
                GetRSS(city.Id);
                RSS rs = RSS.Load((HttpContext.Current.Server.MapPath("~/app_data/" + 
string.Format("{0}.xml", city.Id)))); var dataXml = DateTime.Parse(rs.channel.item[0].pubDate[0]); var previsao = rs.channel.item[0].description[0]; var imagem = previsao.Substring(previsao.IndexOf("<img"),
previsao.IndexOf("/>") - 1); Forecast fore = new Forecast(); fore.City = city; fore.ForecastImageUrl = imagem; fore.ForecastTemperature = previsao.Substring(previsao.IndexOf("</b>") + 8); fore.ForecastTemperature = fore.ForecastTemperature.
Remove(fore.ForecastTemperature.IndexOf("<br>")); string data = previsao.Substring(previsao.IndexOf("<br>") + 4); data = data.Remove(data.IndexOf("</b>")); fore.ForecastDate = GetForeDate(dataXml, data); File.Delete(HttpContext.Current.Server.MapPath("~/app_data/" +
string.Format("{0}.xml", city.Id))); } return forecs; } private void GetRSS(int cityCode) { DownloadFile(string.Format(URL, cityCode.ToString()),
HttpContext.Current.Server.MapPath("~/app_data/" + cityCode.ToString() + ".xml")); }

O método DownloadFile está nesse mesmo fonte.

Estou salvando os xml no app_data pois é uma pasta que normalmente tem permissão de gravação.

Um detalhe importante: para pesquisar no XML do RSS eu estou usando o Linq to XML. Já fiz um post tempos atrás sobre como utilizar esse framework. De qualquer forma já estará tudo pronto para você utilizar no código que irei disponibilizar para download. Você pode tratar o XML direto também, existem várias alternativas.

O RSS do INPE retorna a previsão do dia de amanhã (ou de hoje) e mais alguns dias para frente. A previsão de amanhã (ou de hoje) é dada através de uma imagem. As demais através de uma string. Por hora, irei pegar apenas a previsão de amanhã (ou de hoje) e exibir direto a imagem.

5. Exibindo as informações no Mapa

Agora que passamos a parte mais chatinha, falta apenas exibir os dados no mapa, o que será muito fácil de fazer em se tratando de Google Maps!

Utilizaremos a classe GIcon da API do Google Maps para criar um marcador personalizado com a imagem da previsão e a classe GMarker para colocar a previsão no mapa.

Poderíamos fazer o controller Index retornar uma lista de objetos Forecast e iterarmos sobre essa lista.  Mas aqui teríamos um problema: o objeto mapa pode não estar criado ainda e você irá tentar adicionar marcadores em um objeto nulo. Então para garantir que não teremos problemas iremos invocar os dados apenas após a chamada do método initialize:

   1:      <script type="text/javascript">
   2:          $(document).ready(function() {
   3:              initialize();
   4:   
   5:   
   6:              var url = "/Home/GetForecasts";
   7:              $.getJSON(url, null, function(data) {
   8:                  $.each(data, function(index, forec) {
   9:                      var forecIcon = new GIcon(G_DEFAULT_ICON);
  10:                      forecIcon.image = forec.ForecastImageUrl;
  11:                      forecIcon.iconSize = new GSize(73, 59);
  12:                      forecIcon.shadow = "http://maps.google.com/mapfiles/water.gif";
  13:                      forecIcon.shadowSize = new GSize(83, 59);
  14:                      markerOptions = { icon: forecIcon, title: forec.City.Name + '/' + 
forec.City.StateAcronym + ' Temperatura: ' + forec.ForecastTemperature };
  15:                      var point = new GLatLng(forec.City.Latitude, forec.City.Longitude)
  16:                      mapa.addOverlay(new GMarker(point, markerOptions));                  
  17:   
  18:                  });
  19:              });
  20:   
  21:          });
  22:   
  23:      </script>

A linha 7 utiliza ajax para chamar um método no servidor que retornará um json com os dados. O método está no controller Home (mais abaixo colo ele)

A partir da linha 8 eu itero sobre cada elemento json que retornou.

Nas linhas 9,10,11,12,13 eu crio um icone personalizado contendo a imagem do INPE. Como a imagem vai ficar meio ilegível, apliquei um fundo (o resultado final não é dos mais belos, mas serve para nossos fins didáticos).

Na linha 14 crio uma array contendo o ícone personalizado e um título contendo o nome da cidade e a temperatura.

Por último, nas linhas 15 e 16 eu crio um marcador utilizando a latitude e longitude da cidade e o ícone personalizado.

O resultado final é esse:

image

No link você pode baixar todo o código fonte da solução. Apenas não esqueça de gerar sua chave de API no Google Maps!

Qualquer dúvida, deixe um comentário!

Tags: , , , ,

Google Maps

MS lança o Photosynth... para comemorar, juntei ele com o Google Maps!

by Cássio R Eskelsen 22. agosto 2008 01:14

A Microsoft lançou uma das suas maiores promessas tecnológicas, o Photosynth. Claro, é mais um daqueles casos de tecnologia criada por uma startup obscura e posteriormente comprada por um big player, mas ninguém irá lembrar disso quando vir o que ele produz.

A idéia do Photosynth é analisar centenas (milhares?) de fotos de alguma coisa ou lugar e linká-las entre si, através das texturas e elementos da foto. Mais ou menos como o Photoshop faz ao montar um panorama com várias fotos.  No entanto, no Photosynth a imagem é dinâmica, você pode clicar nela para mostrar em volta ou aproximar algum detalhe (obviamente se houver foto de um detalhe).

O grosso do processamento do Photosynth é feito nos servidores da Microsoft. Ou, usando o termo da moda, computação na "nuvem". Para visualizar um "synth", você precisa descarregar um player no site. Com esse mesmo player você pode criar seus próprios synths e hospedar na Microsoft ( ela disponibiliza 20 Giga de espaço). Apenas reze para que o servidor não esteja sobrecarregado como aconteceu hoje!

Talvez o fato de exigir um player específico e que roda apenas no Windows seja o maior senão dele, mas com o tempo isso se resolve (ou as pessoas se acostumam).

Se você quiser ver como ele funciona antes de instalá-lo, rode o vídeo abaixo:

 

 

Bom, agora vamos para o lado geek!

Chris Pendleton, "evangelista" do Virtual Earth, fez um post sobre o lançamento do Photosynth e como integrar um synth com o Virtual Earth. 
Não sou um grande fã do Virtual Earth. Prefiro o Google Maps, pois os mapas dele são mais completos aqui no Brasil e em outros lugares. No entanto, a tecnologia do Virtual Earth me parece superior, então vamos torcer para que a coleção de mapas dele aumente rapidamente!

Resolvi fazer algo semelhante ao que Chris mostrou, mas usando o Google Maps!

 

image

 

Não é necessário nada especial, apenas uma página HTML e javascript.

Leia mais...

Tags: ,

Geo | Web | Google Maps