Timelapse 3D · 100 % local

La historia de la ciudad, creciendo en el mapa

Desde el primer poblado hasta hoy: la ciudad nace y crece en 3D, con año, población, bandera de cada época y tarjetas con fuente. Sin IA de video y sin costo de render.

Banner: Historia en 3D
En resumen

historia-timelapse es un conjunto de scripts que convierte la historia de una ciudad en un video en 3D, en timelapse. Usa el relieve real del terreno y los edificios de OpenStreetMap: cada edificio "nace" en el año en que se ocupó su barrio, y la ciudad crece en pantalla junto con el contador de población del IBGE. Los acontecimientos aparecen en tarjetas con número, explicación y fuente. Para usarlo necesitas una computadora con tarjeta de video, Python y Node, y una investigación histórica de la ciudad.

Videos

Los videos

Hechos con este motor, en el canal Aventuras INEMA (en portugués). Cada ciudad tiene versión sin narración y narrada.

Florianópolis — de Desterro a capital

6 min: sambaquíes, Dias Velho, fortalezas, azorianos, la invasión española de 1777, los puentes y los rellenos. Narrada: aquí. Abrir en YouTube

Río de Janeiro — de 1500 a la metrópoli

7 min: tupinambás, Francia Antártica, la Corte en 1808, Imperio y República. Narrada: aquí. Abrir en YouTube

Itaguaí — de la aldea al puerto

6 min: de la aldea jesuita a la hacienda de Santa Cruz y al puerto. Narrada: aquí. Abrir en YouTube

Centro de Itaguaí — las calles, de cerca

5 min, con la cámara en las calles y los edificios históricos. Narrada: aquí. Abrir en YouTube

Porto Alegre con luz real (Mitsuba)

1min35, narrado: el mismo Porto Alegre con relieve y edificios iluminados por path tracing en la GPU — sombras reales y edificios que suben en el año del barrio. Abrir en YouTube

Porto Alegre — 1772 a 2024

1min35, narrado (el piloto): fundación, sitios farrapos, la inundación de 1941, el Muro de Mauá y la de 2024. Abrir en YouTube

Qué es

Un motor de historia en mapa 3D

Datos reales en lugar de imágenes inventadas.

Banner con los recursos de historia-timelapse

🏙️ La ciudad nace año a año

Relieve Copernicus de 30 m y edificios de OpenStreetMap (con la base abierta de Microsoft donde el OSM tiene poco). Cada edificio recibe un año según su barrio y la curva de población.

📜 Tarjetas con fuente

Cada evento tiene título, número grande, explicación y fuente. Año, población (censos del IBGE) y bandera del régimen cambian en la esquina de la pantalla.

🎬 Efectos de historia

Globo con zoom hasta la ciudad, línea de tiempo, flotas navegando, sitios, fortalezas, puentes dibujados, rellenos que crecen en color e inundaciones que suben.

Cómo funciona

Del dato al video

Todo corre en tu máquina: deck.gl en Chrome con la GPU, cerca de 0,1 s por cuadro en 1080p.

Investigación histórica→ OSM + relieve→ Año de cada edificio→ Guion (bloques)→ Escena→ Render + música
Apertura con el globo
Apertura: globo con zoom hasta la ciudad
Fortaleza de Anhatomirim destacada
Fortaleza de Anhatomirim (1739) destacada
Florianópolis en 1961
Florianópolis en 1961: nace la UFSC
Porto Alegre durante la inundación de 2024
Porto Alegre, mayo de 2024: el agua vuelve al Centro
Requisitos previos

Qué necesitas

Linux con tarjeta de video, Python 3, Node 20+ y ffmpeg. Los datos son archivos públicos descargados por un script; no hay API ni costo.

Programas

Python, Node, ffmpeg y git.

sudo apt install python3-venv ffmpeg git
# Node 20+: con nvm (nvm install 20) o NodeSource — el nodejs de apt es antiguo

Tarjeta de video

El render usa Chrome con la GPU (Vulkan). Sin Vulkan, ejecuta con GL="--use-angle=gl" antes del comando (más lento).

# ~0,1 s por cuadro en 1080p en una GPU NVIDIA

Espacio en disco

El recorte regional del OSM pesa cientos de MB (el Sur de Brasil, ~430 MB). Cada bloque de relieve pesa 20–50 MB.

# todo queda en la carpeta dl/ (fuera de git)
Guía de uso · paso a paso

Haz el video de tu ciudad

Cada ciudad vive en cidades/<id>/. Los scripts leen la ciudad mediante la variable CIDADE. Los ejemplos usan Florianópolis (floripa): cámbialo por el id de tu ciudad en todos los comandos y mantén export CIDADE=<id> en la terminal (sin él los scripts usan poa).

0

Descarga el proyecto e instala

Crea el entorno Python (venv = una carpeta con las bibliotecas solo de este proyecto) e instala Playwright, que abre Chrome para grabar los cuadros.

git clone https://github.com/inematds/historia-timelapse && cd historia-timelapse
python3 -m venv .venv
.venv/bin/pip install osmium tifffile imagecodecs \
    shapely numpy pyproj scipy pillow
cd motor && npm i && npx playwright install --with-deps chromium && cd ..
1

Crea la configuración de la ciudad

bbox es el rectángulo del mapa en grados: oeste, sur, este, norte (puedes dibujarlo en bboxfinder.com). municipio_osm es el nombre exacto del municipio en OpenStreetMap. centro_antigo es el punto donde empezó la ciudad.

# cidades/floripa/config.json
{ "nome": "Florianópolis", "bbox": [-48.80, -27.92, -48.33, -27.33],
  "pbf": "dl/sul.osm.pbf",
  "pbf_url": "https://download.geofabrik.de/south-america/brazil/sul-latest.osm.pbf",
  "municipio_osm": "Florianópolis", "centro_antigo": [-48.5490, -27.5965],
  "pais_microsoft": "Brazil" }
# opcionales: "efeitos" (puentes, fuertes, rellenos), "tex" (tamaño de la textura),
# "agua_baixa_m", "pop_modelo_extra" — mira cidades/floripa/config.json
2

Descarga los datos públicos

El script descarga el recorte del OSM (formato .pbf), los bloques de relieve que cubren tu bbox y, con --microsoft, los edificios de Microsoft para donde el OSM tiene pocos edificios.

export CIDADE=floripa
.venv/bin/python -I scripts/baixar_dados.py --microsoft
3

Haz la investigación histórica

En cidades/<id>/dados/pesquisa/ van tres archivos. Copia los de Florianópolis como modelo y cambia el contenido.

# populacao.json — censos y estimaciones
{ "serie": [ { "ano": 1872, "pop": 25709, "fonte_url": "https://..." } ] }
# eventos.json — un evento por ítem, con coordenada y fuente
{ "eventos": [ { "id": "ponte-hercilio", "ano": 1926, "titulo_curto_PT": "A PONTE HERCÍLIO LUZ",
    "legenda": "...", "detalhe": "...", "numero_impacto": "821 METROS",
    "local": { "lat": -27.5935, "lon": -48.5649 }, "fontes": ["https://..."] } ] }
# bairros_epocas.json — año en que cada barrio empezó a ser ocupado
{ "bairros": [ { "bairro": "Trindade", "ano_aprox_ocupacao": 1960 } ] }
4

Prepara el mapa

Recorta edificios, agua y barrios, arma el relieve con el mar, da a cada edificio un año de nacimiento y prepara puentes, fuertes y rellenos.

.venv/bin/python -I scripts/extrai_osm.py   # usa el "pbf" del config
.venv/bin/python -I scripts/extrai_msbuild.py   # solo si descargaste los edificios de Microsoft
.venv/bin/python -I scripts/prep_terreno.py
.venv/bin/python -I scripts/prep_predios.py
.venv/bin/python -I scripts/modelo_anos.py
.venv/bin/python -I scripts/extrai_feicoes.py
.venv/bin/python -I scripts/prep_efeitos.py
5

Escribe el guion y genera la escena

roteiro.json es un objeto con la lista "blocos" (y, si los usas, "linhas", "fortes", "areas" y "regimes"). Para una ciudad nueva escribes este archivo a mano, o copias y adaptas cidades/floripa/roteiro_floripa.py, que lo arma a partir de eventos.json. Cada bloque tiene los años (inicio, año del evento, fin), la cámara de salida y de llegada, la duración y la tarjeta. En Florianópolis lo arma roteiro_floripa.py a partir de la investigación; úsalo como modelo.

# un bloque del roteiro.json
{ "id": "ponte-hercilio", "anos": [1895, 1926, 1927], "pulo": 2.0, "dur": 9.5,
  "cam": [ {"lon": -48.552, "lat": -27.600, "zoom": 14, "pitch": 58, "bearing": -40},
           {"lon": -48.570, "lat": -27.602, "zoom": 13.7, "pitch": 60, "bearing": -70} ],
  "cartao": { "numero": "821 METROS", "titulo": "A ponte Hercílio Luz",
              "legenda": "...", "detalhe": "...", "fonte": "IBGE" },
  "destaca": ["linha:hercilio"] }
# anos: [inicio, año del evento, fin]; "pulo": segundos en que el tiempo corre hasta el año del evento
# cam: lon/lat del centro, zoom (12 = barrio, 14 = manzana), pitch (inclinación 0–60), bearing (rumbo en grados)
# "destaca": "linha:<id>" | "forte:<id>" | "area:<id>" — ids definidos en "efeitos" del config y listados al inicio del guion
# ajusta la cámara mirando los cuadros del paso 6
python3 cidades/floripa/roteiro_floripa.py
.venv/bin/python -I scripts/gerar_cena.py   # genera cidades/floripa/cena/cena.js
6

Revisa algunos cuadros

Graba solo los segundos indicados, para revisar encuadre, tarjetas y efectos antes del video completo.

cd motor && node render.mjs ../cidades/floripa/cena --quadros 20,60,120 && cd ..
# las imágenes q_*.jpg quedan en cidades/floripa/cena/
7

Renderiza, pon la música y une

El render se hace en tramos de 60 s (el navegador se reinicia entre ellos, para que no falte memoria). Pon tus músicas instrumentales (.mp3) en cidades/floripa/musica/; suenan en orden alfabético, en bucle.

bash motor/render_trechos.sh cidades/floripa/cena cidades/floripa/final/video.mp4
.venv/bin/python -I scripts/montar_musica.py   # cidades/floripa/final/audio.wav
ffmpeg -i cidades/floripa/final/video.mp4 -i cidades/floripa/final/audio.wav \
  -map 0:v -map 1:a -c:v copy -c:a aac -shortest cidades/floripa/final/floripa.mp4
8

Versión narrada (opcional)

La narración es una capa de audio sobre el mismo video: un texto por bloque en cidades/<id>/narracao.json, encajado al inicio de cada evento. La grabación usa un motor de voz local del autor (cardshorts, fuera de este repositorio); con otra voz, graba un archivo por bloque y completa narracao/tempos.json.

# narracao/tempos.json, si grabas con otra voz: {"<bloque>": {"dur": 6.2, "wav": "ruta.wav", "texto": "...", "ouvido": "..."}}
python3 scripts/narrar.py          # solo con cardshorts; con otra voz, sáltalo y llena tempos.json
python3 scripts/montar_narrada.py   # final/audio-narrado.wav
ffmpeg -i cidades/floripa/final/video.mp4 -i cidades/floripa/final/audio-narrado.wav -map 0:v -map 1:a -c:v copy -c:a aac -shortest cidades/floripa/final/floripa-narrado.mp4
9

Render con luz real (opcional, GPU NVIDIA)

El mismo video con el relieve y los edificios iluminados por path tracing en Mitsuba 3: sombras entre edificios, aspecto de maqueta y edificios que suben en su año. El motor sigue dibujando encima el HUD, las tarjetas y los efectos. Unos 45 min para 95 s de video (el render normal, ~5 min). Detalles (en portugués): docs/RENDER-LUZ-REAL.md.

Los dos formatos y Blender (mismo guion, mismas tarjetas y efectos; solo cambian el relieve y los edificios):

  • Estándar (deck.gl en Chrome): 0,07–0,11 s por cuadro. Porto Alegre (95 s) en ~5 min, Florianópolis (6 min) en ~17 min. Es el de todo video y toda revisión.
  • Luz real (Mitsuba + motor encima): ~0,75–0,95 s por cuadro. Porto Alegre en ~45 min, Florianópolis en ~3 h (estimado). Versión destacada, después de aprobar la estándar.
  • Blender (no usado): aquí corre solo por procesador, ~40 s por cuadro. Porto Alegre tardaría ~32 h y Florianópolis ~5 días. No hay Blender oficial con GPU para Linux ARM64.
.venv/bin/pip install mitsuba==3.9.1 drjit==1.5.0 mapbox-earcut
bash motor/render_luz.sh cidades/floripa/cena cidades/floripa/final/floripa-mitsuba-video.mp4 64
Roadmap

Próximos pasos

Lo que ya existe y lo que viene.

Hecho
Porto Alegre e FlorianópolisMotor con globo, línea de tiempo, tarjetas con fuente, inundaciones, sitios, fortalezas, puentes y rellenos.
Nuevo
Luz real con Mitsuba (GPU)Sombras y luz de maqueta por path tracing. Comparación medida: deck.gl × Blender × Mitsuba (en portugués).
Próximo
Versión con imágenes antiguasGrabados y fotos históricas de dominio público entrando junto con los eventos.
Después
Más ciudades y skillConvertir el flujo en una skill para hacer una ciudad nueva solo con la investigación.