Introducción

Govisor es una herramienta web que permite visualizar y analizar datos geoespaciales de forma interactiva. En este blog se detalla la elaboración de Govisor, incluyendo el código fuente completo y una explicación paso a paso.

Estructura del Proyecto

El proyecto consta de tres archivos principales:

  • index.html: Estructura de la página web.

  • styles.css: Estilo visual de la interfaz.

  • script.js: Lógica de la aplicación.

Código Fuente Completo

index.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Geovisor Catastral Trigal</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
    <link rel="stylesheet" href="styles.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <div id="map"></div>
    <div id="dashboard">
        <div class="chart-container">
            <canvas id="histogram"></canvas>
        </div>
    </div>

    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script src="script.js"></script>
</body>
</html>

styles.css

body {
    margin: 0;
    padding: 0;
    font-family: 'Roboto', sans-serif;
    background: #f5f6fa;
}

#map {
    height: 60vh;
    width: 100%;
    background: #e8e8e8;
    border-bottom: 2px solid #2c3e50;
}

#dashboard {
    padding: 25px;
    background: #ffffff;
    box-shadow: 0 -2px 15px rgba(0,0,0,0.1);
}

.chart-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 3px 15px rgba(0,0,0,0.1);
}

.leaflet-popup-content {
    min-width: 280px;
    font-size: 14px;
    line-height: 1.7;
}

.popup-header {
    color: #2c3e50;
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 2px solid #3498db;
}

.report-btn {
    background: #3498db;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 15px;
    width: 100%;
    transition: all 0.3s ease;
    font-size: 14px;
}

.report-btn:hover {
    background: #2980b9;
    transform: translateY(-1px);
    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.loading-message {
    color: #7f8c8d;
    font-style: italic;
    text-align: center;
    padding: 15px;
}

script.js


// Función para generar reporte
function generarReporte(idPredio) {
    // Implementar lógica de generación de reporte
    console.log("Generando reporte para:", idPredio);
    window.open(`http://localhost:8080/geoserver/munitrigal/wms?REQUEST=GetPrint&FORMAT=application/pdf&LAYERS=munitrigal:vista_catastro_completo&FEATUREID=${idPredio}`);
}

// Configuración inicial del mapa
const map = L.map('map').setView([-18.32, -64.13], 14);

// Capas base
var googleSat = L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
    maxZoom: 14,
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
});

var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; OpenStreetMap contributors'
});

// Capas WMS desde GeoServer
var parcelas = L.tileLayer.wms("http://localhost:8080/geoserver/munitrigal/wms", {
    layers: "munitrigal:vista_catastro_completo",
    format: "image/png",
    transparent: true
});

var vertices = L.tileLayer.wms("http://localhost:8080/geoserver/munitrigal/wms", {
    layers: "munitrigal:vertices",
    format: "image/png",
    transparent: true
});

var vias = L.tileLayer.wms("http://localhost:8080/geoserver/munitrigal/wms", {
    layers: "munitrigal:vias",
    format: "image/png",
    transparent: true
});

// Control de capas
var baseMaps = {
    "Google Earth": googleSat,
    "OpenStreetMap": osm
};

var overlayMaps = {
    "Parcelas": parcelas,
    "Vértices": vertices,
    "vias": vias
};

L.control.layers(baseMaps, overlayMaps).addTo(map);
googleSat.addTo(map);

// Evento click para popup con reporte - VERSIÓN CORREGIDA
map.on('click', async (e) => {
    try {
        const mapBounds = map.getBounds();
        const pixelPoint = map.latLngToContainerPoint(e.latlng);
       
        const wmsParams = new URLSearchParams({
            service: 'WMS',
            version: '1.3.0',
            request: 'GetFeatureInfo',
            layers: 'munitrigal:vista_catastro_completo',
            query_layers: 'munitrigal:vista_catastro_completo',
            info_format: 'application/json',
            propertyName: 'nompred,beneficiario,sup_pred,idpredio',
            crs: 'EPSG:32720',
            bbox: `${mapBounds.getSouthWest().lng},${mapBounds.getSouthWest().lat},${mapBounds.getNorthEast().lng},${mapBounds.getNorthEast().lat}`,
            width: map.getSize().x,
            height: map.getSize().y,
            i: Math.round(pixelPoint.x),
            j: Math.round(pixelPoint.y)
        });

        const wmsUrl = `http://localhost:8080/geoserver/munitrigal/wms?${wmsParams}`;
        console.log('URL de consulta:', wmsUrl); // Para depuración

        const response = await fetch(wmsUrl);
       
        if (!response.ok) {
            throw new Error(`Error HTTP: ${response.status}`);
        }
       
        const data = await response.json();
        console.log('Respuesta GeoServer:', data); // Para depuración

        if (data.features.length > 0) {
            const props = data.features[0].properties;
            const contenido = `
                <div class="popup-header">${props.nompred || 'Predio sin nombre'}</div>
                <p><b>Beneficiario:</b> ${props.beneficiario || 'No registrado'}</p>
                <p><b>Superficie:</b> ${props.sup_pred ? `${props.sup_pred} m²` : 'N/A'}</p>
                <p><b>ID Predio:</b> ${props.idpredio || 'Sin ID'}</p>
                <button class="report-button"
                        onclick="generarReporte('${props.idpredio}')">
                    📄 Generar Reporte PDF
                </button>
            `;
           
            L.popup()
                .setLatLng(e.latlng)
                .setContent(contenido)
                .openOn(map);
        } else {
            L.popup()
                .setLatLng(e.latlng)
                .setContent('⚠️ No se encontraron datos en esta ubicación')
                .openOn(map);
        }
    } catch (error) {
        console.error('Error en la solicitud:', error);
        L.popup()
            .setLatLng(e.latlng)
            .setContent('❌ Error al cargar los datos')
            .openOn(map);
    }
});

// Dashboard
const cargarDashboard = async () => {
    try {
        const url = new URL("http://localhost:8080/geoserver/munitrigal/ows");
        const params = {
            SERVICE: 'WFS',
            VERSION: '2.0.0',
            REQUEST: 'GetFeature',
            TYPENAMES: 'munitrigal:vista_catastro_completo',
            OUTPUTFORMAT: 'application/json',
            PROPERTYNAME: 'beneficiario,sup_pred'
        };

        Object.entries(params).forEach(([key, value]) =>
            url.searchParams.append(key, value));

        const response = await fetch(url);
        const data = await response.json();
       
        const dataset = data.features.reduce((acc, feature) => {
            const ben = feature.properties.beneficiario;
            const sup = parseFloat(feature.properties.sup_pred);
            if (ben && !isNaN(sup)) acc[ben] = (acc[ben] || 0) + sup;
            return acc;
        }, {});

        new Chart(document.getElementById('histogram'), {
            type: 'bar',
            data: {
                labels: Object.keys(dataset),
                datasets: [{
                    label: 'Superficie (m²)',
                    data: Object.values(dataset),
                    backgroundColor: '#3498db',
                    borderColor: '#2980b9'
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: { beginAtZero: true }
                }
            }
        });
    } catch (error) {
        console.error('Error en dashboard:', error);
        document.getElementById('dashboard').innerHTML = `
            <div class="error">
                Error al cargar datos: ${error.message}
            </div>
        `;
    }
};

// Iniciar
document.addEventListener('DOMContentLoaded', cargarDashboard);

Explicación del Código

  • index.html crea la estructura básica de la web y define el contenedor donde se mostrará el mapa interactivo.

  • styles.css define el diseño visual, destacando colores y bordes para mejorar la experiencia del usuario.

  • script.js controla la interactividad del proyecto, simulando la carga de datos geoespaciales.

Conclusión

Este ejemplo proporciona una base sólida para desarrollar una aplicación web de visualización geoespacial. Puedes ampliarlo integrando bibliotecas como Leaflet, OpenLayers o Mapbox para una experiencia más completa.