El formato para datos geográficos GeoJSON está ámpliamente extendido por su sencillez y versatilidad. GeoJSON es muy utilizado para la publicación de datos en la web porque es soportado de forma nativa por muchas librerías JavaScript como Leaflet u OpenLayers.
En concreto, Leaflet dispone de varios métodos para trabajar con GeoJSON. En este artículo vamos a tratar algunas de las opciones de las que dispone Leaflet para trabajar con GeoJSON, centrándonos especialmente en la que nos permite realizar el filtrado de datos.
GeoJSON y Leaflet
GeoJSON admite los siguientes tipos de geometría: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon y GeometryCollection. Los features en GeoJSON contienen un objeto Geometry y propiedades adicionales, y una FeatureCollection contiene una lista de features. Por su parte, Leaflet es una librería JavaScript de código abierto que se utiliza para publicar mapas en la web. Es especialmente eficiente para trabajar con GeoJSON. Las opciones de Leaflet para GeoJSON son las siguientes:
- pointToLayer, es la opción utilizada para trabajar con puntos. Nos permite utilizar marcadores y trabajar con ellos
- style, es la función que se encarga de asignar los estilos de visualización de la capa.
- onEachFeature, es una función que llama a cada una de las características creadas en el mapa y se emplea para adjuntar eventos o ventanas emergentes.
- filter, es la opción que nos permite seleccionar un elemento geográfico a partir de alguna de sus características.
- coordsToLatLng es una función que se utiliza para convertir coordenadas GeoJSON a LatLngs.
Leaflet en acción
Una vez que hemos repasado las opciones de Leaflet para trabajar con GeoJSON vamos a escribir un ejemplo que muestra su uso. Utilizaremos un archivo que contiene las coordenadas geográficas de los principales monumentos de la ciudad de Salamanca, junto con algunas de sus características, como el nombre, su estilo y el siglo de construcción. Por tanto, lo primero que hay que hacer es incluir la referencia al GeoJSON:
<script src="SalamancaMonumental.js"></script>
Creamos el mapa de Leaflet y añadimos una capa de teselas de OSM que nos sirva de base para el mapa:
var map = L.map('map', { center: [40.965, -5.664], zoom: 16, }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', }).addTo(map); var salamancaMonumental = L.layerGroup().addTo(map);
Cuando el mapa está configurado podemos empezar a trabajar con nuestra capa GeoJSON.
Asignando estilos
Definimos un estilo personalizado para los marcadores. En primer lugar asignamos las características de los marcadores que van a representar los diferentes monumentos de la ciudad de Salamanca:
var MarkerOptions = { radius: 8, fillColor: "#ff7800", color: "#000", weight: 1, opacity: 1, fillOpacity: 0.8 };
Como queremos que cada estilo artístico esté representado por un color necesitamos una función que asigne colores a las propiedades del GeoJSON:
function colorPuntos(d) { return d == "Románico" ? '#FF0000' : d == "Plateresco" ? '#00FF00' : d == "Renacimiento" ? '#0000FF' : d == "Barroco" ? '#FF00FF' : d == "Gótico" ? '#FFFF00' : '#000000'; };
A continuación definimos un estilo:
function estilo_monumentos (feature) { return{ radius: 7, fillColor: colorPuntos(feature.properties.Estilo), color: colorPuntos(feature.properties.Estilo), weight: 1, opacity : 1, fillOpacity : 0.8 }; };
Como se puede ver, hemos creado la función estilo_monumentos() que contiene la definición de un marcador. En esta ocasión diseñamos un marcador con forma de círculo con las características de radio, opacidad… definidas. El color que tendrá el marcador será el resultado de aplicar la función colorPuntos().
Ventanas emergentes
Para crear una ventana emergente que muestre los datos del punto seleccionado por el usuario construimos una nueva función:
function popup_monumentos (feature, layer) { layer.bindPopup("<div style=text-align:center><h3>"+feature.properties.Nombre+ "<h3></div><hr><table><tr><td>Estilo: "+feature.properties.Estilo+ "</td></tr><tr><td>Siglo: "+feature.properties.Siglo+ "</td></tr></table>", {minWidth: 150, maxWidth: 200}); };
La función popup_monumentos() utiliza el método .bindPopup para crear una tabla html que contendrá los elementos de cada una de los features del archivo GeoJSON.
L.geoJSON
Ahora que ya tenemos definidos los marcadores que vamos a utilizar, su estilo y hemos diseñado la ventana emergente, creamos la capa a partir de los datos GeoJSON. Se realiza mediante una instancia a L.geoJSON en que se pasan como opciones las funciones creadas anteriormente.
var monumentos = L.geoJSON(geojson, { pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, MarkerOptions); }, style:estilo_monumentos, onEachFeature: popup_monumentos }); salamancaMonumental.addLayer(monumentos);
El mapa que obtenemos es el siguiente:
Filtrando los datos GeoJSON
El filtrado de los datos es una de las operaciones más frecuentes en las aplicaciones web mapping. El usuario necesita poder interactuar con el mapa para seleccionar los datos según sus categorías. La opción filter de L.geoJSON nos permite realizar esta tarea fácilmente.
Para realizar la selección creamos un formulario que contendrá las opciones que deseamos elegir. El elemento principal de ese formulario es la función estiloSelect(), que se dispara cuando se realiza un evento onchange; es decir, cada vez que el usuario seleccione una opción se disparará la función:
<div id="panel" style = "margin-left:0px; background-color:#D3D3D3;"> <label>Selecciona un estilo artístico</label> <select id="estilo" onchange="estiloSelect()"> <option value="Militar" >Militar</option> <option value="Plateresco">Plateresco</option> <option value="Renacimiento" >Renacimiento</option> <option value="Barroco" >Barroco</option> <option value="Gótico" >Gótico</option> <option value="TODOS" >Todos</option> </select> </div>
La función estiloSelect() es la que se encarga ahora de crear la capa y añadirla al mapa. Siguiendo con el ejemplo anterior diseñamos una función que filtre los datos según el estilo de cada monumento:
function estiloSelect() { var miSelect = document.getElementById("estilo").value; var monumentos = L.geoJSON(geojson, { pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, MarkerOptions); }, filter: function(feature, layer) { if(miSelect != "TODOS") return (feature.properties.Estilo == miSelect ); else return true; }, style:estilo_monumentos, onEachFeature: popup_monumentos }); salamancaMonumental.clearLayers(); salamancaMonumental.addLayer(monumentos); }
Como vemos la función filter utiliza una condición if-else. Cuando el usuario selecciona un estilo artístico la función retorna la capa con los valores correspondientes, en el caso en que el usuario selecciona el valor «TODOS» la función devuelve todos los marcadores.
Puedes ver el mapa a continuación.
Código completo:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>Filtro GeoJSON Leaflet</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" /> <style> body { padding: 0; margin: 0; } html, body, #map { height: 95%; } </style> </head> <body onload="myFunction()"> <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script> <script src="SalamancaMonumental.js"></script> <div id="panel" style = "margin-left:0px; background-color:#D3D3D3;"> <label>Selecciona un estilo artístico</label> <select id="estilo" onchange="estiloSelect()"> <option value="Militar" >Militar</option> <option value="Plateresco">Plateresco</option> <option value="Renacimiento" >Renacimiento</option> <option value="Barroco" >Barroco</option> <option value="Gótico" >Gótico</option> <option value="TODOS" >Todos</option> </select> </div> <div id ="map"> </div> <script> var map = L.map('map', { center: [40.965, -5.664], zoom: 16, }); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', }).addTo(map); var salamancaMonumental = L.layerGroup().addTo(map); function colorPuntos(d) { return d == "Románico" ? '#FF0000' : d == "Plateresco" ? '#00FF00' : d == "Renacimiento" ? '#0000FF' : d == "Barroco" ? '#FF00FF' : d == "Gótico" ? '#FFFF00' : '#000000'; }; function estilo_monumentos (feature) { return{ radius: 7, fillColor: colorPuntos(feature.properties.Estilo), color: colorPuntos(feature.properties.Estilo), weight: 1, opacity : 1, fillOpacity : 0.8 }; }; function popup_monumentos (feature, layer) { layer.bindPopup("<div style=text-align:center><h3>"+feature.properties.Nombre+ "<h3></div><hr><table><tr><td>Estilo: "+feature.properties.Estilo+ "</td></tr><tr><td>Siglo: "+feature.properties.Siglo+ "</td></tr></table>", {minWidth: 150, maxWidth: 200}); }; var MarkerOptions = { radius: 8, fillColor: "#ff7800", color: "#000", weight: 1, opacity: 1, fillOpacity: 0.8 }; function myFunction() { var monumentos = L.geoJSON(geojson, { pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, MarkerOptions); }, style:estilo_monumentos, onEachFeature: popup_monumentos }); salamancaMonumental.addLayer(monumentos); } function estiloSelect() { var miSelect = document.getElementById("estilo").value; var monumentos = L.geoJSON(geojson, { pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, MarkerOptions); }, filter: function(feature, layer) { if(miSelect != "TODOS") return (feature.properties.Estilo == miSelect ); else return true; }, style:estilo_monumentos, onEachFeature: popup_monumentos }); salamancaMonumental.clearLayers(); salamancaMonumental.addLayer(monumentos); } </script> </body> </html>
Si quieres aprender a crear visores web con Leaflet, inscríbete ya a nuestro curso online de web mapping interactivo con Leaflet.
Tutor del curso online de Análisis GeoEspacial con Python y de los cursos online de webmapping. Echa un vistazo a todos nuestros cursos de SIG online.
Hola, muchas gracias por el contenido que suben ayuda mucho para desarrollar aplicaciones.
Mi pregunta es la siguiente; es posible filtrar datos por dos atributos,por ejemplo filtrar por plateresco y militar al mismo tiempo. algo así.
feature.properties.Estilo == plateresco, militar
Hola Daniel:
Gracias por tu comentario. Puedes realizar filtros por dos elementos aunque no de la forma que propones. Tendrás que utilizar algunas operaciones adicionales.
¿Para trabajar con una herramienta web en general no muy pesada pero que carga ficheros de hasta unos 4000 polígonos, con folium (‘versión’ de python de leaflet) para representar y con geopandas como procesador de información geográfica y que recomendáis usar un SHP o usar un geojson de fondo?
Para manipular json esta tool es potentisima: jq
https://stedolan.github.io/jq/