refactor(mapa): simplificat de 15 a 3 capes (dropdown -> botons grans)
L'usuaria no entenia el mapa: 15 variables, 2 capes redundants
(consumo_p10_muy_bajo + consumo_p10_bajo pintaven el MATEIX camp), i
noms quasi-iguals ('Cua de baix consum' vs 'Cua de consum baix').
Decidim tornar a 0.
3 capes simples, 3 botons grans (no dropdown):
1. 'Habitatges totals' (capa original)
2. 'Possibles vacants' (renova 4 capes consumo + viviendas_vacantes,
pintant consumo_p10_kwh amb semafor vermell)
3. 'Pressio turistica' (capa composta nova: airbnb_listings a
Mallorca/Menorca + hut_plazas a Eivissa,
pintades simultaniament)
Treu:
- 14 entrades de variableOptions
- 13 entrades de LAYERS
- 18 claus i18n obsoletes (group_consumo/presion/hut/avanzado +
variables buides)
- El format dropdown + optgroup
AFEGEIX:
- HTML <button> amb aria-pressed (estil actiu automatic via <style>)
- Listener click en lloc de change event
- Llegenda custom per 'pressio_turistica' amb 2 mini-barres
(groc-taronya Airbnb, blau HUT) perque la capa composta no
es pot representar amb un sol degradat
- formatTick especial per a possibles_vacants (kWh)
Net: -404 linies a Mapa.astro, -42 a cada i18n (-488 total).
astro-check: 0 errors nous a Mapa.astro.
This commit is contained in:
+149
-263
@@ -12,26 +12,10 @@ interface VariableOption {
|
||||
}
|
||||
|
||||
const variableOptions: VariableOption[] = [
|
||||
// Grupo 1: Vivienda (INE Censo 2021)
|
||||
// 3 capas simples. Sin sub-grupos.
|
||||
{ value: "viviendas_totales", labelKey: "map.variable_viviendas_totales", descKey: "map.variable_viviendas_totales_desc", scope: "all", groupKey: "map.group_vivienda" },
|
||||
{ value: "viviendas_vacantes", labelKey: "map.variable_viviendas_vacantes", descKey: "map.variable_viviendas_vacantes_desc", scope: "all", groupKey: "map.group_vivienda" },
|
||||
// Grupo 2: Consumo eléctrico
|
||||
{ value: "consumo_p10_muy_bajo", labelKey: "map.variable_consumo_p10_muy_bajo", descKey: "map.variable_consumo_p10_muy_bajo_desc", scope: "all", groupKey: "map.group_consumo" },
|
||||
{ value: "consumo_p10_bajo", labelKey: "map.variable_consumo_p10_bajo", descKey: "map.variable_consumo_p10_bajo_desc", scope: "all", groupKey: "map.group_consumo" },
|
||||
{ value: "consumo_p50_medio", labelKey: "map.variable_consumo_p50_medio", descKey: "map.variable_consumo_p50_medio_desc", scope: "all", groupKey: "map.group_consumo" },
|
||||
{ value: "consumo_p90_alto", labelKey: "map.variable_consumo_p90_alto", descKey: "map.variable_consumo_p90_alto_desc", scope: "all", groupKey: "map.group_consumo" },
|
||||
// Grupo 3: Presión Airbnb
|
||||
{ value: "airbnb_listings", labelKey: "map.variable_airbnb_listings", descKey: "map.variable_airbnb_listings_desc", scope: "airbnb", groupKey: "map.group_presion" },
|
||||
{ value: "airbnb_entire_homes", labelKey: "map.variable_airbnb_entire", descKey: "map.variable_airbnb_entire_desc", scope: "airbnb", groupKey: "map.group_presion" },
|
||||
{ value: "airbnb_revenue_total", labelKey: "map.variable_airbnb_revenue", descKey: "map.variable_airbnb_revenue_desc", scope: "airbnb", groupKey: "map.group_presion" },
|
||||
{ value: "airbnb_per_1000", labelKey: "map.variable_airbnb_per_1000", descKey: "map.variable_airbnb_per_1000_desc", scope: "airbnb", groupKey: "map.group_presion" },
|
||||
// Grupo 4: Vivienda turística legal (HUT)
|
||||
{ value: "hut_registros", labelKey: "map.variable_hut_registros", descKey: "map.variable_hut_registros_desc", scope: "hut", groupKey: "map.group_hut" },
|
||||
{ value: "hut_plazas", labelKey: "map.variable_hut_plazas", descKey: "map.variable_hut_plazas_desc", scope: "hut", groupKey: "map.group_hut" },
|
||||
// Grupo 5: Avanzado
|
||||
{ value: "airbnb_con_licencia", labelKey: "map.variable_airbnb_con_licencia", descKey: "map.variable_airbnb_con_licencia_desc", scope: "airbnb", groupKey: "map.group_avanzado" },
|
||||
{ value: "ratio_p10_p50_consumo", labelKey: "map.variable_ratio_p10_p50", descKey: "map.variable_ratio_p10_p50_desc", scope: "all", groupKey: "map.group_avanzado" },
|
||||
{ value: "consumo_p50_kwh", labelKey: "map.variable_consumo_p50", descKey: "map.variable_consumo_p50_desc", scope: "all", groupKey: "map.group_avanzado" },
|
||||
{ value: "possibles_vacants", labelKey: "map.variable_possibles_vacants", descKey: "map.variable_possibles_vacants_desc", scope: "all", groupKey: "map.group_vivienda" },
|
||||
{ value: "pressio_turistica", labelKey: "map.variable_pressio_turistica", descKey: "map.variable_pressio_turistica_desc", scope: "all", groupKey: "map.group_vivienda" },
|
||||
];
|
||||
|
||||
const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
@@ -66,29 +50,40 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
class="absolute top-3 left-3 z-20 bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm p-3 w-64 max-w-[calc(100vw-1.5rem)]"
|
||||
>
|
||||
<div class="mb-2">
|
||||
<label for="variable-select" class="block text-xs font-medium text-stone-700 mb-1">
|
||||
<p class="text-xs font-medium text-stone-700 mb-2">
|
||||
{t(lang, "map.variable_label")}
|
||||
</label>
|
||||
<select
|
||||
id="variable-select"
|
||||
class="w-full text-sm border border-stone-300 rounded px-2 py-1 bg-white"
|
||||
>
|
||||
{(() => {
|
||||
const groups = Array.from(new Set(variableOptions.map((o) => o.groupKey)));
|
||||
return groups.map((g) => (
|
||||
<optgroup label={t(lang, g)}>
|
||||
{variableOptions
|
||||
.filter((o) => o.groupKey === g)
|
||||
.map((o) => (
|
||||
<option value={o.value} data-scope={o.scope}>{t(lang, o.labelKey)}</option>
|
||||
))}
|
||||
</optgroup>
|
||||
));
|
||||
})()}
|
||||
</select>
|
||||
<p id="variable-desc" class="text-[10px] text-stone-500 mt-1 leading-tight"></p>
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
{variableOptions.map((o) => (
|
||||
<button
|
||||
type="button"
|
||||
data-variable={o.value}
|
||||
data-scope={o.scope}
|
||||
class="variable-btn w-full text-left border border-stone-300 rounded px-3 py-2 bg-white hover:bg-stone-50 active:bg-stone-100 transition-colors"
|
||||
aria-pressed="false"
|
||||
>
|
||||
<span class="font-medium text-sm text-stone-900 block">{t(lang, o.labelKey)}</span>
|
||||
<span class="text-[11px] text-stone-500 block mt-0.5 leading-snug">{t(lang, o.descKey)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p id="variable-desc" class="text-[10px] text-stone-500 mt-2 leading-tight"></p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.variable-btn[aria-pressed="true"] {
|
||||
background: #1c1917;
|
||||
border-color: #1c1917;
|
||||
color: #fafaf9;
|
||||
}
|
||||
.variable-btn[aria-pressed="true"] span {
|
||||
color: inherit;
|
||||
}
|
||||
.variable-btn[aria-pressed="true"] .text-stone-500 {
|
||||
color: #d6d3d1 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="border-t border-stone-200 pt-2">
|
||||
<p class="text-xs font-medium text-stone-700 mb-1">
|
||||
{t(lang, "map.layer_label")}
|
||||
@@ -134,18 +129,15 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
listings: "Listings Airbnb",
|
||||
entire_homes: lang === "es" ? "Pisos enteros" : "Pisos sencers",
|
||||
revenue: lang === "es" ? "Ingresos L365d" : "Ingressos L365d",
|
||||
con_licencia: lang === "es" ? "Con licencia Airbnb" : "Amb llicència Airbnb",
|
||||
hut_registros: lang === "es" ? "Registros HUT" : "Registres HUT",
|
||||
hut_plazas: lang === "es" ? "Plazas HUT" : "Places HUT",
|
||||
viviendas_totales: lang === "es" ? "Viviendas totales" : "Habitatges totals",
|
||||
consumo_p50: lang === "es" ? "Mediana consumo kWh" : "Mediana consum kWh",
|
||||
viviendas_vacantes: lang === "es" ? "Viv. vacantes (estim.)" : "Hab. vacants (estim.)",
|
||||
consumo_p10: lang === "es" ? "Consumo p10 (kWh)" : "Consum p10 (kWh)",
|
||||
consumo_p10_muy_bajo: lang === "es" ? "Cola bajo consumo (p10)" : "Cua de baix consum (p10)",
|
||||
viviendas_vacantes_label: lang === "es" ? "Viv. vacantes" : "Hab. vacants",
|
||||
consumo_p10_bajo: lang === "es" ? "Cola consumo bajo (p10)" : "Cua de consum baix (p10)",
|
||||
consumo_p50_medio: lang === "es" ? "Consumo medio (p50)" : "Consum mitjà (p50)",
|
||||
consumo_p90_alto: lang === "es" ? "Cola consumo alto (p90)" : "Cua de consum alt (p90)",
|
||||
airbnb_per_1000: lang === "es" ? "Airbnb / 1000 viv." : "Airbnb / 1000 hab.",
|
||||
consumo_p50: lang === "es" ? "Mediana consumo kWh" : "Mediana consum kWh",
|
||||
ratio_p10_p50: lang === "es" ? "Ratio p10/p50" : "Ratio p10/p50",
|
||||
possibles_vacants: lang === "es" ? "Posibles vacantes" : "Possibles vacants",
|
||||
pressio_turistica: lang === "es" ? "Presión turística" : "Pressió turística",
|
||||
granularitat: lang === "es" ? "Granularidad" : "Granularitat",
|
||||
seccion_censal: lang === "es" ? "sección censal" : "secció censal",
|
||||
municipio: "municipi",
|
||||
@@ -155,50 +147,30 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
legend_islands_mm: lang === "es" ? "Mallorca / Menorca (sección censal)" : "Mallorca / Menorca (secció censal)",
|
||||
legend_islands_ef: lang === "es" ? "Ibiza / Formentera (municipio)" : "Eivissa / Formentera (municipi)",
|
||||
legend_islands_all_mun: lang === "es" ? "Todas (municipio)" : "Totes (municipi)",
|
||||
legend_islands_all_dis: lang === "es" ? "Todas (distrito)" : "Totes (districte)",
|
||||
legend_islands_tourism: lang === "es" ? "Airbnb + HUT simultáneos" : "Airbnb + HUT simultanis",
|
||||
};
|
||||
|
||||
const DESC: Record<string, string> = {
|
||||
airbnb_listings:
|
||||
lang === "es"
|
||||
? "Total de listings activos por sección censal"
|
||||
: "Total de listings actius per secció censal",
|
||||
airbnb_entire_homes:
|
||||
lang === "es"
|
||||
? "Listings que son pisos completos — proxy de conversión residencial → turístico"
|
||||
: "Listings que són pisos complets — proxy de conversió residencial → turístic",
|
||||
airbnb_revenue_total:
|
||||
lang === "es"
|
||||
? "Suma estimada de ingresos de los últimos 365 días por sección"
|
||||
: "Suma estimada dels ingressos dels darrers 365 dies per secció",
|
||||
airbnb_con_licencia:
|
||||
lang === "es"
|
||||
? "Listings con son campo `license` rellenado (autorreportado, no validado)"
|
||||
: "Listings amb camp `license` emplenat (autorreportat, no validat)",
|
||||
airbnb_per_1000:
|
||||
lang === "es"
|
||||
? "Listings Airbnb por cada 1000 viviendas del municipio"
|
||||
: "Listings Airbnb per cada 1000 habitatges del municipi",
|
||||
hut_plazas:
|
||||
lang === "es"
|
||||
? "Plazas registradas en HUT del Consell d'Eivissa (municipio)"
|
||||
: "Places registrades al HUT del Consell d'Eivissa (municipi)",
|
||||
hut_registros:
|
||||
lang === "es"
|
||||
? "Número de registros HUT activos (municipio)"
|
||||
: "Nombre de registres HUT actius (municipi)",
|
||||
viviendas_totales:
|
||||
lang === "es"
|
||||
? "Total de viviendas por municipio (Censo 2021, replicado a todas las secciones del municipio)"
|
||||
: "Total d'habitatges per municipi (Cens 2021, replicat a totes les seccions del municipi)",
|
||||
consumo_p50_kwh:
|
||||
possibles_vacants:
|
||||
lang === "es"
|
||||
? "Percentil 50 de consumo eléctrico anual por distrito (Censo 2021)"
|
||||
: "Percentil 50 de consum elèctric anual per districte (Cens 2021)",
|
||||
ratio_p10_p50:
|
||||
? "Secciones donde la cola baja del consumo eléctrico es muy baja (p10). Proxy Holm 2025: cuanto más bajo el p10, más probable que haya viviendas vacías o 2ª residencia."
|
||||
: "Seccions on la cua baixa del consum elèctric és molt baixa (p10). Proxy Holm 2025: com més baix el p10, més probable que hi hagi habitatges buits o 2a residència.",
|
||||
pressio_turistica:
|
||||
lang === "es"
|
||||
? "Cociente entre el percentil 10 y la mediana del consumo eléctrico del distrito (Censo 2021). Bajo (<0.3) = grupo de casas con consumo muy bajo (posibles vacías). Alto (~0.6+) = uso residencial estable."
|
||||
: "Quocient entre el percentil 10 i la mediana del consum elèctric del districte (Cens 2021). Baix (<0.3) = grup de cases amb consum molt baix. Alt (~0.6+) = ús residencial estable.",
|
||||
? "Mallorca y Menorca: listings Airbnb activos (junio 2026). Eivissa: plazas de vivienda turística legal — HUT (julio 2026)."
|
||||
: "Mallorca i Menorca: listings d'Airbnb actius (juny 2026). Eivissa: places d'habitatge turístic legal — HUT (juliol 2026).",
|
||||
airbnb_listings:
|
||||
lang === "es"
|
||||
? "Total de listings activos por sección censal"
|
||||
: "Total de listings actius per secció censal",
|
||||
hut_plazas:
|
||||
lang === "es"
|
||||
? "Plazas registradas en HUT del Consell d'Eivissa (municipio)"
|
||||
: "Places registrades al HUT del Consell d'Eivissa (municipi)",
|
||||
};
|
||||
|
||||
const LAYERS: Record<string, {
|
||||
@@ -211,6 +183,44 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
desc: string;
|
||||
appliesTo: string;
|
||||
}> = {
|
||||
viviendas_totales: {
|
||||
scope: "all",
|
||||
field: "viviendas_totales",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "viviendas_totales"], 0],
|
||||
"#dcfce7", 500, "#bbf7d0", 2000, "#86efac", 5000, "#22c55e", 15000, "#14532d",
|
||||
],
|
||||
stops: [0, 500, 2000, 5000, 15000],
|
||||
colors: ["#dcfce7", "#bbf7d0", "#86efac", "#22c55e", "#14532d", "#14532d"],
|
||||
label: T.viviendas_totales,
|
||||
desc: DESC.viviendas_totales,
|
||||
appliesTo: T.legend_islands_all_mun,
|
||||
},
|
||||
possibles_vacants: {
|
||||
scope: "all",
|
||||
field: "consumo_p10_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p10_kwh"], 0],
|
||||
"#7f1d1d", 600, "#dc2626", 1000, "#f97316", 1500, "#fbbf24", 2200, "#fde68a",
|
||||
],
|
||||
stops: [0, 600, 1000, 1500, 2200],
|
||||
colors: ["#7f1d1d", "#dc2626", "#f97316", "#fbbf24", "#fde68a", "#fde68a"],
|
||||
label: T.possibles_vacants,
|
||||
desc: DESC.possibles_vacants,
|
||||
appliesTo: T.legend_islands_all_mun,
|
||||
},
|
||||
pressio_turistica: {
|
||||
// Capa compuesta: usa los fill-colors de airbnb_listings y hut_plazas
|
||||
// directamente (ver applyVariable). Este entry existe solo para legend/UI.
|
||||
scope: "all",
|
||||
field: "",
|
||||
colorExpr: NO_DATA_COLOR,
|
||||
stops: [0],
|
||||
colors: ["#fde68a", "#7c2d12", "#7dd3fc", "#0c4a6e", "#fde68a", "#0c4a6e"],
|
||||
label: T.pressio_turistica,
|
||||
desc: DESC.pressio_turistica,
|
||||
appliesTo: T.legend_islands_tourism,
|
||||
},
|
||||
airbnb_listings: {
|
||||
scope: "airbnb",
|
||||
field: "airbnb_listings",
|
||||
@@ -224,46 +234,6 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
desc: DESC.airbnb_listings,
|
||||
appliesTo: T.legend_islands_mm,
|
||||
},
|
||||
airbnb_entire_homes: {
|
||||
scope: "airbnb",
|
||||
field: "airbnb_entire_homes",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "airbnb_entire_homes"], 0],
|
||||
"#fef3c7", 1, "#fde68a", 5, "#fbbf24", 30, "#f97316", 100, "#c2410c", 250, "#7c2d12",
|
||||
],
|
||||
stops: [0, 1, 5, 30, 100, 250],
|
||||
colors: ["#fef3c7", "#fde68a", "#fbbf24", "#f97316", "#c2410c", "#7c2d12"],
|
||||
label: T.entire_homes,
|
||||
desc: DESC.airbnb_entire_homes,
|
||||
appliesTo: T.legend_islands_mm,
|
||||
},
|
||||
airbnb_revenue_total: {
|
||||
scope: "airbnb",
|
||||
field: "airbnb_revenue_total",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "airbnb_revenue_total"], 0],
|
||||
"#fef3c7", 100000, "#fde68a", 500000, "#fbbf24",
|
||||
2000000, "#f97316", 5000000, "#c2410c", 10000000, "#7c2d12",
|
||||
],
|
||||
stops: [0, 100000, 500000, 2000000, 5000000, 10000000],
|
||||
colors: ["#fef3c7", "#fde68a", "#fbbf24", "#f97316", "#c2410c", "#7c2d12"],
|
||||
label: T.revenue,
|
||||
desc: DESC.airbnb_revenue_total,
|
||||
appliesTo: T.legend_islands_mm,
|
||||
},
|
||||
airbnb_con_licencia: {
|
||||
scope: "airbnb",
|
||||
field: "airbnb_con_licencia",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "airbnb_con_licencia"], 0],
|
||||
"#fef3c7", 1, "#fde68a", 10, "#fbbf24", 50, "#f97316", 150, "#c2410c", 400, "#7c2d12",
|
||||
],
|
||||
stops: [0, 1, 10, 50, 150, 400],
|
||||
colors: ["#fef3c7", "#fde68a", "#fbbf24", "#f97316", "#c2410c", "#7c2d12"],
|
||||
label: T.con_licencia,
|
||||
desc: DESC.airbnb_con_licencia,
|
||||
appliesTo: T.legend_islands_mm,
|
||||
},
|
||||
hut_plazas: {
|
||||
scope: "hut",
|
||||
field: "hut_plazas",
|
||||
@@ -277,136 +247,6 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
desc: DESC.hut_plazas,
|
||||
appliesTo: T.legend_islands_ef,
|
||||
},
|
||||
hut_registros: {
|
||||
scope: "hut",
|
||||
field: "hut_registros",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "hut_registros"], 0],
|
||||
"#e0f2fe", 1, "#7dd3fc", 200, "#0ea5e9", 1000, "#0369a1", 2000, "#0c4a6e",
|
||||
],
|
||||
stops: [0, 1, 200, 1000, 2000],
|
||||
colors: ["#e0f2fe", "#7dd3fc", "#0ea5e9", "#0369a1", "#0c4a6e", "#0c4a6e"],
|
||||
label: T.hut_registros,
|
||||
desc: DESC.hut_registros,
|
||||
appliesTo: T.legend_islands_ef,
|
||||
},
|
||||
airbnb_per_1000: {
|
||||
scope: "airbnb",
|
||||
field: "airbnb_listings_por_1000_viviendas",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "airbnb_listings_por_1000_viviendas"], 0],
|
||||
"#ede9fe", 5, "#c4b5fd", 20, "#a78bfa", 50, "#8b5cf6", 100, "#6d28d9",
|
||||
],
|
||||
stops: [0, 5, 20, 50, 100],
|
||||
colors: ["#ede9fe", "#c4b5fd", "#a78bfa", "#8b5cf6", "#6d28d9", "#6d28d9"],
|
||||
label: T.airbnb_per_1000,
|
||||
desc: DESC.airbnb_per_1000,
|
||||
appliesTo: T.legend_islands_mm,
|
||||
},
|
||||
viviendas_totales: {
|
||||
scope: "all",
|
||||
field: "viviendas_totales",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "viviendas_totales"], 0],
|
||||
"#dcfce7", 500, "#bbf7d0", 2000, "#86efac", 5000, "#22c55e", 15000, "#14532d",
|
||||
],
|
||||
stops: [0, 500, 2000, 5000, 15000],
|
||||
colors: ["#dcfce7", "#bbf7d0", "#86efac", "#22c55e", "#14532d", "#14532d"],
|
||||
label: T.viviendas_totales,
|
||||
desc: DESC.viviendas_totales,
|
||||
appliesTo: T.legend_islands_all_mun,
|
||||
},
|
||||
viviendas_vacantes: {
|
||||
scope: "all",
|
||||
field: "viviendas_vacantes_estimadas",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "viviendas_vacantes_estimadas"], 0],
|
||||
"#fee2e2", 100, "#fca5a5", 500, "#ef4444", 1500, "#b91c1c", 5000, "#7f1d1d",
|
||||
],
|
||||
stops: [0, 100, 500, 1500, 5000],
|
||||
colors: ["#fee2e2", "#fca5a5", "#ef4444", "#b91c1c", "#7f1d1d", "#7f1d1d"],
|
||||
label: T.viviendas_vacantes,
|
||||
desc: DESC.viviendas_vacantes,
|
||||
appliesTo: T.legend_islands_all_mun,
|
||||
},
|
||||
consumo_p10_muy_bajo: {
|
||||
scope: "all",
|
||||
field: "consumo_p10_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p10_kwh"], 0],
|
||||
"#7f1d1d", 800, "#dc2626", 1200, "#f97316", 1800, "#fbbf24", 2500, "#22c55e",
|
||||
],
|
||||
stops: [0, 800, 1200, 1800, 2500],
|
||||
colors: ["#7f1d1d", "#dc2626", "#f97316", "#fbbf24", "#22c55e", "#22c55e"],
|
||||
label: T.consumo_p10_muy_bajo,
|
||||
desc: DESC.consumo_p10_muy_bajo,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
consumo_p10_bajo: {
|
||||
scope: "all",
|
||||
field: "consumo_p10_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p10_kwh"], 0],
|
||||
"#7f1d1d", 1000, "#dc2626", 1500, "#f97316", 2000, "#fbbf24", 2500, "#22c55e",
|
||||
],
|
||||
stops: [0, 1000, 1500, 2000, 2500],
|
||||
colors: ["#7f1d1d", "#dc2626", "#f97316", "#fbbf24", "#22c55e", "#22c55e"],
|
||||
label: T.consumo_p10_bajo,
|
||||
desc: DESC.consumo_p10_bajo,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
consumo_p50_medio: {
|
||||
scope: "all",
|
||||
field: "consumo_p50_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p50_kwh"], 0],
|
||||
"#7f1d1d", 2500, "#dc2626", 3000, "#facc15", 3500, "#86efac", 4000, "#22c55e",
|
||||
],
|
||||
stops: [0, 2500, 3000, 3500, 4000],
|
||||
colors: ["#7f1d1d", "#dc2626", "#facc15", "#86efac", "#22c55e", "#22c55e"],
|
||||
label: T.consumo_p50_medio,
|
||||
desc: DESC.consumo_p50_medio,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
consumo_p90_alto: {
|
||||
scope: "all",
|
||||
field: "consumo_p90_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p90_kwh"], 0],
|
||||
"#7f1d1d", 3000, "#facc15", 4000, "#86efac", 5000, "#22c55e", 6000, "#15803d",
|
||||
],
|
||||
stops: [0, 3000, 4000, 5000, 6000],
|
||||
colors: ["#7f1d1d", "#facc15", "#86efac", "#22c55e", "#15803d", "#15803d"],
|
||||
label: T.consumo_p90_alto,
|
||||
desc: DESC.consumo_p90_alto,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
consumo_p50_kwh: {
|
||||
scope: "all",
|
||||
field: "consumo_p50_kwh",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "consumo_p50_kwh"], 0],
|
||||
"#fee2e2", 1500, "#fecaca", 2500, "#fca5a5", 3500, "#ef4444", 4500, "#7f1d1d",
|
||||
],
|
||||
stops: [0, 1500, 2500, 3500, 4500],
|
||||
colors: ["#fee2e2", "#fecaca", "#fca5a5", "#ef4444", "#7f1d1d", "#7f1d1d"],
|
||||
label: T.consumo_p50,
|
||||
desc: DESC.consumo_p50_kwh,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
ratio_p10_p50_consumo: {
|
||||
scope: "all",
|
||||
field: "ratio_p10_p50_consumo",
|
||||
colorExpr: [
|
||||
"step", ["coalesce", ["get", "ratio_p10_p50_consumo"], 0],
|
||||
"#7c2d12", 0.15, "#c2410c", 0.25, "#f97316", 0.35, "#fbbf24", 0.5, "#86efac",
|
||||
],
|
||||
stops: [0, 0.15, 0.25, 0.35, 0.5],
|
||||
colors: ["#7c2d12", "#c2410c", "#f97316", "#fbbf24", "#86efac", "#86efac"],
|
||||
label: T.ratio_p10_p50,
|
||||
desc: DESC.ratio_p10_p50,
|
||||
appliesTo: T.legend_islands_all_dis,
|
||||
},
|
||||
};
|
||||
|
||||
const fmt = (n: number) =>
|
||||
@@ -553,14 +393,27 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
if (loading) loading.style.display = "none";
|
||||
}
|
||||
|
||||
let activeVariable = "airbnb_listings";
|
||||
let activeVariable = "viviendas_totales";
|
||||
|
||||
function applyVariable(variable: string) {
|
||||
const cfg = LAYERS[variable];
|
||||
if (!cfg) return;
|
||||
activeVariable = variable;
|
||||
|
||||
if (cfg.scope === "airbnb") {
|
||||
if (variable === "pressio_turistica") {
|
||||
// Capa compuesta: Airbnb en Mallorca/Menorca + HUT en Eivissa, simultáneas.
|
||||
map.setPaintProperty(
|
||||
"secciones-airbnb-fill",
|
||||
"fill-color",
|
||||
LAYERS.airbnb_listings.colorExpr
|
||||
);
|
||||
map.setPaintProperty(
|
||||
"secciones-hut-fill",
|
||||
"fill-color",
|
||||
LAYERS.hut_plazas.colorExpr
|
||||
);
|
||||
map.setPaintProperty("secciones-all-fill", "fill-color", NO_DATA_COLOR);
|
||||
} else if (cfg.scope === "airbnb") {
|
||||
map.setPaintProperty("secciones-airbnb-fill", "fill-color", cfg.colorExpr);
|
||||
map.setPaintProperty("secciones-hut-fill", "fill-color", NO_DATA_COLOR);
|
||||
map.setPaintProperty("secciones-all-fill", "fill-color", NO_DATA_COLOR);
|
||||
@@ -575,10 +428,11 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
map.setPaintProperty("secciones-hut-fill", "fill-color", NO_DATA_COLOR);
|
||||
}
|
||||
|
||||
// Auto-ocultar círculos de Airbnb cuando la variable no es Airbnb.
|
||||
// Auto-ocultar círculos de Airbnb cuando la variable no es Airbnb/turística.
|
||||
// Antes: los círculos mostraban siempre el mismo dato (count de listings)
|
||||
// independientemente de la variable, lo que confundía al usuario.
|
||||
const showAirbnbClusters = cfg.scope === "airbnb";
|
||||
const showAirbnbClusters =
|
||||
variable === "airbnb_listings" || variable === "pressio_turistica";
|
||||
const clusterVis: "visible" | "none" = showAirbnbClusters ? "visible" : "none";
|
||||
["airbnb-clusters", "airbnb-cluster-count", "airbnb-unclustered"].forEach((id) => {
|
||||
if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", clusterVis);
|
||||
@@ -587,6 +441,14 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
const clusterToggle = document.getElementById("toggle-clusters") as HTMLInputElement | null;
|
||||
if (clusterToggle) clusterToggle.checked = showAirbnbClusters;
|
||||
|
||||
// aria-pressed en los botones según cuál está activo (estilo visual)
|
||||
Array.from(document.querySelectorAll<HTMLButtonElement>(".variable-btn")).forEach((b) => {
|
||||
b.setAttribute(
|
||||
"aria-pressed",
|
||||
b.dataset.variable === variable ? "true" : "false"
|
||||
);
|
||||
});
|
||||
|
||||
updateLegend(cfg);
|
||||
const desc = document.getElementById("variable-desc");
|
||||
if (desc) desc.textContent = cfg.desc;
|
||||
@@ -594,16 +456,35 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
|
||||
function formatTick(n: number): string {
|
||||
if (n === 0) return "0";
|
||||
if (activeVariable === "airbnb_revenue_total") return fmtMoney(n);
|
||||
if (activeVariable === "consumo_p50_kwh") return `${fmt(n)} kWh`;
|
||||
if (activeVariable === "airbnb_per_1000") return n.toFixed(0);
|
||||
if (activeVariable === "ratio_p10_p50_consumo") return n.toFixed(2);
|
||||
if (activeVariable === "possibles_vacants") return `${fmt(Math.round(n))} kWh`;
|
||||
return fmtShort(n);
|
||||
}
|
||||
|
||||
function updateLegend(cfg: typeof LAYERS[string]) {
|
||||
const el = document.getElementById("legend-content");
|
||||
if (!el) return;
|
||||
|
||||
// Caso especial: la capa composta 'pressio_turistica' usa dos fill-colors
|
||||
// distintos geográficamente (Airbnb en Mallorca/Menorca, HUT en Eivissa).
|
||||
// El degradado único del LAYERS no representa eso; mostramos dos mini-leyendas.
|
||||
if (activeVariable === "pressio_turistica") {
|
||||
const airbnbBar = LAYERS.airbnb_listings.colors
|
||||
.map((c) => `<span class="flex-1" style="background:${c}"></span>`)
|
||||
.join("");
|
||||
const hutBar = LAYERS.hut_plazas.colors
|
||||
.map((c) => `<span class="flex-1" style="background:${c}"></span>`)
|
||||
.join("");
|
||||
el.innerHTML = `
|
||||
<p class="font-semibold text-stone-700 mb-1">${escape(cfg.label)}</p>
|
||||
<p class="text-stone-500 mb-2 leading-tight">${escape(cfg.desc)}</p>
|
||||
<p class="text-xs font-medium text-stone-700 mb-1">Airbnb — Mallorca / Menorca</p>
|
||||
<div class="flex items-center gap-0.5 h-2 rounded overflow-hidden mb-2">${airbnbBar}</div>
|
||||
<p class="text-xs font-medium text-stone-700 mb-1">HUT — Eivissa</p>
|
||||
<div class="flex items-center gap-0.5 h-2 rounded overflow-hidden mb-2">${hutBar}</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const swatches = cfg.colors
|
||||
.map((c) => `<span class="flex-1" style="background:${c}"></span>`)
|
||||
.join("");
|
||||
@@ -628,9 +509,14 @@ const popupTitleTmpl = t(lang, "map.popup_title");
|
||||
}
|
||||
|
||||
map.on("load", () => {
|
||||
const variableSelect = document.getElementById("variable-select") as HTMLSelectElement;
|
||||
variableSelect?.addEventListener("change", () => applyVariable(variableSelect.value));
|
||||
if (variableSelect) applyVariable(variableSelect.value);
|
||||
const variableBtns = Array.from(
|
||||
document.querySelectorAll<HTMLButtonElement>(".variable-btn")
|
||||
);
|
||||
variableBtns.forEach((btn) => {
|
||||
btn.addEventListener("click", () => applyVariable(btn.dataset.variable ?? ""));
|
||||
});
|
||||
const initial = variableBtns[0]?.dataset.variable ?? "viviendas_totales";
|
||||
if (variableBtns[0]) applyVariable(initial);
|
||||
|
||||
const toggleChoro = document.getElementById("toggle-choropleth") as HTMLInputElement;
|
||||
const toggleClu = document.getElementById("toggle-clusters") as HTMLInputElement;
|
||||
|
||||
+7
-35
@@ -69,41 +69,13 @@
|
||||
"layer_choropleth": "Plànol coroplètic",
|
||||
"layer_clusters": "Punts Airbnb (clústers)",
|
||||
"variable_label": "Variable",
|
||||
"group_vivienda": "Habitatge (INE Cens 2021)",
|
||||
"group_consumo": "Consum elèctric (Cens 2021)",
|
||||
"group_presion": "Pressió Airbnb (juny 2026)",
|
||||
"group_hut": "Habitatge turístic legal — HUT (juliol 2026)",
|
||||
"group_avanzado": "Avançat",
|
||||
"variable_airbnb_listings": "Listings Airbnb",
|
||||
"variable_airbnb_listings_desc": "Total de listings actius per secció censal",
|
||||
"variable_airbnb_entire": "Pisos sencers (entire home/apt)",
|
||||
"variable_airbnb_entire_desc": "Listings que són pisos complets — proxy de conversió residencial → turístic",
|
||||
"variable_airbnb_revenue": "Ingressos Airbnb L365d",
|
||||
"variable_airbnb_revenue_desc": "Suma estimada dels ingressos dels darrers 365 dies per secció",
|
||||
"variable_airbnb_con_licencia": "Listings amb llicència Airbnb",
|
||||
"variable_airbnb_con_licencia_desc": "Listings amb camp `license` emplenat (autorreportat, no validat pel Govern)",
|
||||
"variable_hut_plazas": "Places HUT legals",
|
||||
"variable_hut_plazas_desc": "Places registrades al registre HUT del Consell d'Eivissa (municipi)",
|
||||
"variable_hut_registros": "Registres HUT",
|
||||
"variable_hut_registros_desc": "Nombre de registres HUT actius (municipi)",
|
||||
"variable_viviendas_totales": "Habitatges totals (INE)",
|
||||
"variable_viviendas_totales_desc": "Total d'habitatges per municipi (Cens 2021, replicat a totes les seccions del municipi)",
|
||||
"variable_viviendas_vacantes": "Habitatges vacants (estimació per municipi)",
|
||||
"variable_viviendas_vacantes_desc": "Estimació del nombre d'habitatges vacants per municipi, calculada com el 16,2% del total (taxa CCAA de l'INE, Cens 2021). És un proxy: la taxa real varia per municipi (més alta a zones turístiques). Total Balears: 105.632 vacants.",
|
||||
"variable_consumo_p50": "Mediana consum elèctric (kWh)",
|
||||
"variable_consumo_p50_desc": "Percentil 50 de consum elèctric anual per districte (Cens 2021). Mediana del que consumeix una llar al districte.",
|
||||
"variable_consumo_p10_muy_bajo": "Cola de baix consum (p10)",
|
||||
"variable_consumo_p10_muy_bajo_desc": "Districte amb percentil 10 de consum elèctric inferior a 1.000 kWh/any. Una p10 baixa indica que hi ha un grup nombrós de cases amb consum molt reduït (vacants, segones residències, consum testimonial). Vermell = risc elevat de semi-vaciació.",
|
||||
"variable_consumo_p10_bajo": "Cola de consum baix (p10)",
|
||||
"variable_consumo_p10_bajo_desc": "Districte amb p10 entre 1.000 i 2.000 kWh/any. Taronja = possible ús estacional o semi-vaciació parcial.",
|
||||
"variable_consumo_p50_medio": "Consum mitjà (p50)",
|
||||
"variable_consumo_p50_medio_desc": "Districte amb p50 entre 2.500 i 3.500 kWh/any. Groc = consum residencial estàndard. La mediana de Balears és 2.869 kWh.",
|
||||
"variable_consumo_p90_alto": "Cua de consum alt (p90)",
|
||||
"variable_consumo_p90_alto_desc": "Districte amb p90 superior a 4.500 kWh/any. Verd = sense cues de baix consum, perfil residencial estable amb AC intensiu o família nombrosa.",
|
||||
"variable_ratio_p10_p50": "Ratio p10/p50 consum elèctric",
|
||||
"variable_ratio_p10_p50_desc": "Quocient entre el percentil 10 i la mediana del consum elèctric del districte (Cens 2021). Baix (<0.3) = grup de cases amb consum molt baix, possibles vacants o segones residències. Alt (~0.6+) = distribució més homogènia d'ús residencial estable.",
|
||||
"variable_airbnb_per_1000": "Pressió Airbnb (per 1000 habitatges)",
|
||||
"variable_airbnb_per_1000_desc": "Listings Airbnb per cada 1000 habitatges del municipi. Mesura relativa de la pressió turística sobre el parc residencial.",
|
||||
"group_vivienda": "Habitatge i pressió",
|
||||
"variable_viviendas_totales": "Habitatges totals",
|
||||
"variable_viviendas_totales_desc": "Total d'habitatges censats per secció censal (INE 2021).",
|
||||
"variable_possibles_vacants": "Possibles vacants",
|
||||
"variable_possibles_vacants_desc": "Seccions on la cua baixa del consum elèctric és molt baixa (p10). Com més baix el p10, més probable que hi hagi habitatges buits o 2a residència (proxy Holm 2025).",
|
||||
"variable_pressio_turistica": "Pressió turística",
|
||||
"variable_pressio_turistica_desc": "Mallorca i Menorca: listings d'Airbnb actius (juny 2026). Eivissa: places d'habitatge turístic legal — HUT (juliol 2026).",
|
||||
"legend_title": "Llegenda",
|
||||
"legend_scale_label": "Escala",
|
||||
"legend_applies_to": "Aplica a",
|
||||
|
||||
+7
-35
@@ -69,41 +69,13 @@
|
||||
"layer_choropleth": "Plano coroplético",
|
||||
"layer_clusters": "Puntos Airbnb (clústeres)",
|
||||
"variable_label": "Variable",
|
||||
"group_vivienda": "Vivienda (INE Censo 2021)",
|
||||
"group_consumo": "Consumo eléctrico (Censo 2021)",
|
||||
"group_presion": "Presión Airbnb (junio 2026)",
|
||||
"group_hut": "Vivienda turística legal — HUT (julio 2026)",
|
||||
"group_avanzado": "Avanzado",
|
||||
"variable_airbnb_listings": "Listings Airbnb",
|
||||
"variable_airbnb_listings_desc": "Total de listings activos por sección censal",
|
||||
"variable_airbnb_entire": "Pisos enteros (entire home/apt)",
|
||||
"variable_airbnb_entire_desc": "Listings que son pisos completos — proxy de conversión residencial → turístico",
|
||||
"variable_airbnb_revenue": "Ingresos Airbnb L365d",
|
||||
"variable_airbnb_revenue_desc": "Suma estimada de ingresos de los últimos 365 días por sección",
|
||||
"variable_airbnb_con_licencia": "Listings con licencia Airbnb",
|
||||
"variable_airbnb_con_licencia_desc": "Listings con campo `license` rellenado (autorreportado, no validado por Govern)",
|
||||
"variable_hut_plazas": "Plazas HUT legales",
|
||||
"variable_hut_plazas_desc": "Plazas registradas en el registro HUT del Consell d'Eivissa (municipio)",
|
||||
"variable_hut_registros": "Registros HUT",
|
||||
"variable_hut_registros_desc": "Número de registros HUT activos (municipio)",
|
||||
"variable_viviendas_totales": "Viviendas totales (INE)",
|
||||
"variable_viviendas_totales_desc": "Total de viviendas por municipio (Censo 2021, replicado en todas las secciones del municipio)",
|
||||
"variable_viviendas_vacantes": "Viviendas vacantes (estimación por municipio)",
|
||||
"variable_viviendas_vacantes_desc": "Estimación del número de viviendas vacantes por municipio, calculada como el 16,2% del total (tasa CCAA del INE, Censo 2021). Es un proxy: la tasa real varía por municipio (más alta en zonas turísticas). Total Baleares: 105.632 vacantes.",
|
||||
"variable_consumo_p50": "Mediana consumo eléctrico (kWh)",
|
||||
"variable_consumo_p50_desc": "Percentil 50 de consumo eléctrico anual por distrito (Censo 2021). Mediana de lo que consume un hogar en el distrito.",
|
||||
"variable_consumo_p10_muy_bajo": "Cola de bajo consumo (p10)",
|
||||
"variable_consumo_p10_muy_bajo_desc": "Distrito con percentil 10 de consumo eléctrico inferior a 1.000 kWh/año. Una p10 baja indica que hay un grupo numeroso de casas con consumo muy reducido (vacías, segundas residencias, consumo testimonial). Rojo = riesgo elevado de semi-vacío.",
|
||||
"variable_consumo_p10_bajo": "Cola de consumo bajo (p10)",
|
||||
"variable_consumo_p10_bajo_desc": "Distrito con p10 entre 1.000 y 2.000 kWh/año. Naranja = posible uso estacional o semi-vacío parcial.",
|
||||
"variable_consumo_p50_medio": "Consumo medio (p50)",
|
||||
"variable_consumo_p50_medio_desc": "Distrito con p50 entre 2.500 y 3.500 kWh/año. Amarillo = consumo residencial estándar. La mediana de Baleares es 2.869 kWh.",
|
||||
"variable_consumo_p90_alto": "Cola de consumo alto (p90)",
|
||||
"variable_consumo_p90_alto_desc": "Distrito con p90 superior a 4.500 kWh/año. Verde = sin colas de bajo consumo, perfil residencial estable con AC intensivo o familia numerosa.",
|
||||
"variable_airbnb_per_1000": "Presión Airbnb (por 1000 viviendas)",
|
||||
"variable_airbnb_per_1000_desc": "Listings Airbnb por cada 1000 viviendas del municipio. Medida relativa de la presión turística sobre el parque residencial.",
|
||||
"variable_ratio_p10_p50": "Ratio p10/p50 consumo eléctrico (proxy semi-vacío)",
|
||||
"variable_ratio_p10_p50_desc": "Cociente entre el percentil 10 y la mediana del consumo eléctrico del distrito (Censo 2021). Bajo (<0.3) = hay un grupo numeroso de casas con consumo muy bajo, posibles vacías o segundas residencias. Alto (~0.6+) = distribución más homogénea de uso residencial estable.",
|
||||
"group_vivienda": "Vivienda y presión",
|
||||
"variable_viviendas_totales": "Viviendas totales",
|
||||
"variable_viviendas_totales_desc": "Total de viviendas censadas por sección censal (INE 2021).",
|
||||
"variable_possibles_vacants": "Posibles vacantes",
|
||||
"variable_possibles_vacants_desc": "Secciones donde la cola baja del consumo eléctrico es muy baja (p10). Cuanto más bajo el p10, más probable que haya viviendas vacías o 2ª residencia (proxy Holm 2025).",
|
||||
"variable_pressio_turistica": "Presión turística",
|
||||
"variable_pressio_turistica_desc": "Mallorca y Menorca: listings de Airbnb activos (junio 2026). Eivissa: plazas de vivienda turística legal — HUT (julio 2026).",
|
||||
"legend_title": "Leyenda",
|
||||
"legend_scale_label": "Escala",
|
||||
"legend_applies_to": "Aplica a",
|
||||
|
||||
Reference in New Issue
Block a user