Acentos errados em um Shapefile: .cpg, Latin-1 e UTF-8

Resposta curta: o .dbf que guarda os atributos de um Shapefile não carrega nenhuma codificação própria. Um arquivo-satélite de poucos bytes, o .cpg, indica uma; sem ele, cada leitor adivinha, e dois leitores adivinham de forma diferente. Adivinhe errado de um jeito e Amapá vira Amapá; adivinhe errado do outro jeito e vira Amap�. Os dois resultados são diferentes, então o lixo revela de que lado você errou, e o conserto é um .cpg de uma linha ou um comando ogr2ogr que declara a entrada antes de recodificá-la.

Tudo abaixo foi testado em 16 de setembro de 2026 com o GDAL 3.13.3 e o shapefile-viewer na sua versão de setembro de 2026 (static-page-tools@442f8d8 mais a mudança do .cpg solto descrita adiante, ainda não lançada quando isto foi escrito e publicada antes deste post); o que cada ferramenta imprimiu está citado. O conjunto de dados é a malha de 2022 do IBGE dos 27 estados brasileiros, cujo .dbf é UTF-8 e cujo .cpg diz isso: Amapá é armazenado como os dois bytes c3 a1. O guia de Shapefile explica o mecanismo em "Latin-1 por padrão, e o arquivo-satélite .cpg"; este post é a reprodução e os consertos.

Duas direções, dois tipos diferentes de lixo

Bytes UTF-8 lidos como Latin-1: apague o .cpg do arquivo do IBGE e diga ao GDAL que ele é Latin-1 com ogrinfo -al BR_UF_2022.shp -fields=YES -geom=NO --config SHAPE_ENCODING ISO-8859-1, e NM_UF imprime Amapá e São Paulo. Cada sequência UTF-8 de dois bytes foi lida como duas letras Latin-1, então o texto ficou mais longo e nada se perdeu; exportado para GeoJSON do mesmo jeito, o arquivo carrega "NM_UF":"Amapá" para sempre. Esse é o tipo de dano é que toda tabela de atributos latino-americana já viu: um leitor com padrão Latin-1 encontrando um arquivo UTF-8.

Bytes Latin-1 lidos como UTF-8: um .dbf Latin-1 guarda á como o único byte e1, que não é UTF-8 válido, então um decodificador UTF-8 o substitui por U+FFFD, o glifo , e a letra desaparece. Essa é a direção do navegador. O caso real é o Shapefile de terras indígenas da FUNAI: byte 29 do cabeçalho em 00, sem .cpg, e 288 dos seus 665 nomes imprimem como Acim� por padrão e como Acimã só quando o GDAL recebe ISO-8859-1. Uma precisão sobre as transcrições do GDAL: sem nada para se basear, o GDAL repassa o byte e1 sem mudança, e o que você vê é o seu terminal renderizando um byte inválido; só o decodificador do visualizador no navegador escreve de fato U+FFFD no valor.

Como um arquivo Latin-1 sem .cpg é gerado

Nada de exótico. Um ogr2ogr -f "ESRI Shapefile" uf.shp BR_UF_2022.shp simples, sem nenhuma opção de codificação, já basta: o GDAL 3.13.3 escreve um .dbf Latin-1, define o byte do driver de idioma no offset 29 como 87, e não escreve nenhum .cpg, mesmo quando o próprio .cpg da origem dizia UTF-8; o c3 a1 da origem vira e1 no caminho. O GDAL lê esse arquivo de volta corretamente porque respeita o byte 29 (ogrinfo reporta ENCODING_FROM_LDID=ISO-8859-1), então nada parece errado até o arquivo chegar a um leitor que nunca olha o byte 29, como o visualizador do navegador. A forma mais rara é a da FUNAI, byte 29 em 00 e sem .cpg, onde nem o GDAL adivinha nada (SOURCE_ENCODING= volta vazio) e repassa os bytes.

O que o visualizador do navegador mostra

O visualizador lê o .dbf com um decodificador, UTF-8 por padrão, e passa a usar o que o .cpg indicar. A mesma malha de 27 estados, convertida para Latin-1 com -lco ENCODING=ISO-8859-1 (byte 29 em 00, um .cpg de dez bytes dizendo ISO-8859-1), foi solta de quatro formas. Cada arraste leu 27 polígonos e 5 campos no painel de camadas, e digitar amap na caixa Buscar em 27 feições da tabela de atributos devolveu uma linha, 4 sob Número da linha e Multipolígono sob Tipo; a célula NM_UF é o que muda:

  • Zip com o .cpg dentro: Amapá e São Paulo, decodificados como Latin-1 porque o .cpg dizia isso.
  • O mesmo zip com o .cpg apagado: Amap� e S�o Paulo.
  • .shp, .dbf, .prj e .cpg soltos, largados juntos: Amapá. Este é novo: desde a mudança de setembro de 2026 o visualizador respeita um .cpg largado ao lado dos arquivos soltos; antes disso, arquivos soltos eram sempre lidos como UTF-8, seja lá o que o arquivo-satélite dissesse.
  • Arquivos soltos sem o .cpg: Amap�.

Mais dois arrastes fecham o quadro. O arquivo UTF-8 original com o .cpg apagado ainda lê Amapá no navegador, porque o padrão UTF-8 é certo para bytes UTF-8; é o mesmo arquivo que vira Amapá sob SHAPE_ENCODING ISO-8859-1 do GDAL. E um arquivo com a mesma forma da saída padrão do GDAL, byte 29 em 87 e sem .cpg, lê Amap� no visualizador tanto zipado quanto solto: o parser de DBF do visualizador nunca lê o byte 29 (grep -c 'getUint8(29)' node_modules/parsedbf/index.js imprime 0). O GDAL lê esse byte, o visualizador não, e o .cpg é o único sinal em que os dois concordam.

Correção 1: escreva o .cpg

Se os bytes são Latin-1, diga isso. Uma linha, sem precisar de quebra: printf 'ISO-8859-1' > BR_UF_2022_l1.cpg, ao lado do .dbf com o mesmo nome-base. Os dois leitores respeitaram: ogrinfo reporta ENCODING_FROM_CPG=ISO-8859-1 e imprime Amapá, e o visualizador, dado o zip com o novo arquivo dentro, mostra Amapá de novo. Escreva ISO-8859-1. O GDAL também aceita LATIN1, latin1 e o numérico 88591 da ESRI, e perdoa uma quebra de linha no final, mas o navegador passa o rótulo para o TextDecoder padrão, que não conhece 88591 e recai para UTF-8: um .cpg dizendo 88591Amap� no visualizador e Amapá no GDAL.

Correção 2: recodifique para UTF-8 e declare a entrada

Se preferir entregar um arquivo UTF-8, o comando seguro declara tanto a codificação de entrada quanto a de saída: ogr2ogr -f "ESRI Shapefile" fixed.shp BR_UF_2022_l1.shp -oo ENCODING=ISO-8859-1 -lco ENCODING=UTF-8. Depois, cat fixed.cpg imprime UTF-8, os bytes guardados para á são c3 a1, o ogrinfo reporta ENCODING_FROM_CPG=UTF-8 e imprime Amapá, e o visualizador mostra Amapá a partir do zip. O .cpg e os bytes concordam, e todo leitor chega na mesma resposta.

A armadilha: -lco ENCODING=UTF-8 sozinho

O atalho tentador é -lco ENCODING=UTF-8 sem o -oo, e se funciona depende de se o GDAL sabia o que estava lendo. Num arquivo cuja codificação o GDAL não consegue determinar (sem .cpg, byte 29 em 00, a forma da FUNAI) ele copia os bytes Latin-1 sem mudança e rotula a cópia: cat out.cpg imprime UTF-8, o .dbf ainda guarda e1, e o ogrinfo reporta ENCODING_FROM_CPG=UTF-8 enquanto imprime Amap�. O .cpg mente, e mente para todo mundo: o visualizador mostra Amap� para essa saída e o GDAL mostra o mesmo. Um arquivo Latin-1 que já carrega um .cpg errado dizendo UTF-8 segue o mesmo caminho, uma cópia sem efeito com a mentira carregada adiante. Na própria saída padrão do GDAL, byte 29 em 87, o atalho funciona bem: o GDAL confia no byte do cabeçalho, lê Latin-1 e escreve c3 a1 de verdade com um .cpg sincero. Como raramente você sabe qual das três formas recebeu, -oo ENCODING=ISO-8859-1 não custa nada e é certo em todas elas. No QGIS o equivalente é o menu de codificação da fonte de dados nas propriedades da camada; isso muda como o QGIS lê o arquivo, não o arquivo.

Arquivos de teste que você pode usar

O conjunto de dados é a malha estadual de 2022 do IBGE, 13.717.460 bytes, SHA-256 282ec7f0f0beeeead45e6609f4ffffce161bda04cb8ee0afcad2316d1c841bcb; a listagem do diretório não imprime nenhuma linha de licença, então dê crédito ao IBGE e confira os termos do portal antes de republicar.

Para reproduzir a variante quebrada, converta para Latin-1 com ogr2ogr -f "ESRI Shapefile" latin1/BR_UF_2022_l1.shp BR_UF_2022.shp -lco ENCODING=ISO-8859-1, depois apague latin1/BR_UF_2022_l1.cpg. Zipe os quatro arquivos restantes, ou solte-os soltos, no shapefile-viewer e busque por amap. O post de comparação mediu a direção contrária no mesmo arquivo em "Codificação", ao lado do GeoPackage e do GeoJSON, que são UTF-8 por especificação e não têm arquivo-satélite para perder.

Para times

Um acento que sobrevive numa máquina e morre na próxima é um arquivo de cinco bytes que ninguém sabia que precisava enviar. O Geodocs mantém os valores dos seus campos como UTF-8 num único mapa compartilhado, então Amapá aparece igual em todo dispositivo, e a questão da codificação só surge na exportação, quando agora você já sabe quais cinco bytes escrever.

Artigos Relacionados

Navegação