Este repositório documenta o processo de criação, ingestão, enriquecimento e exportação de um banco de dados PostGIS (mexico) focado em dados geoespaciais do México. O objetivo é consolidar múltiplas fontes de dados em uma única base coesa para análise.
Os scripts de ingestão e análise referenciados neste documento estão localizados no diretório src/.
O banco de dados mexico é composto por dados de diversas fontes, ingeridos através de diferentes pipelines de ETL.
| Tabela | Schema | Fonte de Dados Primária | Método de Ingestão | Script de Referência |
|---|---|---|---|---|
poligono |
public |
Thierry (Polígonos de CEP) | shp2pgsql (loop bash) |
src/ingestion/poligono/loop_pol.sh |
direccion |
public |
Claiton (Geoaddress / preserv-MX) | shp2pgsql (loop bash) |
src/ingestion/direccion/loop.sh |
inegi |
public |
Carlos (OpenAddresses / CSV) | \COPY (CSV) |
src/ingestion/inegi/inegi_load.sql |
new_inegi |
public |
Carlos (INEGI / Shapefiles) | ogr2ogr (loop bash + Docker) |
src/ingestion/new_inegi/ingest.sh |
jurisdiction |
optim |
Banco newgrid (Limites Adm.) |
pg_dump + psql \copy |
src/migration/migrate_jurisdiction.sh |
jurisdiction_geom |
optim |
Banco newgrid (Limites Adm.) |
pg_dump + psql \copy |
src/migration/migrate_jurisdiction.sh |
overture_add |
public |
Overture Maps (Endereços) | DuckDB + GDAL + parquet_fdw |
src/ingestion/overture/overture_add.sql |
overture3 |
public |
Overture Maps (Endereços) | DuckDB + postgres connector |
src/ingestion/overture/overture_to_postgres.sql |
produto_final_staging |
public |
Tabela new_inegi |
CREATE TABLE AS SELECT ... |
src/analysis/03_create_staging_table.sql |
Este repositório contém os scripts (src/), mas não os dados brutos, que são muito grandes para o Git.
Para que os scripts de ingestão funcionem, os dados de origem devem ser baixados e colocados nos caminhos exatos que os scripts esperam. A pasta data/ deste repositório contém READMEs que descrevem as fontes e os caminhos esperados para cada conjunto de dados.
Resumo das Fontes de Dados e Caminhos Esperados:
| Fonte de Dados | Link de Download | Comando de Extração (Exemplo) | Caminho Esperado |
|---|---|---|---|
| Polígonos CEP | SharePoint do Thierry | unzip -o "*.zip" -d /mnt/dados/download/mexico/poligonos/ |
/mnt/dados/download/mexico/poligonos/ |
| Geoaddress | Digital Guard | unzip [arquivo.zip] -d /mnt/dados/download/mexico/direccion |
/mnt/dados/download/mexico/direccion |
| INEGI (CSV) | Digital Guard | unzip [arquivo.zip] (Gera inegi-20240621.csv) |
/mnt/dados/download/mexico/ |
| INEGI (SHPs) | (Fornecido por Carlos) | (N/A) | /mnt/dados/MX/download-final |
Banco newgrid |
(Acesso de rede) | (N/A) | (Acesso de rede) |
Para reproduzir este projeto:
- Baixe todas as fontes de dados originais.
- Extraia-as para os caminhos exatos listados acima.
- OU edite os scripts em
src/para apontar para os novos locais onde você salvou os dados.
Estes dados representam os polígonos de códigos postais (Fonte: Thierry). Os arquivos SHP de origem são esperados em /mnt/dados/download/mexico/poligonos/.
- Script de Ingestão:
src/ingestion/poligono/loop_pol.sh - Processo: Um loop
bash(shp2pgsql) cria a tabelapoligonocom o primeiro SHP e anexa (-a) os demais.
Dados de geo-endereçamento (Fonte: Claiton). Os arquivos SHP de origem são esperados em /mnt/dados/download/mexico/direccion.
- Script de Ingestão:
src/ingestion/direccion/loop.sh - Processo: Mesmo método da tabela
poligono, criando e anexando dados na tabeladireccion.
Estas tabelas foram migradas do banco newgrid para o banco mexico.
- Script de Migração:
src/migration/migrate_jurisdiction.sh - Processo: O script executa um processo de 3 etapas para copiar a estrutura, copiar os dados filtrados (
WHERE isolabel_ext LIKE 'MX%') e, por fim, aplicar os índices e chaves.
Dados de endereço em formato CSV (Fonte: Carlos/OpenAddresses). O arquivo inegi-20240621.csv é esperado em /mnt/dados/download/mexico/.
- Script de Ingestão:
src/ingestion/inegi/inegi_load.sql - Pipeline de ETL: O script SQL cria a tabela, ingere os dados via
\COPY, cria as geometrias (geomem 6362,geom_wgsem 4326) e enriquece opostcodeusandoST_Withincom a tabelapoligono.
Esta é a fonte principal de dados de endereço. Os dados brutos (fornecidos por Carlos) chegaram em uma estrutura complexa de arquivos .zip aninhados contendo diversas camadas geográficas misturadas.
-
Localização Esperada:
/mnt/dados/MX/download-final -
Fase 1: Staging (Extração e Organização)
- Script de Referência:
src/ingestion/new_inegi/prepare_staging.sh - Desafio: Os dados continham múltiplos níveis de compactação (zips dentro de zips).
- Processo:
- Permissões: Ajuste de permissões (
chmod) para garantir acesso. - Extração Recursiva: Execução de múltiplos comandos
unzipem cascata, movendo o conteúdo para pastas temporárias (extract1aextract5) para garantir que nenhum arquivo fosse perdido. - Catalogação: Criação de pastas temáticas (
direccion,vial,manzana, etc.) e uso do comandofindpara mover os Shapefiles corretos para cada pasta baseando-se no sufixo do arquivo (ex:*ne.shp->direccion).
- Permissões: Ajuste de permissões (
- Script de Referência:
-
Fase 2: Carga (GDAL/OGR via Docker)
- O processo de carga foi focado na pasta resultante
direccion, que continha os shapefiles de nós de endereço. - Script Principal:
src/ingestion/new_inegi/ingest.sh- Script
bashque usaogr2ogr(via Docker) em loop. O primeiro SHP cria a tabelapublic.new_inegi(EPSG:6362), e os demais são anexados (-append).
- Script
- Script de Contingência:
src/ingestion/new_inegi/re_ingest.sh- Script de retomada que continua o
ogr2ogr -appenda partir de um ponto de parada (LAST_STARTED=...).### 3.5.public.new_inegi(Endereços INEGI - Fonte Shapefiles)
- Script de retomada que continua o
- O processo de carga foi focado na pasta resultante
Esta é a fonte principal de dados de endereço. Os dados brutos (fornecidos por Carlos) chegaram em uma estrutura complexa de arquivos .zip aninhados contendo diversas camadas geográficas misturadas.
-
Localização Esperada:
/mnt/dados/MX/download-final -
Fase 1: Staging (Extração e Organização)
- Script de Referência:
src/ingestion/new_inegi/prepare_staging.sh - Desafio: Os dados continham múltiplos níveis de compactação (zips dentro de zips).
- Processo:
- Permissões: Ajuste de permissões (
chmod) para garantir acesso. - Extração Recursiva: Execução de múltiplos comandos
unzipem cascata, movendo o conteúdo para pastas temporárias (extract1aextract5) para garantir que nenhum arquivo fosse perdido. - Catalogação: Criação de pastas temáticas (
direccion,vial,manzana, etc.) e uso do comandofindpara mover os Shapefiles corretos para cada pasta baseando-se no sufixo do arquivo (ex:*ne.shp->direccion).
- Permissões: Ajuste de permissões (
- Script de Referência:
-
Fase 2: Carga (GDAL/OGR via Docker)
- O processo de carga foi focado na pasta resultante
direccion, que continha os shapefiles de nós de endereço. - Script Principal:
src/ingestion/new_inegi/ingest.sh- Script
bashque usaogr2ogr(via Docker) em loop. O primeiro SHP cria a tabelapublic.new_inegi(EPSG:6362), e os demais são anexados (-append).
- Script
- Script de Contingência:
src/ingestion/new_inegi/re_ingest.sh- Script de retomada que continua o
ogr2ogr -appenda partir de um ponto de parada (LAST_STARTED=...).
- Script de retomada que continua o
- O processo de carga foi focado na pasta resultante
Dois métodos foram usados para ingerir e comparar os mesmos dados de endereço da Overture Maps.
Pré-requisito: Instalação do DuckDB
Ambos os métodos exigem o duckdb (CLI) instalado no sistema Linux.
- Comando de Instalação (Linux):
curl -s https://install.duckdb.org | sh
- Script:
src/ingestion/overture/overture_add.sql - Processo: O DuckDB baixa os Parquets do S3 da Overture.
ogr2ogré usado para converter/corrigir os tipos. A extensãoparquet_fdwé usada no PostGIS para criar umaFOREIGN TABLE.
- Script:
src/ingestion/overture/overture_to_postgres.sql - Processo: O DuckDB (com as extensões
spatialepostgres) conecta-se diretamente ao PostGIS (ATTACH 'dbname=mexico...') e executa umCREATE TABLE pg.public.overture3 AS SELECT ...para ler do S3 e escrever uma tabela física no PostGIS.
Estas são as geometrias "limpas" de estados e municípios usadas como fonte da verdade para o enriquecimento (ver Seção 4.3).
- Fonte de Download: INEGI Marco Geoestadístico 2024
- Processo de Extração:
- O arquivo
...794551132173_s.zipé baixado. - Dentro dele, o arquivo
mg_2024_integrado.zipé extraído. - Este, por sua vez, contém o subdiretório
conjunto_de_datos.
- O arquivo
- Ingestão: Os arquivos
00ent.shp(Estados) e00mun.shp(Municípios) foram ingeridos no banco de dados (schemaoptim) usandoogr2ogr(GDAL). - Transformação (SRID): Durante a ingestão, os dados foram transformados do EPSG:6372 (origem) para EPSG:6362 (destino), para garantir a compatibilidade com a tabela
poligono. - Tabelas Finais:
optim.estadoeoptim.municipio.
Após a ingestão, os dados brutos foram limpos e cruzados para gerar um produto final coeso.
- Problema: Análises espaciais falham com geometrias inválidas (
NOTICE: Ring Self-intersection...). - Solução: Um script de limpeza foi executado para corrigir as geometrias usando
ST_MakeValid()eST_CollectionExtract()(para evitar o erroGeometryCollection does not match column type). - Script:
src/analysis/01_clean_geometries.sql
O primeiro método para preencher estado_name e municipio_name na tabela poligono usou a hierarquia da tabela optim.jurisdiction baseada no isolabel_ext.
- Scripts:
src/analysis/02_enrich_poligono_state_municipality.sqlesrc/analysis/02b_patch_orphan_poligonos.sql - Lógica (Híbrida):
- Estado: Encontrava a jurisdição de
length(isolabel_ext) = 6com maior sobreposição. - Município: Encontrava a jurisdição de
length(isolabel_ext) > 6com maior sobreposição (exceto para 'MX-CMX'). - Patch: Um script (
02b) corrigia 13 polígonos órfãos usando uma lógica "bottom-up" (do município para o estado viaparent_id).
- Estado: Encontrava a jurisdição de
Devido à complexidade do Método 1, uma abordagem mais robusta foi desenvolvida usando fontes de geometria limpas e dedicadas(optim.estado e optim.municipio) ingeridas na Seção 3.7.
- Script:
src/analysis/enrich_poligono_mx.sql - Lógica (Baseada em Fontes Limpas): Este script é mais rápido e robusto. Ele ignora a lógica
isolabel_exte usa tabelas de geometria separadas (ambas em SRID 6362, eliminando a necessidade deST_Transform):- Etapa 1 (Estado): Encontra o
NOMGEOda tabelaoptim.estadocom a maior sobreposição (ST_Area(ST_Intersection(...))) e preenchepoligono.estado_name. - Etapa 2 (Município): Encontra o
NOMGEOda tabelaoptim.municipiocom a maior sobreposição e preenchepoligono.municipio_name.
- Etapa 1 (Estado): Encontra o
(Esta etapa depende do poligono ter estado_name e municipio_name, fornecidos pelo Método 1 ou 2)
Para otimizar o pipeline final, uma tabela de staging (produto_final_staging) foi criada, contendo apenas as colunas necessárias da massiva tabela new_inegi.
- Script:
src/analysis/03_create_staging_table.sql - Processo: Cria a tabela selecionando
gid,geome formatando as colunas de endereço (concat_ws(' ', tipovial, nomvial) AS via,numext AS hnum,nomasen AS nsvia). A colunacporiginal é mantida comopostcode_original.
A tabela de staging (pontos de endereço) foi enriquecida com os dados da tabela poligono (áreas de CEP, agora com dados de estado/município).
- Script:
src/analysis/04_enrich_staging_table.sql - Processo: Um
UPDATEcomST_Withiné usado para transferir os atributos (cp,estado_name,municipio_name) do polígono para o ponto contido nele. - Validação (Confronto): O
postcode_original(do ponto) é comparado com ocpdo polígono (postcode_validado), e o resultado salvo na colunaflag_postcode_match.
A etapa final é exportar a tabela de staging enriquecida para um CSV limpo.
- Script:
src/export/export_final.sql - Local de Saída: O script está configurado para salvar o arquivo em
/mnt/dados/MX/mexico/produto_final.csv(a raiz do repositório). - Lógica da Consulta:
- Seleciona os dados da
produto_final_staging(gid, via, hnum, nsvia, estado_name, municipio_name). - Converte a geometria (
geomSRID 6362) paralatitudeelongitude(WGS84) usandoST_Transform,ST_YeST_X. - Aplica uma lógica de fallback (plano B) para o código postal:
COALESCE(s.postcode_validado, s.postcode_original) AS postcode. - Exporta o resultado para CSV usando
\copy.
- Seleciona os dados da
Após a ingestão de dados (Fase 3), o fluxo de trabalho de análise e exportação é o seguinte:
1. Executar Análise no psql:
Conecte-se ao banco (psql -U postgres -d mexico) e execute os scripts de análise em ordem:
-- 1. Limpa geometrias (Obrigatório)
\i src/analysis/01_clean_geometries.sql
-- 2. Enriquece 'poligono' (Método 2 - Fontes Limpas)
\i src/analysis/enrich_poligono_mx.sql
-- 3. CORRIGE os 13 órfãos
\i src/analysis/02b_patch_orphan_poligonos.sql
-- 4. Cria tabela 'produto_final_staging'
\i src/analysis/03_create_staging_table.sql
-- 5. Enriquece 'produto_final_staging' (Validação)
\i src/analysis/04_enrich_staging_table.sql
-- 6. Exportação de produto final com estrutura idêntica aos dados da OpenAddress
\i src/export/export_final.sqlEsta seção resume a origem, transformação e estado dos dados contidos no arquivo de saída produto_final.csv (e no arquivo de amostra amostra_produto_final.csv).
O produto final é um conjunto de dados de pontos de endereço derivado da tabela public.new_inegi do banco de dados mexico.
- Fonte: Os dados brutos foram baixados pelo colaborador Carlos do site do INEGI.
- Ingestão: A fonte consistia em múltiplos arquivos
.zip, que foram descompactados em um grande conjunto de arquivos.shp. Estes shapefiles foram ingeridos em lote (usando os scriptssrc/ingestion/new_inegi/) para criar a tabela únicapublic.new_inegi. - Filtragem de Atributos: Para a criação do produto final, foi criada uma tabela de staging (
produto_final_staging) que aproveitou apenas os atributos essenciais danew_inegi. Esta seleção foi baseada nas estruturas de dados de projetos como OpenAddresses e Overture Maps. - Enriquecimento Espacial: A tabela de staging foi enriquecida espacialmente (
ST_Within) usando os dados da tabelapublic.poligono(que, por sua vez, foi enriquecida pelaoptim.jurisdiction). - Validação de Postcode: O
postcodeoriginal danew_inegifoi "confrontado" (validado) com opostcodedo polígono onde o ponto estava contido.
A tabela a seguir descreve a origem de cada coluna no arquivo produto_final.csv:
| Coluna no CSV Final | Origem (Tabela.Coluna) | Transformação / Lógica |
|---|---|---|
gid |
new_inegi.gid |
ID primário original. |
estado |
poligono.estado_name |
Nome do estado (Ex: 'Jalisco') obtido via ST_Within. |
cidade |
poligono.municipio_name |
Nome do município (Ex: 'Guadalajara') obtido via ST_Within. |
via |
new_inegi.tipovial, new_inegi.nomvial |
Explicação: O nome da rua é composto pela junção de duas colunas: tipovial (ex: 'Avenida', 'Calle') e nomvial (ex: 'Vallarta'). Elas são unidas por um espaço para formar um nome completo (ex: 'Avenida Vallarta'). |
hnum |
new_inegi.numext |
Número externo da porta. |
nsvia |
new_inegi.nomasen |
Nome do assentamento (bairro/colônia). |
latitude |
new_inegi.geom |
ST_Y(ST_Transform(geom, 4326)) (WGS84). |
longitude |
new_inegi.geom |
ST_X(ST_Transform(geom, 4326)) (WGS84). |
postcode |
poligono.cp (Primário), new_inegi.cp (Fallback) |
Explicação: O código postal final é determinado por um processo de validação espacial. O postcode do ponto (new_inegi.cp) foi comparado com o postcode do polígono (poligono.cp) onde o ponto está localizado. A versão final prioriza o postcode do polígono (validado). Se o ponto não caiu dentro de nenhum polígono (um "ponto órfão"), o postcode original do ponto é usado como um fallback (plano B). |
A análise foi executada na tabela produto_final_staging, que contém um total de 31.236.822 pontos de endereço. A validação espacial (confrontando o postcode_original do ponto com o postcode_validado do polígono) revelou as seguintes métricas:
- Pontos com
postcode"Zero": 15.646.607 pontos (50,09% do total) tinham umpostcodeoriginal consistindo apenas de zeros (ex: '0', '00000'). - Pontos com
postcodeNulo: Apenas 4 pontos (insignificante). - Esta alta contagem de postcodes "zeros" foi a principal motivação para realizar a validação espacial contra a camada de polígonos.
- Correspondência Perfeita: 13.656.144 pontos (43,72% do total) tinham um
postcodeoriginal que bateu perfeitamente com opostcodedo polígono (flag_postcode_match = 't'). - Pontos Órfãos: 70.505 pontos (0,23% do total) não caíram dentro de nenhum polígono de CEP (
postcode_validado is null). Para estes, opostcodeoriginal foi mantido no arquivo final (lógica de fallback). - Total de Conflitos (Corrigidos): 17.510.171 pontos (56,06% do total) tiveram seu
postcodeoriginal corrigido pela análise espacial (flag_postcode_match = 'f').
O dado mais importante é a composição desses 17,5 milhões de conflitos:
- Correção de Zeros (Maioria): 15.619.830 conflitos (89,2% dos conflitos) foram casos em que o
postcode_originalera '00000' e foi corrigido para um valor real (ex: '41000'). - Correção de Postcodes Reais (Minoria): 1.890.341 conflitos (10,8% dos conflitos) foram casos em que o
postcode_originalparecia válido (ex: '41000'), mas a análise espacial provou que estava errado e o corrigiu para o valor do polígono (ex: '41001').
As métricas da seção 7.3 foram derivadas das seguintes consultas SQL, executadas na tabela produto_final_staging após a conclusão do script 04_enrich_staging_table.sql.
-- Total de pontos na tabela final
select count(*) from produto_final_staging;
-- Resultado: 31236822
-- 1. QUALIDADE DOS DADOS ORIGINAIS
-- Postcodes nulos na origem
select count(*) from produto_final_staging where postcode_original is null;
-- Resultado: 4
-- Postcodes '00000' na origem
select count(*) from produto_final_staging where postcode_original ~ '^[0]+$';
-- Resultado: 15646607
-- 2. RESULTADOS DA VALIDAÇÃO
-- Pontos Órfãos (não caíram em nenhum polígono)
select count(*) from produto_final_staging where postcode_validado is null;
-- Resultado: 70505
-- Correspondências Perfeitas (Original = Validado)
select count(*) from produto_final_staging where flag_postcode_match ='t';
-- Resultado: 13656144
-- Total de Conflitos (Original != Validado)
select count(*) from produto_final_staging where flag_postcode_match ='f';
-- Resultado: 17510171
-- 3. DETALHAMENTO DOS CONFLITOS
-- Conflitos que eram '00000' (Zeros Corrigidos)
select count(*) from produto_final_staging where postcode_original ~ '^[0]+$' and flag_postcode_match = 'f';
-- Resultado: 15619830
-- (Nota: 15.646.607 (Total Zeros) - 26.777 (Zeros Órfãos) = 15.619.830)
-- Conflitos que eram postcodes "reais" (Não-Zeros Corrigidos)
select count(*) from produto_final_staging where postcode_original !~ '^[0]+$' and flag_postcode_match ='f';
-- Resultado: 1890341