Saltar a contenido

5. Interactuando con el mapa

Autores

Control de capas

Pendiente

Algunos controles sencillos más

Vista de pájaro

A este tipo de control se le suele llamar Overview, Vista de Pájaro, MiniMap, Localizador, ...

Hay varios plugins para Leaflet que añaden esta funcionalidad usaremos Leaflet.MiniMap. Al contrario que el plugin de Leaflet no es una representación exacta del mapa principal si no que le pasamos una capa que será la que se visualice en el localizador.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interacción con Leaflet</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <link rel="stylesheet" href="http://norkart.github.io/Leaflet-MiniMap/Control.MiniMap.css">
    <style>
        html,
        body,
        .map {
            height: 100%;
            margin: 0px;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script src="http://norkart.github.io/Leaflet-MiniMap/Control.MiniMap.js"></script>
    <script>
        let map = L.map('map').setView([42.2, -8.8], 12);
        let PNOA = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        }).addTo(map);
        let PNOAOverview = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        });
        var miniMap = new L.Control.MiniMap(PNOAOverview).addTo(map);

    </script>
</body>
</html>

Posición del ratón

De nuevo existen bastantes plugins con distintas variantes de como mostrar las coordenadas del ratón, o del centro del mapa, o capturar las coordenadas al hacer click.

Es una implementación sencilla, que dejamos como deberes para casa. Simplemente añade un elemento html en el que pintar las coordenadas. Escucha el evento mousemove. Y pinta o transforma a otras coordenadas la propiedad latlng del evento.

Hagamos un ejemplo con el plugin Leaflet.Coordinates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interacción con Leaflet</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <link rel="stylesheet" href="http://norkart.github.io/Leaflet-MiniMap/Control.MiniMap.css">
    <link rel="stylesheet" href="http://mrmufflon.github.io/Leaflet.Coordinates/dist/Leaflet.Coordinates-0.1.3.css">
    <style>
        html,
        body,
        .map {
            height: 100%;
            margin: 0px;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script src="http://norkart.github.io/Leaflet-MiniMap/Control.MiniMap.js"></script>
    <script src="http://mrmufflon.github.io/Leaflet.Coordinates/dist/Leaflet.Coordinates-0.1.3.min.js"></script>
    <script>
        let map = L.map('map').setView([42.2, -8.8], 12);
        let PNOA = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        }).addTo(map);
        let PNOAOverview = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        });
        var miniMap = new L.Control.MiniMap(PNOAOverview).addTo(map);
        L.control.coordinates({
            position: 'bottomleft',
            labelTemplateLat: "{y}",
            labelTemplateLng: "{x}, ",
            enableUserInput: false,
            useLatLngOrder: false,
        }).addTo(map);

    </script>
</body>
</html>

Interactuando con el servidor

Fijate en que hemos usado el parámetro transparent para que podamos pintar dos capas raster una encima de otra sin que se oculten la una a la otra. Y el formato png, dado que jpeg, el formato por defecto no admite transparencia.

Los eventos en Leaflet funcionan de modo parecido a OpenLayers. En Leaflet escucharemos el click que nos devolverá, un objeto MouseEvent, cuya propiedad latlng nos dará las coordenadas del punto. Pero getFeatureInfo espera unas coordenadas en pixeles correspondientes a la imagen.

Dado que no disponemos de un método nativo para obtener la url del getFeatureInfo debemos construírla a mano. Lo haremos en modo hack, no usar en producción.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interactuando con el servidor con Leaflet</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <style>
        html,
        body {
            height: 100%;
            margin: 0px;
        }
        .map,
        #info {
            height: 50%;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <div id="info" style="width: 100%;"></div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script>
        let map = L.map('map').setView([42.2, -8.8], 12);
        // http://maps.stamen.com/
        // o con un plugin: https://github.com/leaflet-extras/leaflet-providers
        L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
            attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
        }).addTo(map);
        let baseURL = 'http://wms.mapama.es/sig/Agua/RedHidrograficaMDT/wms.aspx'
        let rios = L.tileLayer.wms(baseURL, {
            layers: 'HY.PhysicalWaters.Waterbodies',
            transparent: true,
            format: 'image/png',
        }).addTo(map);
        map.on('click', function(e) {
            let url = buildURL(e);
            console.log(url.href)
            document.getElementById('info').innerHTML = '<iframe seamless src="' + url + '" style="width: 98%; height: 98%"></iframe>';
        })
        let defaultParameters = {
            service: 'WFS',
            version: '1.3.0',
            info_format: 'text/html',
            query_layers: 'HY.PhysicalWaters.Waterbodies',
            layers: 'HY.PhysicalWaters.Waterbodies',
            styles: '',
            transparent: true,
            format: 'image/png',
            request: 'GetFeatureInfo',
            crs: 'EPSG:4326',
        }
        const buildURL = function(e) {
            let height = map.getSize().y;
            let width = map.getSize().x;
            let bounds = map.getBounds();
            let west = bounds.getWest();
            let south = bounds.getSouth();
            let east = bounds.getEast();
            let north = bounds.getNorth();
            let bbox = `${south},${west},${north},${east},EPSG:4326`
            let i = e.containerPoint.x;
            let j = e.containerPoint.y;
            const params = Object.assign({}, {
                bbox,
                i,
                j,
                height,
                width,
            }, defaultParameters);
            const urlParams = new URLSearchParams(params);
            const url = new URL(baseURL);
            for (let pair of urlParams.entries()) {
                url.searchParams.set(pair[0], pair[1])
            }
            return url;
        }

    </script>
</body>
</html>

Leaflet y un plugin de WMS

Hasta ahora habíamos empleado la clase de Leaflet L.TileLayer.WMS para interactuar con este tipo de servicios. Pero existe un plugin Leaflet.wms que aporta algunas funcionalidades a mayores como el realizar de forma automática un getFeatureInfo y visualizar los resultados.

Prueba los ejemplos del plugin y pruébalo con la capa WMS que estamos usando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interactuando con el servidor con Leaflet</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <style>
        html,
        body {
            height: 100%;
            margin: 0px;
        }
        .map,
        #info {
            height: 50%;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <div id="info" style="width: 100%;"></div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <!-- <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> -->
    <script src="http://heigeo.github.io/leaflet.wms/dist/leaflet.wms.js"></script>
    <script>
        var MySource = L.WMS.Source.extend({
            getFeatureInfo: function(point, latlng, layers, callback) {
                // Request WMS GetFeatureInfo and call callback with results
                // (split from identify() to faciliate use outside of map events)
                var params = this.getFeatureInfoParams(point, layers),
                    url = this._url + L.Util.getParamString(params, this._url);
                if (url) {
                    document.getElementById('info').innerHTML = '<iframe seamless src="' + url + '" style="width: 100%; height: 300px"></iframe>';
                }
            }
        });
        let map = L.map('map').setView([42.2, -8.8], 12);
        L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
            attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
        }).addTo(map);
        let baseURL = 'http://wms.mapama.es/sig/Agua/RedHidrograficaMDT/wms.aspx'
        var source = new MySource(baseURL, {
            'transparent': true,
            'format': 'image/png',
            'info_format': 'text/html',
        });
        source.getLayer('HY.PhysicalWaters.Waterbodies').addTo(map);

    </script>
</body>
</html>

En este ejemplo sobreescribimos la funcionalidad relacionada con getFeatureInfo para mostrar los resultados en un iframe

Interactuando en el cliente

Pedimos todas las features como GeoJSON a un servidor WFS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interactuando con el cliente</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <style>
        html,
        body,
        .map {
            height: 100%;
            margin: 0px;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script>
        let map = L.map('map', {
            center: [40.25, -3.68],
            zoom: 6,
            crs: L.CRS.EPSG4326,
        });
        let pnoaOrthoWMS = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        }).addTo(map);
        var baseURL = 'https://cors-anywhere.herokuapp.com/http://ideadif.adif.es/gservices/Tramificacion/wfs'
        let defaultParameters = {
            service: 'WFS',
            version: '1.1.0',
            request: 'GetFeature',
            typename: 'Tramificacion:TramosFueraServicio',
            outputFormat: 'application/json',
            srsname: map.options.crs.code,
        }
        const buildURL = function() {
            const params = Object.assign({}, defaultParameters);
            const urlParams = new URLSearchParams(params);
            const url = new URL(baseURL);
            for (let pair of urlParams.entries()) {
                url.searchParams.set(pair[0], pair[1])
            }
            return url;
        }
        let url = buildURL();
        fetch(url).then(response => response.json()).then(json => {
            console.log(json);
            L.geoJSON(json, {
                style: {
                    color: 'red',
                    stroke: 2,
                }
            }).addTo(map);
        });

    </script>
</body>
</html>

En Leaflet no existe el concepto de interaction.Select. Lo más habitual cuando se quiere interactuar con features concretas de una capa, para por ejemplo poner un popup al pulsar sobre una feature, cambiar el color al pasar el botón por encima, ... se realiza añadiendo un listener a la interacción del usuario sobre esa Feature concreta.

Para ello Leaflet provee un hook onEachFeature, que es un método que recibe cada una de las features durante la carga de la capa y les aplica la función definida.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Interactuando con el cliente</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css">
    <style>
        html,
        body {
            height: 100%;
            margin: 0px;
        }
        .map {
            height: 75%;
        }

    </style>
</head>
<body>
    <div id="map" class="map"></div>
    <div id="div-data-features">
        <table id="data-features"></table>
    </div>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script>
        // Utilidad para crear una tabla
        const createTableFromFeature = e => {
            const div = document.getElementById('div-data-features');
            let table = document.getElementById('data-features');
            if (table) {
                div.removeChild(table)
                table = document.createElement('table');
                table.id = 'data-features';
                div.appendChild(table);
            }
            const properties = e.target.feature.properties;
            Object.keys(properties).forEach(key => {
                if (properties[key]) {
                    const row = table.insertRow(0);
                    const cell1 = row.insertCell(0);
                    const cell2 = row.insertCell(1);
                    cell1.innerHTML = key;
                    cell2.innerHTML = properties[key];
                }
            })
        }
        //
        let map = L.map('map', {
            center: [40.25, -3.68],
            zoom: 6,
            crs: L.CRS.EPSG4326,
        });
        let tramos;
        let pnoaOrthoWMS = L.tileLayer.wms('http://www.ign.es/wms-inspire/pnoa-ma?', {
            layers: 'OI.OrthoimageCoverage',
            attribution: 'PNOA cedido por © <a href="http://www.ign.es/ign/main/index.do" target="_blank">Instituto Geográfico Nacional de España</a>'
        }).addTo(map);
        var baseURL = 'https://cors-anywhere.herokuapp.com/http://ideadif.adif.es/gservices/Tramificacion/wfs'
        let defaultParameters = {
            service: 'WFS',
            version: '1.1.0',
            request: 'GetFeature',
            typename: 'Tramificacion:TramosFueraServicio',
            outputFormat: 'application/json',
            srsname: map.options.crs.code,
        }
        const buildURL = function() {
            const params = Object.assign({}, defaultParameters);
            const urlParams = new URLSearchParams(params);
            const url = new URL(baseURL);
            for (let pair of urlParams.entries()) {
                url.searchParams.set(pair[0], pair[1])
            }
            return url;
        }
        let url = buildURL();
        const highlightFeature = function(e) {
            var layer = e.target;
            layer.setStyle({
                weight: 5,
                color: '#666',
                dashArray: '',
                fillOpacity: 0.7
            });
            layer.bringToFront();
        }
        const resetHighlight = function(e) {
            tramos.resetStyle(e.target);
        }
        const onEachFeature = function(feature, layer) {
            layer.on({
                mouseover: highlightFeature,
                mouseout: resetHighlight,
                click: createTableFromFeature,
            });
        }
        fetch(url).then(response => response.json()).then(json => {
            tramos = L.geoJSON(json, {
                style: {
                    color: 'red',
                    stroke: 2,
                },
                onEachFeature: onEachFeature,
            }).addTo(map);
        });

    </script>
</body>
</html>