Skip to content

Instantly share code, notes, and snippets.

@jsanz
Last active December 7, 2015 09:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsanz/b621435f418ad6a856c2 to your computer and use it in GitHub Desktop.
Save jsanz/b621435f418ad6a856c2 to your computer and use it in GitHub Desktop.
Taller Elecciones 2015

Taller mapas electorales con CartoDB

En este taller se describe el proceso de creación de diversos mapas electorales con CartoDB, enfocado sobre todo al tratamient o de los datos y las reglas de simbolización cartográfica.

http://bit.ly/cdb-elecciones-151203

Contacto:

Los datos

Los datos que se van a utilizar son de dos tipos:

  • Datos geográficos obtenidos del centro de descargas del CNIG. Estos datos ya se encuentran disponibles en el Data Library de CartoDB en dos variantes: dataset original y dataset con las Islas Canarias desplazadas para su representación en mapas estadísticos.

  • Datos alfanuméricos de las elecciones al Congreso de 2008 y 2011 descargados del portal de información electoral del Ministerio de Interior. Estos datos se obtienen en formato Excel y se han simplificado a ficheros CSV con nombres de partido adecuados para servir como columnas del dataset de CartoDB. También se han tratado para asegurar que no se genera un CSV con valores de texto en las columnas de datos numéricos.

NOTA El centro de descargas del CNIG además de las líneas límite de municipios, provincias y comunidades autónomas proporciona algunos datasets más con licencia de uso abierta y otros con licencias que permiten uso no comercial. De igual manera, es posible cargar servicios WMS publicados en la IDEE como mapas base en CartoDB donde cabe destacar la otrofotografía del PNOA pero hay muchas otras capas interesantes.

Importar datos

  1. Cargar CSVs de resultados de elecciones 2008 y 2001
  2. Crear campo de identificación
UPDATE jsanz.mun_electoral_2011 SET code = mun + prov*1000;
UPDATE jsanz.mun_electoral_2008 SET code = mun + prov*1000;
  1. Importar del data library ign_spanish_adm3_municipalities_displaced_canary

No es necesario en los datasets que hemos importado pero si tienes curiosidad o quieres tener las Canarias cerca de la Ponínsula en tus propios datasets debes ejecutar una instrucción como esta:

UPDATE tabla
SET the_geom = ST_Translate(the_geom,5.0,7.4)
WHERE
ST_Intersects(
 the_geom,
 ST_MakeEnvelope(-18.748169,27.571591,-13.342896,29.463514,4326)
)

NOTA: Atención porque esto desplaza las geometrías de forma definitiva, haz siempre una copia de tus datos para mantener el dataset original.

Preparar mapa base

El primer paso es crear un mapa base sobre el que trabajar, la capa inferior solo se mostrará en el mapa proporcional de puntos pero es bueno tenerla a mano y reutilizarla cuando sea conveniente.

  1. Un mapa nuevo
  2. Desactivar el mapa base y utilizar un color plano
  3. Cargar capa de municipios y cambiar la SQL
SELECT
  cartodb_id,
  natcode::bigint % 100000 as code,
  nameunit as name,
  the_geom_webmercator
FROM ign_spanish_adm3_municipalities_displaced_canary
  1. Cargar la capa de provincias encima
  2. Utilizamos un CartoCSS que permita ver los municpios a partir de cierto nivel de zoom

Detalles

Inciso: mapa europeo

En el taller vamos a ver capas en España, que se ve bien en la proyección cartográfica utilizada por defecto en CartoDB con el pequeño truco de desplazar las Canarias. Cuando visualizamos áreas más grandes y especialmente para mapas como los electorales donde el área ocupada por cada polígono interesa que sea lo más fiel posible. La proyección de Mercator no mantiene bien las áreas conforme nos alejamos al norte por lo que para mapas electorales de ámbito europeo es interesante cambiar a una proyección cartográfica diferente.

El proceso mínimo sería algo como esto:

  1. Importamos la capa ne_adm0_europe desde esta tabla en CartoDB
  2. Creamos un mapa nuevo añadiendo dos veces la capa, una con todos los países y otra filtrando solo los que forman la Unión Europea
SELECT
  cartodb_id,
  admin,
  adm0_a3,
  the_geom_webmercator
FROM jsanz.ne_adm0_europe
WHERE eu_union
  1. Vamos a usar una proyección cartográfica que mantiene las áreas. La proyección Lambert Azimutal Equal Area es adecuada para información estadística que cubre el continente. Más información aquí.
  2. El truco consiste en aplicar la función ST_Transform para convertir el campo the_geom_webmercator, quedando la consulta SQL así:
SELECT
  cartodb_id,
  admin,
  adm0_a3,
  ST_Transform(the_geom_webmercator,3035)
    AS the_geom_webmercator
FROM jsanz.ne_adm0_europe
WHERE eu_union

Obviamente en los mapas en los que cambiamos la proyección cartográfica tenemos que desactivar el mapa base y utilizar un color plano.

Detalles

  • Una mejora de esto es crear una tabla completa nueva con las geometrías transformadas.

Mapa de resultados del PP

El primer mapa electoral va a ser el mapa de la proporción de voto de un único partido político.

  1. Hacemos copia de nuestro mapa base
  2. Volvemos a importar la capa de municipios y la dejamos entre las otras dos
  3. Ahora tenemos que traer los datos de las municipales de 2001, para ello usamos un JOIN
  4. Para mostrar un porcentaje dividimos el número de votos por el total de votos válidos

La simbología la podemos procesar directamente con el wizard, vigilando el método de cuantificación que queremos aplicar y atenuando el borde para reducir su presencia visual.

Detalles

Mapa de coropletas

El siguiente mapa muestra la diferencia de voto del PP frente al PSOE. La idea es generar un mapa de coropletas por municipio con una gradación en favor de uno u otro partido.

La consulta es una evolución de la anterior y además de los campos habituales se generan dos campos nuevos:

  1. Un campo de texto con una sentencia CASE en función del partido que más votos tiene.
  2. Un campo que obtiene el valor absoluto del porcentaje de voto de un partido respecto a otro.

Para la simbología podemos aprovechar las funciones de color y los selecctores por atributo, discriminando en función del ganador y estableciendo como límites de la rampa de tres colores los límites del 20 y 30 por cien. Al utulizar variables de color podemos reusar el color para calcular automáticamente los colores de la gradación usando la función lighten para aclarar el color en los cortes inferiores.

Detalles

Mapa proporcional

En lugar de pintar todo el territorio vamos a usar símbolos puntuales que tendrán un radio proporcional a la diferencia en votos. Para que sea proporcional usaremos una variación de esta fórmula a la que le sumaremos un tamaño mínimo.

La consulta es grande pero sencilla:

  • Primero calculamos la máxima diferencia entre nuestros valores en porcentaje
  • A continuación realizamos una consulta parecida a la del mapa anterior pero devolviendo solo el centroide
  • Finalmente combinamos las dos anteriores aplicando la fórmula para obtener un campo symbol_size Una vez obtenidos los datos el CartoCSS es relativamente sencillo ya que aplicamos el tamaño calculado. Para evitar que el mapa salga muy cargado en zooms bajos dividimos este valor por diferentes factores

Detalles

Partido más votado

Para obtener el partido más votado duplicamos el primer mapa que hicimos del PP y cambiaremos la consulta SQL por la siguiente: De nuevo una consulta larga pero sencilla:

  • La primera subconsulta obtiene la tabla mun_electoral_2011 y además obtiene el mayor valor para los partidos principales.
  • La siguiente consulta utiliza una estructura CASE para crear un campo nuevo con el nombre del partido más votado
  • Finalmente la última consulta une esta última con la tabla con las geometrías para poder renderizarla

La simbología es un simple, una selección por atributo usando los colores representativos de cada partido. Igualmente se procesa la leyenda para presentar estos colores.

Detalles

Cambio en el partido más votado frente a elecciones 2008

Siguiendo el mapa que acabamos de hacer podemos cargar también los datos de 2008 y compararlos.

En este caso la consulta es casi igual que la anterior solo que se duplican las subconsultas para obtener los ganadores y en la consulta final se hace un CASE para compararlos y en caso de que no sean iguales indicar el ganador y así aplicar la misma leyenda que en el mapa anterior.

Detalles

Sobre el rendimiento

Sobre los datos alfanuméricos

Estos mapas que hemos visto sirven como punto de partida para el diseño de los mapas que vamos a publicar. En general se hacen varias operaciones que involucran un tratamiento dinámico de los datos. Dado que los datos no van a cambiar, o lo van a hacer de forma controlada y periódica, es conveniente precalcular los datasets para que CartoDB pueda renderizar de forma eficiente toda la información.

Para mapa estáticos como los que se generan una vez los datos electorales han sido escrutados por completo, lo más directo es utilizar la opción del menú del editor Dataset from Query, ya que genera un juego de datos de forma immediata y listo para usar en CartoDB.

Para mapas dinámicos la actualización de estos juegos de datos involucraría la creación de vistas materializadas y el disparo del refresco de las mismas dentro del proceso de actualización de las tablas con los datos electorales. Consulta con nosotros si es este tu caso ya que queda fuera del ámbito de este taller.

Sobre los datos geográficos

Los datasets del CNIG son de muy alta precisión. En general para mapas electorales no vamos a necesitar este nivel de detalle y por tanto podemos hacer que los mapas sean más rápidos generando un dataset derivado que sea una simplificación de éstos.

Generar una adecuada simplificación de una capa de polígonos no es una tarea sencilla ni inmediata y conlleva un trabajo en off line adecuado para que las geometrías que se muestren en nuestros mapas sean continuas y representen lo mejor posible la realidad.

Por otro lado existe el problema de que el juego de datos tiene que tener una calidad alfanumérica adecuada especialmente en dos campos:

  • Un campo identificador adecuado basado en los códigos del INE para asegurar una correcta vinculación con los datos electorales.

  • Un campo con el nombre oficial para cada límite administrativo para asegurar su correcta identificación.

Todo junto en un visor web

Una vez hemos generado nuestros mapas, depende de nuestro proyecto podemos incrustarlos directamente en nuestro sitio web o bien tenemos la posibilidad de hacer un pequeño desarrollo con CartoDB.js que integre diferentes mapas en una única página web.

En los siguientes enlaces os dejamos una plantilla que podéis reutilizar fácilmente para publicar vuestros mapas. El código JavaScript es muy sencillo y solo hay que cambiar las url de los viz.json en el fichero app.js, así como las etiquetas en el index.html para publicar vuestros.

Cómo obtener niveles de corte

Esta consulta muestra cómo usar la función de CartoDB CDB_Quantiles para calcular los niveles de corte para una variable a utilizar en CartoCSS. Estas funciones normalmente las realiza el editor por nosotros. Más sobre las funciones estadísticas de CartoDB aquí.

WITH data AS (
SELECT
    (10e6 * e.pob / ST_Area(m.the_geom::geography))::int AS densidad
FROM
  ign_spanish_adm3_municipalities_displaced_canary m
JOIN
  mun_electoral_2011 e
ON
  m.natcode::bigint % 100000 = e.code
)
SELECT
  CDB_QuantileBins(ARRAY_AGG(densidad), 3)
FROM
  data

Otros enlaces interesantes

¡¡Gracias!!

http://bit.ly/cdb-elecciones-151203

Contacto:

var sync_center = true;
var a = 0;
var jsons = [
// mapa base
{viz: 'https://team.cartodb.com/u/jsanz/api/v2/viz/7d7072c2-9821-11e5-a861-0e787de82d45/viz.json'},
// resultados partido
{viz: 'https://team.cartodb.com/u/jsanz/api/v2/viz/0a93b43a-99ca-11e5-b52b-0e31c9be1b51/viz.json'},
// pp vs psoe
{viz: 'https://team.cartodb.com/u/jsanz/api/v2/viz/bd1ecd0e-982d-11e5-b8a2-0e5db1731f59/viz.json'},
// más votado
{viz: 'https://team.cartodb.com/u/jsanz/api/v2/viz/26c61028-984c-11e5-b286-0ecd1babdde5/viz.json'},
// 2008 vs 2011
{viz: 'https://team.cartodb.com/u/jsanz/api/v2/viz/c6cc543c-9857-11e5-8415-0ecd1babdde5/viz.json'}
];
var map;
var center = {lat: 40.0, lng: -2.32};
var center2 = [35.0,-2.32];
var zoom = 7;
var zoom2 = 4;
function loadMaps(a){
if(map){center = map.getCenter(); zoom = map.getZoom()}
$('#map').empty();
cartodb.createVis('map', jsons[a].viz,{
center_lat: center.lat,
center_lon: center.lng,
zoom: zoom,
shareable:false,
search:false,
zoomControl:true,
loaderControl: true
}).done(function(vis,layers){
map = vis.getNativeMap();
});
}
function main() {
// Put the thumbnails
cartodb.Image(jsons[0].viz)
.center(center2)
.zoom(zoom2)
.getUrl(function(err, url) {
var img = new Image();
$('#map0').css('background-image', 'url(' + url + ')');
});
cartodb.Image(jsons[1].viz)
.center(center2)
.zoom(zoom2)
.getUrl(function(err, url) {
var img = new Image();
$('#map1').css('background-image', 'url(' + url + ')');
});
cartodb.Image(jsons[2].viz)
.center(center2)
.zoom(zoom2)
.getUrl(function(err, url) {
var img = new Image();
$('#map2').css('background-image', 'url(' + url + ')');
});
cartodb.Image(jsons[3].viz)
.center(center2)
.zoom(zoom2)
.getUrl(function(err, url) {
var img = new Image();
$('#map3').css('background-image', 'url(' + url + ')');
});
cartodb.Image(jsons[4].viz)
.center(center2)
.zoom(zoom2)
.getUrl(function(err, url) {
var img = new Image();
$('#map4').css('background-image', 'url(' + url + ')');
});
//loadMaps events
$('#map0').click(function(){
loadMaps(0);
});
$('#map1').click(function(){
loadMaps(1);
});
$('#map2').click(function(){
loadMaps(2);
});
$('#map3').click(function(){
loadMaps(3);
});
$('#map4').click(function(){
loadMaps(4);
});
// Click on first button
$('#map3').click();
}
$('.Thumbnails > li').click(function() {
$('.Thumbnails > li').removeClass('selected');
$(this).addClass('selected');
});
window.onload = main;
<!DOCTYPE html>
<html>
<head>
<title>Election Mapping Bonanza</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" href="http://cartodb.com/assets/favicon.ico" />
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,700|Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css'>
<style>
ul {
margin: 0;
padding: 0;
}
li {
margin: 0;
padding: 0;
list-style-type: none;
text-shadow: 0 0 10px #eee;
}
li > p {
float: right;
}
html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
#map {
position: absolute;
left: 380px;
top: 0;
right: 0;
bottom: 0;
padding: 0;
margin: 0;
}
.top_map{
z-index: 1000;
}
.bottom_map {
display: none;
z-index: 0;
}
#map_selector {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
padding: 0;
margin: 0;
z-index: 1000;
overflow: hidden;
padding: 40px;
background: #f2f2f2;
}
#Thumbnails ul {
padding: 0; margin: 0;
list-style-type: none;
height: 100%;
width: 100%;
}
#Thumbnails li {
float: left;
font-family: "Helvetica", Arial;
font-size: 13px;
color: #444;
cursor: auto;
width: 33.33%;
height: 150px;
cursor: pointer;
}
.map {
width: 100%; height: 100%;
}
.Header h1 {
font: 700 30px 'Open Sans Condensed';
color: #333;
padding: 0 0 20px 0;
border-bottom: 1px solid #ccc;
margin: 0 0 30px 0;
}
.ramp-inner {
height: 20px;
/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#1e5799+0,2f4886+0,2f4886+50,2f4886+50,2989d8+51,2989d8+51,ad373e+51 */
background: #1e5799; /* Old browsers */
background: -moz-linear-gradient(left, #1e5799 0%, #2f4886 0%, #2f4886 50%, #2f4886 50%, #2989d8 51%, #2989d8 51%, #ad373e 51%); /* FF3.6-15 */
background: -webkit-linear-gradient(left, #1e5799 0%,#2f4886 0%,#2f4886 50%,#2f4886 50%,#2989d8 51%,#2989d8 51%,#ad373e 51%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to right, #1e5799 0%,#2f4886 0%,#2f4886 50%,#2f4886 50%,#2989d8 51%,#2989d8 51%,#ad373e 51%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#ad373e',GradientType=1 ); /* IE6-9 */
border-radius: 50px;
}
.ramp {
margin-bottom: 30px;
}
.ramp-info {
width: auto;
overflow: hidden;
margin-bottom: 12px;
}
.ramp-info li {
float: left;
width: 20%;
text-align: left;
margin: 0;
font: 300 20px/18px 'Open Sans Condensed', sans-serif;
text-transform: uppercase;
}
.cartodb-overlay {
display: none !Important;
}
.Thumbnails > li {
opacity: .5;
margin-bottom: 30px;
position: relative;
cursor: pointer;
}
.Thumbnails > li p {
font: 300 14px/15px 'Open Sans Condensed', sans-serif;
margin: 0;
position: absolute;
bottom: 10px;
left: 10px;
}
.Thumbnails > li.selected {
opacity: 1;
}
.mapx {
height: 100px;
position: relative;
overflow: hidden;
}
.selected .mapx {
box-shadow: 0 0 8px rgba(0, 0, 0, .2);
}
.mapx:after {
content:"";
position: absolute;
display: block;
pointer-events: none;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 1px solid rgba(0, 0, 0, .3);
border-radius: 2px;
}
.logo {
position: absolute;
left: 40px;
bottom: 40px;
}
</style>
<link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/3.15/themes/css/cartodb.css" />
</head>
<body>
<div id="map" class=" top_map"></div>
<div id="map_selector" class="">
<div class="Header">
<h1>Elecciones Congreso 2011</h1>
<img class="logo" src="https://cartodb-libs.global.ssl.fastly.net/cartodb.com/static/logos_full_cartodb_light.png" height="30" />
</div>
<ul class="Thumbnails">
<li id='img0' class="selected">
<div id="map0" class="mapx"></div>
<p>Mapa base</p>
</li>
<li id='img1'>
<div id="map1" class="mapx"></div>
<p>% votos</p>
</li>
<li id='img2'>
<div id="map2" class="mapx"></div>
<p>PP vs PSOE</p>
</li>
<li id='img3'>
<div id="map3" class="mapx"></div>
<p>Más votado 2014</p>
</li>
<li id='img4'>
<div id="map4" class="mapx"></div>
<p>2008 vs 2011</p>
</li>
</ul>
</div>
<!-- include cartodb.js library -->
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.js"></script>
<script src="app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment