







Olá, pessoal! Aqui estamos nós, ficando fera em Web Services, afinal já consumimos uma REST API em nosso primeiro post! No entanto, podemos evoluir ainda mais. Para isso consumiremos mais um web service, só que agora, um cujo response é de XML! Neste serão exigidos dois requests. Será que conseguimos?
Bom, desta vez consumiremos dados do CPTEC/INPE, imprimindo na página a previsão do tempo dos próximos 4 dias de qualquer cidade de nosso país.
Antes de iniciarmos, atentemos às seguintes informações:
- Para obter os dados da previsão do tempo dos próximos 4 dias é nos fornecido o endpoint http://servicos.cptec.inpe.br/XML/cidade/{codigoCidade}/previsao.xml
- Já para obter o codigoCidade é o endpoint http://servicos.cptec.inpe.br/XML/listaCidades?city={nomeCidade}, e deste, do response resultante, obtêm-se o valor do elemento id.
Tudo isto é exemplificado no código abaixo:
<!DOCTYPE html>
<html>
<head>
<title>Previsor do Tempo</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Incorpora o jQuery ao site -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function getCidade(nomeCidade){
// Faz um HTTP Request, por meio de uma GET call (assíncrona)
// para um endpoint definido
// Aguarda como dados vindos do response um XML object
$.ajax({
url: "http://servicos.cptec.inpe.br/XML/listaCidades?city=" + nomeCidade,
method: "get",
dataType: "xml"
})
// Se o servidor retornar SUCCESS
.done(function(data){
// chama a getPrevisaoTempoProximosQuatroDias
// passando como argumento o valor do id
getPrevisaoTempoProximosQuatroDias($(data).find("id").text());
})
// Se o servidor retornar ERRO
.fail(function(jqXHR){
// imprime o HTTP error number
console.log("Erro HTTP: " + jqXHR.status);
});
}
function getPrevisaoTempoProximosQuatroDias(codigoCidade){
// Faz um HTTP Request, por meio de uma GET call (assíncrona)
// para um endpoint definido
// Aguarda como dados vindos do response um XML object
$.ajax({
url: "http://servicos.cptec.inpe.br/XML/cidade/" + codigoCidade + "/previsao.xml",
method: "get",
dataType: "xml"
})
// Se o servidor retornar SUCCESS
.done(function(data){
// imprime os dados do response no console do browser
console.log(data);
})
// Se o servidor retornar ERRO
.fail(function(jqXHR){
// imprime o HTTP error number
console.log("Erro HTTP: " + jqXHR.status);
});
}
</script>
</head>
<body>
<!-- Aciona um evento de clique chamando a getCidade function
por meio de um nome de cidade -->
<button type="button" onclick="getCidade('Atibaia')">getCidadeByName</button>
</body>
</html>
Bom, a partir disto, nossa tarefa é criar uma página web, na qual o usuário preenche o nome da cidade e requisita. Ao fazer isto, devemos imprimir na página o nome da cidade, o UF, o dia, além dos dados da previsão, isto é, tempo, temperatura máxima, temperatura mínima e IUV dos 4 dias conseguintes. O layout é livre. Lembremos sempre de recorrer à documentação quando surgirem dúvidas.
É ideal também, consultarmos os métodos do jQuery utilizados no exemplo para obter uma melhor compreensão. Assim, seguem os links: jQuery.ajax(), jQuery(), .find() e .text().
E que venha o desafio!
A resolução:
Bom, iniciamos obtendo os arquivos do Bootstrap v5.1.3 e do jQuery v3.6.0, juntamente com uma imagem favicon.png de nossa escolha, e então, criamos a seguinte estrutura de arquivos e diretórios:
p2/
assets/
css/
bootstrap.min.css
bootstrap.min.css.map
style.css
image/
favicon.png
js/
bootstrap.min.js
bootstrap.min.js.map
jquery-3.6.0.min.js
script.js
index.html
No arquivo index.html, adicionamos o código-exemplo fornecido, removendo deste os blocos de script e as tags button. A este mesmo arquivo, linkamos os respectivos assets. Feito isto, nosso arquivo ficou assim:
<!DOCTYPE html>
<html>
<head>
<title>Previsor do Tempo</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
<link href="assets/image/favicon.png" rel="icon" type="image/png">
</head>
<body>
<script src="assets/js/jquery-3.6.0.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/script.js"></script>
</body>
</html>
Agora entre as tags body criamos a estrutura abaixo. Esta usa o Grid System do Bootstrap, além de Forms e Icons.
<!-- cria o CONTAINER -->
<body class="container-fluid">
<!-- cria a LINHA 1 -->
<div class="box row">
<!-- cria a COLUNA 1 -->
<div class="box col-sm-8 col-xs-12">
<img alt="" src="assets/image/favicon.png">
<h2>Previsão do Tempo</h2>
</div>
<!-- cria a COLUNA 2 -->
<div class="box col-sm-4 col-xs-12">
<!-- Bootstrap Form Item: Input group > Button addons -->
<div class="input-group">
<input class="form-control" placeholder="Cidade" type="text">
<span class="input-group-btn">
<button class="btn" type="button">
<!-- Bootstrap Icon: Search (Copy HTML) -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
</button>
</span>
</div>
</div>
</div>
<!-- cria a LINHA 2 -->
<div class="box row"></div>
<script src="assets/js/jquery-3.6.0.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/script.js"></script>
</body>
Agora estilizamos nossa página, atualizando o arquivo style.css com as seguintes configurações:
.container-fluid {
font-family: Arial, Helvetica, sans-serif;
padding: 0;
}
.row {
margin: 0;
}
.box {
padding: 10px;
}
.container-fluid > .row:first-child,
.form-control,
.form-control:focus,
.btn {
background-color: #24292E;
}
.container-fluid > .row:first-child div {
align-items: center;
display: flex;
}
.row div img {
float: left;
}
h2 {
font-size: 28px;
margin-bottom: 0;
margin-left: 10px;
}
h2,
.form-control,
.form-control:focus,
svg {
color: #FFF;
}
.form-control::placeholder {
color: #999;
}
.form-control,
.form-control:focus,
.btn {
border: solid 1px #999;
}
.form-control:focus,
.btn:focus {
box-shadow: none;
}
.btn {
border-radius: 0 4px 4px 0;
}
Bom, com isto feito, nossa interface ficou com a seguinte aparência:

Figura 1 - Interface simplificada, feita com o framework Bootstrap.
Fonte: Autor (CC BY-NC-SA 4.0)
Bom, agora é hora de codificar as funções! Vamos lá? Para isso, primeiro copiamos as duas functions fornecidas no código-exemplo para nosso arquivo script.js, conforme o código abaixo:
function getCidade(nomeCidade){
$.ajax({
url: "http://servicos.cptec.inpe.br/XML/listaCidades?city=" + nomeCidade,
method: "get",
dataType: "xml"
})
.done(function(data){
getPrevisaoTempoProximosQuatroDias($(data).find("id").text());
})
.fail(function(jqXHR){
console.log("Erro HTTP: " + jqXHR.status);
});
}
function getPrevisaoTempoProximosQuatroDias(codigoCidade){
$.ajax({
url: "http://servicos.cptec.inpe.br/XML/cidade/" + codigoCidade + "/previsao.xml",
method: "get",
dataType: "xml"
})
.done(function(data){
console.log(data);
})
.fail(function(jqXHR){
console.log("Erro HTTP: " + jqXHR.status);
});
}
No arquivo index.html, na tag input, adicionamos um id com o valor cidade.
<input class="form-control" id="cidade" placeholder="Cidade" type="text">
Já na segunda row adicionamos um id com o valor localContent.
<div class="box row" id="localContent"></div>
Agora criamos no arquivo script.js um atributo constante, de escopo global, que armazene o #localContent. Criamos também uma nova function para impedir requests vazios.
const localContent = $("#localContent");
function isEmpty(){
// Verifica se o input está vazio
if($.trim($("#cidade").val()) === ""){
// imprime mensagem de erro
localContent.html("<h3>Informe a cidade para que a pesquisa seja realizada!</h3>");
} else {
// senão chama a getCidade passando como argumento o valor do input
getCidade($("#cidade").val());
}
}
No arquivo style.css, incluímos a tag h3 ao seletor .box:
.box,
h3 {
padding: 10px !important;
}
Também movemos a propriedade margin-bottom do seletor h2, para o seletor combinado:
h2,
h3 {
margin-bottom: 0;
}
e ainda adicionamos:
h3 {
color: #24292E;
font-size: 25px;
}
Já no index.html chamamos a function no atributo onclick da tag button.
<button class="btn" onclick="isEmpty()" type="button">
E agora abrimos a index.html no Google Chrome e testamos nossa aplicação, clicando no button sem preencher o input de texto. Assim, temos a mensagem abaixo, confirmando que estamos no caminho certo!

Figura 2 - Mensagem de erro impressa em caso de request com input vazio.
Fonte: Autor (CC BY-NC-SA 4.0)
Agora testaremos a nossa aplicação, informando o nome de uma cidade (atibaia | ATIBAIA | Atibaia, por exemplo) e clicando no botão de pesquisa, atentando-nos, neste momento, em manter o Console aberto. Feito isso, obtemos o seguinte resultado:

Figura 3 - Resposta em XML de um request feito ao Web service CPTEC/INPE no Console.
Fonte: Autor (CC BY-NC-SA 4.0)
Percebemos com este response que esta API não distingue maiúsculas de minúsculas, assim não precisamos nos preocupar nesse sentido. No entanto, outros testes ainda devem ser feitos, tais como a acentuação.
Para tanto, testamos com a cidade Jacareí:

Figura 4 - Erro HTTP 404 é reportado: termo acentuado não existe.
Fonte: Autor (CC BY-NC-SA 4.0)
E então percebemos dois problemas. O primeiro referente à acentuação, evidenciado por um HTTP error code, o 404. Já o segundo, é a impressão de uma mensagem de texto no console, ao invés da própria página.
Bom, solucionamos primeiro o segundo problema, substituindo a instrução console.log("Erro HTTP: " + jqXHR.status); presente nas functions getCidade e getPrevisaoTempoProximosQuatroDias por:
localContent.html("<h3>Erro HTTP: " + jqXHR.status + "</h3>");
Já para solucionarmos a questão da acentuação, devemos primeiro normalizar todo o texto digitado pelo usuário. Em seguida sobre este, aplicar uma função nativa JS, o replace() que recebe como primeiro argumento uma Regex que aceite apenas letras e espaços em branco. Desse modo, qualquer coisa diferente disso, deverá ser substituída por "" (vazio), o segundo argumento da função.
Considerando estas informações, realizamos a seguinte alteração em nossa isEmpty function:
getCidade($("#cidade").val().normalize("NFD").replace(/[^a-zA-Z\s]/g, ""));
Com isto feito, testamos Jacareí (acento), Guarar@ema (caractere especial) e rio de janeiro (espaços em branco), tendo êxito em todos estes cenários.
Bom, agora já temos os dados, basta-nos apenas imprimí-los na página! Para imprimir os dados do XML object, usamos a sintaxe $(response-object).find("nomeElemento").text(), que em resumo faz:
- Seleciona o objeto response
- Deste response localiza o elemento de nome "nomeElemento"
- Deste elemento, obtém o seu conteúdo textual
Aplicando a sintaxe apresentada teremos os seguintes resultados:
- nome da cidade: $(data).find("nome").text()
- UF: $(data).find("uf").text()
- dia: $(data).find("dia").text()
- tempo: $(data).find("tempo").text()
- temperatura máxima: $(data).find("maxima").text()
- temperatura mínima: $(data).find("minima").text()
- IUV: $(data).find("iuv").text()
Bom, para imprimir a previsão do tempo iremos criar a estrutura abaixo. Consideramos que 4 blocos desta estrutura serão necessários para exibir os dados dos 4 dias requeridos. Consideramos também que estes estarão contidos no elemento #localContent.
<div class="box col-md-3 col-sm-4 col-12">
<div class="row">
<div class="box col-md-6 col-6">TEMPO</div>
<div class="box col-md-6 col-6">
<p>NOME DA CIDADE, UF</p>
<p>DIA</p>
<p>TEMPERATURA MÁXIMAº C | TEMPERATURA MÍNIMAº C | IUV</p>
</div>
</div>
</div>
Para isto, acessamos o arquivo script.js e editamos o bloco done da getPrevisaoTempoProximosQuatroDias, substituindo a instrução console.log(data) por:
// Cria um atributo
let content = "";
// Faz com que o código interno às chaves ({}) seja repetido 4 vezes
for (let i = 0; i < 4; i++) {
// Adiciona à content o conteúdo do bloco anterior, totalizando assim os 4 blocos de maneira sequenciada.
content = content.concat("<div class=\"box col-md-3 col-sm-4 col-12\"><div class=\"row\"><div class=\"box col-md-6 col-6\">" + $(data).find("tempo").text() + "</div><div class=\"box col-md-6 col-6\"><p>" + $(data).find("nome").text() + ", " + $(data).find("uf").text() + "</p><p>" + $(data).find("dia").text() + "</p><p>" + $(data).find("maxima").text() + "º C | " + $(data).find("minima").text() + "º C | " + $(data).find("iuv").text() + "</p></div></div></div>");
}
// Imprime na localContent o content
localContent.html(content);
Editamos também o arquivo style.css, modificando o seletor h2,h3 por:
h2,
h3,
div p:nth-child(3) {
margin-bottom: 0;
}
Movendo a propriedade color do seletor h3 para o novo seletor:
h3,
p {
color: #24292E;
}
E ainda, adicionando ao mesmo arquivo as seguintes novas configurações:
div p:first-child {
font-size: 18px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
div p:first-child,
div p:nth-child(2) {
margin-bottom: 10px;
}
div p:nth-child(2) {
font-size: 16px;
}
div p:nth-child(3) {
font-size: 14px;
}
Com isto feito, testamos:

Figura 5 - Impressão dos dados meteorológicos dos próximos 4 dias para a cidade de Bertioga (dados repetidos).
Fonte: Autor (CC BY-NC-SA 4.0)
Hum... não era exatamente esse resultado que esperávamos! Vejam que os elementos de previsão, isto é, tempo, dia, temperatura máxima, temperatura mínima, e IUV, todos foram impressos em cada um dos 4 dias. Mas porque isto aconteceu? Isto ocorreu porque não definimos quais dados especificadamente devem pertencer à cada bloco. Para fazer isso, devemos utilizar a função jQuery .eq(), que nada mais faz, que selecionar um elemento por meio de um índice informado.
Assim sendo, nossas instruções devem ficar:
- $(data).find("tempo").text() => $(data).find("tempo").eq(i).text()
- $(data).find("dia").text() => $(data).find("dia").eq(i).text()
- $(data).find("maxima").text() => $(data).find("maxima").eq(i).text()
- $(data).find("minima").text() => $(data).find("minima").eq(i).text()
- $(data).find("iuv").text() => $(data).find("iuv").eq(i).text()
Realizando a modificação em nosso arquivo script.js e depois testando, obtemos:

Figura 6 - Impressão dos dados meteorológicos dos próximos 4 dias para a cidade de Bertioga (dados corretos).
Fonte: Autor (CC BY-NC-SA 4.0)
Bem melhor, não é mesmo? Mas vamos melhorar ainda mais! Observamos que a exibição da data está no formato AAAA-MM-DD. Vamos no entanto, convertê-la para o formato Dia-da-semana (curto), DD/MM. E como fazê-lo?
Primeiramente lemos sobre a função nativa JS toLocaleDateString() para compreender em detalhes a conversão realizada. Depois criamos em nosso arquivo script.js a seguinte function:
function getDataFormatada(dataPrevisao) {
const option = {
weekday: "short",
day: "numeric",
month: "numeric"
};
return new Date(dataPrevisao).toLocaleDateString("pt-BR", option).replace(".", "");
}
E chamamos-a na function getPrevisaoTempoProximosQuatroDias, substituindo a instrução $(data).find("dia").eq(i).text() por:
getDataFormatada($(data).find("dia").eq(i).text())
Já em nosso style.css adicionamos o seletor abaixo, fazendo com que a primeira letra do elemento selecionado, que neste caso é a data convertida, seja exibida em maiúsculo.
div p:nth-child(2)::first-letter {
text-transform: capitalize;
}
Com tudo isto feito, testamos e obtemos:

Figura 7 - Impressão do nome da cidade com inicial maiúscula e a data formatada em dia da semana, mês e ano.
Fonte: Autor (CC BY-NC-SA 4.0)
Observando a imagem, nos perguntamos, o que significam os textos "ps" e "c", apresentados no elemento tempo na imagem apresentada? Para descobrir faz-nos necessário recorrer à documentação, na seção Siglas das condições do tempo.
Agora tudo ficou mais claro, no entanto, quem utilizar nosso previsor ainda não saberá o que representam as siglas. Podemos, simplesmente substituir as siglas, pelo nomes completo equivalentes, mas há uma solução que pode ser ainda mais interessante, que é a substituição das siglas por uma imagens representativa. Imagens vetorizadas e animadas, que tal?
Para isso, fizemos o download do repositório Weather Animated SVG Icon Set.
Com o download concluído, descompactamos o arquivo. Deste migramos as imagens SVG para nosso diretório /assets/image/.
Comumente imagens são inseridas utilizando a tag img, mas conforme sugerido pelo autor do repositório, utilizaremos a tag iframe.
Agora, criamos uma function em nosso arquivo script.js que retorna uma imagem, de acordo com a siglaTempo informada. Como há mais siglas do que imagens, haverá múltiplas siglas compartilhando as mesmas imagens. Sem mais delongas, segue:
function getImagem(siglaTempo) {
let imagem = "";
switch (siglaTempo) {
case "c": // chuva
case "cm": // chuva pela manhã
case "cn": // chuva à noite
case "ct": // chuva à tarde
imagem = "c_1_rainy.svg";
break;
case "ci": // chuvas isoladas
case "cv": // chuvisco
case "e": // encoberto
case "ec": // encoberto com chuvas isoladas
case "in": // instável
case "ncm": // nublado com possibilidade de chuva pela manhã
case "nct": // nublado com possibilidade de chuva à tarde
case "npp": // nublado com possibilidade de chuva
case "pcm": // possibilidade de chuva pela manhã
case "pct": // possibilidade de chuva à tarde
case "pp": // possibilidade de pancadas de chuva
case "ppm": // possibilidade de pancadas de chuva pela manhã
case "ppt": // possibilidade de pancadas de chuva à tarde
case "psc": // possibilidade de chuva
imagem = "b_3_very_cloudy.svg";
break;
case "ch": // chuvoso
imagem = "c_2_heavy_rain.svg";
break;
case "cl": // céu claro
imagem = "a_1_sunny.svg";
break;
case "g": // geada
imagem = "d_1_snow.svg";
break;
case "n": // nublado
case "vn": // variação de nebulosidade
imagem = "b_2_cloudy.svg";
break;
case "ncn": // nublado com possibilidade de chuva à noite
case "pcn": // possibilidade de chuva à noite
case "ppn": // possibilidade de pancadas de chuva à noite
imagem = "b_4_cloudy_night.svg";
break;
case "nd": // não definido
imagem = "";
break;
case "ne": // neve
imagem = "d_2_heavy_snow.svg";
break;
case "np": // nublado e pancadas de chuva
case "npm": // nublado com pancadas pela manhã
case "npn": // nublado com pancadas à noite
case "npt": // nublado com pancadas à tarde
imagem = "g_1_stormy.svg";
break;
case "nv": // nevoeiro
imagem = "d_4_fog.svg";
break;
case "pc": // pancadas de chuva
case "pm": // pancadas de chuva pela manhã
case "pnt": // pancadas de chuva à noite
case "pt": // pancadas de chuva à tarde
imagem = "g_2_very_stormy.svg";
break;
case "pn": // parcialmente nublado
imagem = "b_1_partly_cloudy.svg";
break;
case "ps": // predomínio de sol
imagem = "a_3_very_sunny.svg";
break;
case "t": // tempestade
imagem = "c_3_thunderstorm.svg";
break;
}
return imagem;
}
Por fim, em nossa function getPrevisaoTempoProximosQuatroDias substituímos o trecho <div class=\"box col-md-6 col-6\">" + $(data).find("tempo").eq(i).text() + "</div> por:
<div class=\"box col-md-6 col-6\"><iframe src=\"assets/image/" + getImagem($(data).find("tempo").eq(i).text()) + "\"></iframe></div>
E adicionamos ao style.css:
iframe {
border: none;
height: 100px;
width: 100px;
}
E então, testamos, obtendo:

Figura 8 - Uso de imagens vetorizadas (e animadas) para representar as Siglas das condições do tempo.
Fonte: Autor (CC BY-NC-SA 4.0)
Com isto feito, finalizamos esta resolução! Se gostaram, por favor, deixem seus COMENTÁRIOS, nos SIGAM e principalmente COMPARTILHEM para que assim, seja possível alcançar mais pessoas, que como vocês, curtem Programação, com conteúdos PRÁTICOS e GRATUITOS!
Para ver e/ou baixar o código-fonte desta resolução, ou mesmo melhorá-lo, visitem o repositório no GitHub.

Figura 9 - Previsão do Tempo Demo.
Fonte: Autor (CC BY-NC-SA 4.0)
Até a próxima!
Cite este material
FERNANDES, Fábio. Desenvolvendo um previsor do tempo (web app). aprendaCodar, 01 de maio de 2022. Disponível em: <https://aprendacodar.blogspot.com/2022/05/desenvolva-um-web-app-de-previsao-do.html>. Acesso em: 04 de abril de 2025, sob licença Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA 4.0).