Webs de sombras solares de Madrid con datos abiertos
When to Use
- Al construir u optimizar una web que calcula sol/sombra por terraza o punto en Madrid (tipo SolMAD).
- Al cruzar datos de terrazas/edificios del Ayto. de Madrid con OSM/IGN para un mapa Leaflet/MapLibre.
- Al optimizar el rendimiento de un raycast de sombras en Web Worker (cache por slot, "los días son iguales").
Clase de trabajo: construir/optimizar mapas que calculan sol/sombra por terraza o punto en Madrid, usando datos abiertos del Ayto. de Madrid + OSM + IGN, render Leaflet/MapLibre con Web Worker para el raycast. Proyecto referencia: SolMAD (Ntizar/solmad).
Fuentes de datos oficiales (verificadas)
| Fuente |
Qué da |
Acceso |
| Censo de terrazas del Ayto. |
6.2k terrazas abiertas, coords EPSG:25830, Superficie_ES/RA, mesas_es, superficie, id_terraza |
datos.madrid.es (dataset terrazas) |
| Alturas de edificios |
altura por polígono + geometría (rings WGS84), CC BY 4.0 |
sigma.madrid.es/hosted/rest/services/CARTOGRAFIA/EDIFICIOS_ALTURAS/MapServer/0/query |
| Modelo 3D LOD2 |
edificios extruidos 1:1000, SLPK/OBJ por distritos |
Geoportal Madrid (dataset "Modelo tridimensional") |
| IGN WMTS |
mapa base gratuito CC BY 4.0 |
ign.es/wmts/ign-base (ver skill ign-wmts-tiles) |
| OSM Overpass |
edificios/viario en vivo |
overpass-api.de |
Pitfall #1 — El punto del censo NO es la acera
coordenada_x_local/y de una terraza del Ayto. (EPSG:25830) está en la fachada del edificio, NO sobre la acera. Usarlo como centro de la huella deja las terrazas DENTRO de los edificios (el usuario lo detecta al instante: "La has liado con las terrazas").
- Para poner la huella en la acera: proyectar el rectángulo perpendicular a la calle, O ponerla a ~1.5 m del eje de la vía (offset de acera). NO a 5 m del eje (cae en la zona de coches).
- El crudo trae
id_terraza pero terrazas.min.json expone id. Revisar el campo real antes de cruzar con huellas.
Pitfall #2 — Bucle de paginación del ArcGIS
El servicio de alturas pagina a 2000 (exceededTransferLimit). Un tile de ~1.3 km tiene >2000 edificios → 2+ páginas. Si fetchTile no corta el loop (con tope de páginas + timeout + abort), satura el worker y Safari se cuelga ("ha generado problemas repetidamente"). Para fetch en vivo es más estable Overpass; usar el ArcGIS solo paginando con tope/retry/timeout, o precomputar en build.
- bbox del ArcGIS en WGS84 directo:
inSR=4326&outSR=4326, geometry "w,s,e,n". NO convertir a EPSG:25830 a mano → desfase ~1.5 km y el matching nunca acierta.
- User-Agent custom obligatorio en la petición (403 sin él).
- Madrid capital tiene ~492k edificios; descargar todo en runtime (246 requests) es inviable → precomputar en build o por tiles de zona, simplificando geometría a ~8 vértices (basta para la sombra).
Rendimiento — cache por slot horario ("los días son iguales")
El sol se mueve ~1°/día, así que el mismo slot de días cercanos da el mismo sol/sombra. Cachear el estado por (terrazaId, ymd, slot de 30 min):
- Computar solo el slot pedido (4 raycasts la 1ª vez), guardar en
Map<terrazaId, {ymd, states: Uint8Array[48]}>. Al mover el slider → lookup O(1).
- Medido en SolMAD: 1ª llamada 1.13ms → 2ª (mismo slot) 0.01ms (100x).
- Sentinel "no computado" = 255, NUNCA 3 (3 es un estado válido: pendiente). Si usas 3 como K crees que sigue pendiente y recalculas forever.
- Invalidar cache en
setBuildings (al cambiar edificios los raycasts quedan obsoletos, sobre todo los marcados pendiente).
- Cache por ángulo cuantizado a 0.5° solo "pega" si se repite exacta la combinación az/al — al mover el slider casi nunca pasa. La cache por slot es la que hace instantáneo el drag.
Workflow — verificación visual ANTES de desplegar
Cambiar geometría de terrazas/sombras a ciegas y desplegar solo con tsc+build NO es suficiente: rompe la UX y el usuario lo ve al vuelo.
- Validar con verificación visual real (captura de navegador / screenshot) y/o script de medición de solapes sobre el JSON real ANTES de commitear.
- Guarda el SHA del estado estable. Para revertir solo lo que se lió conservando lo bueno:
git checkout <sha> -- <archivos>. SolMAD estable = commit 07e6ad6.
- Los "solapes" de terrazas casi nunca son un error: son clusters (
leaflet.markercluster, círculos con nº) que agrupan terrazas del mismo bloque; se separan al hacer zoom. Con datos reales, la mayoría de pares <3 m son mesas contiguas legítimas — no moverlas.
- Atribución básica:
Hecho con ♥ por David Antizar · datos OSM · Madrid Abierto; licencias CC BY 4.0 → atribución obligatoria.
Estados del estado solar (convención)
0=sombra · 1=sol · 2=noche · 3=pendiente (sin edificios) · 255=sin dato. Mantener esta convención en todo el pipeline (motor, cache, UI) para no mezclar "pendiente" con "sin dato".
Referencias
references/solmad-detalle-sesion.md — detalle de la sesión SolMAD: qué se rompió y por qué, red de datos de terrazas (74 con (0,0), rangos válidos), medición de la cache por slot, SHA estable 07e6ad6.
1---2name: madrid-solar-shadow-geodata3description: Usa al construir webs de sombras solares de Madrid.4license: CC BY 4.05---67# Webs de sombras solares de Madrid con datos abiertos89## When to Use1011- Al construir u optimizar una web que calcula sol/sombra por terraza o punto en Madrid (tipo SolMAD).12- Al cruzar datos de terrazas/edificios del Ayto. de Madrid con OSM/IGN para un mapa Leaflet/MapLibre.13- Al optimizar el rendimiento de un raycast de sombras en Web Worker (cache por slot, "los días son iguales").1415Clase de trabajo: construir/optimizar mapas que calculan sol/sombra por terraza o punto en Madrid, usando datos abiertos del Ayto. de Madrid + OSM + IGN, render Leaflet/MapLibre con Web Worker para el raycast. Proyecto referencia: SolMAD (`Ntizar/solmad`).1617## Fuentes de datos oficiales (verificadas)1819| Fuente | Qué da | Acceso |20|---|---|---|21| **Censo de terrazas del Ayto.** | 6.2k terrazas abiertas, coords EPSG:25830, `Superficie_ES/RA`, `mesas_es`, `superficie`, `id_terraza` | `datos.madrid.es` (dataset terrazas) |22| **Alturas de edificios** | altura por polígono + geometría (rings WGS84), CC BY 4.0 | `sigma.madrid.es/hosted/rest/services/CARTOGRAFIA/EDIFICIOS_ALTURAS/MapServer/0/query` |23| **Modelo 3D LOD2** | edificios extruidos 1:1000, SLPK/OBJ por distritos | Geoportal Madrid (dataset "Modelo tridimensional") |24| **IGN WMTS** | mapa base gratuito CC BY 4.0 | `ign.es/wmts/ign-base` (ver skill `ign-wmts-tiles`) |25| **OSM Overpass** | edificios/viario en vivo | `overpass-api.de` |2627## Pitfall #1 — El punto del censo NO es la acera2829`coordenada_x_local/y` de una terraza del Ayto. (EPSG:25830) está en la **fachada del edificio**, NO sobre la acera. Usarlo como centro de la huella deja las terrazas **DENTRO de los edificios** (el usuario lo detecta al instante: "La has liado con las terrazas").3031- Para poner la huella en la acera: proyectar el rectángulo perpendicular a la calle, O ponerla a ~1.5 m del eje de la vía (offset de acera). **NO a 5 m del eje** (cae en la zona de coches).32- El crudo trae `id_terraza` pero `terrazas.min.json` expone `id`. Revisar el campo real antes de cruzar con huellas.3334## Pitfall #2 — Bucle de paginación del ArcGIS3536El servicio de alturas pagina a **2000** (`exceededTransferLimit`). Un tile de ~1.3 km tiene >2000 edificios → 2+ páginas. Si `fetchTile` no corta el loop (con tope de páginas + timeout + abort), satura el worker y **Safari se cuelga** ("ha generado problemas repetidamente"). Para fetch en vivo es más estable **Overpass**; usar el ArcGIS solo paginando con tope/retry/timeout, o precomputar en build.3738- **bbox del ArcGIS en WGS84 directo**: `inSR=4326&outSR=4326`, geometry `"w,s,e,n"`. NO convertir a EPSG:25830 a mano → desfase ~1.5 km y el matching nunca acierta.39- **User-Agent custom obligatorio** en la petición (403 sin él).40- Madrid capital tiene **~492k edificios**; descargar todo en runtime (246 requests) es inviable → precomputar en build o por tiles de zona, simplificando geometría a ~8 vértices (basta para la sombra).4142## Rendimiento — cache por slot horario ("los días son iguales")4344El sol se mueve ~1°/día, así que el mismo slot de días cercanos da el mismo sol/sombra. Cachear el estado por `(terrazaId, ymd, slot de 30 min)`:4546- Computar **solo el slot pedido** (4 raycasts la 1ª vez), guardar en `Map<terrazaId, {ymd, states: Uint8Array[48]}>`. Al mover el slider → **lookup O(1)**.47- **Medido en SolMAD: 1ª llamada 1.13ms → 2ª (mismo slot) 0.01ms (100x)**.48- **Sentinel "no computado" = 255, NUNCA 3** (3 es un estado válido: pendiente). Si usas 3 como K crees que sigue pendiente y recalculas forever.49- **Invalidar cache en `setBuildings`** (al cambiar edificios los raycasts quedan obsoletos, sobre todo los marcados pendiente).50- Cache por ángulo cuantizado a 0.5° solo "pega" si se repite exacta la combinación az/al — al mover el slider casi nunca pasa. La cache **por slot** es la que hace instantáneo el drag.5152## Workflow — verificación visual ANTES de desplegar5354Cambiar geometría de terrazas/sombras a ciegas y desplegar solo con `tsc`+build **NO es suficiente**: rompe la UX y el usuario lo ve al vuelo.55561. Validar con **verificación visual real** (captura de navegador / screenshot) y/o **script de medición de solapes sobre el JSON real** ANTES de commitear.572. Guarda el SHA del estado estable. Para revertir solo lo que se lió conservando lo bueno: `git checkout <sha> -- <archivos>`. SolMAD estable = commit `07e6ad6`.583. Los "solapes" de terrazas casi nunca son un error: son **clusters** (`leaflet.markercluster`, círculos con nº) que agrupan terrazas del mismo bloque; se separan al hacer zoom. Con datos reales, la mayoría de pares <3 m son mesas contiguas legítimas — no moverlas.594. Atribución básica: `Hecho con ♥ por David Antizar · datos OSM · Madrid Abierto`; licencias CC BY 4.0 → atribución obligatoria.6061## Estados del estado solar (convención)6263`0=sombra · 1=sol · 2=noche · 3=pendiente (sin edificios) · 255=sin dato`. Mantener esta convención en todo el pipeline (motor, cache, UI) para no mezclar "pendiente" con "sin dato".6465## Referencias6667- `references/solmad-detalle-sesion.md` — detalle de la sesión SolMAD: qué se rompió y por qué, red de datos de terrazas (74 con (0,0), rangos válidos), medición de la cache por slot, SHA estable `07e6ad6`.