<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Info</title> <style> .medium {width: 300px; height: auto;} #map {display: inline-block; width: 500px; height: 500px; border: 1 px solid lightblue;} #info {display: inline-block;} #attrinfo { display: flex; flex-wrap: wrap; } #attrinfo div { margin: 5px; } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script> <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.js'></script> <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.css' rel='stylesheet' /> <link href='loader.css' rel='stylesheet' /> <script> "use strict"; let map = null; function init() { const urlParams = new URLSearchParams(window.location.search); const fullTableName = urlParams.get('table'); const attrName = urlParams.get("column"); const attrType = urlParams.get("columntype"); const geomType = urlParams.get('geomtype'); const geomColumn = urlParams.get('geom_column'); document.querySelector('#tablename').innerHTML = `${fullTableName} ${geomColumn}`; document.querySelector('#columnname').innerHTML = `${attrName} (${attrType})`; document.querySelector('#back').innerHTML = `<a href="tableinfo.html?table=${fullTableName}&geom_column=${geomColumn}">Back to layer info</a>`; initMap(); fetch(`data/${fullTableName}/colstats/${attrName}?geom_column=${geomColumn}`).then(response=>{ const attrInfoElement = document.querySelector('#attrinfo'); attrInfoElement.innerHTML = ""; if (!response.ok) { try { response.json(json=>attrInfoElement.innerHtml = json.error); } catch(err) { attrInfoElement.innerHTML = err; } return; } response.json() .then(json=>{ if (json.datatype === 'timestamptz' || json.datatype === 'date') { json.percentiles = json.percentiles.map(percentile=>{ percentile.from = percentile.from?new Date(percentile.from):null; percentile.to = percentile.to?new Date(percentile.to):null; return percentile; }) json.values = json.values.map(value=>{ value.value = value.value?new Date(value.value):null; return value; }) } graphStats(json) }) .catch(err=> attrInfoElement.innerHTML = `Failed to parse response, message: ${err.message}` ); }) } function initMap() { const urlParams = new URLSearchParams(window.location.search); const fullTableName = urlParams.get('table'); const geomType = urlParams.get('geomtype'); const geomColumn = urlParams.get('geom_column'); const attrName = urlParams.get("column"); const mapDefinition = { container: 'map', "style": { "version": 8, "name": "DefaultBaseStyle", "id": "defaultbasestyle", "glyphs": `https://free.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=`, "sources": { "osmgray": { "type":"raster", "tileSize":256, "tiles":[ "https://tiles.edugis.nl/mapproxy/osm/tiles/osmgrayscale_EPSG900913/{z}/{x}/{y}.png?origin=nw" ], "attribution":"© <a href=\"https://www.openstreetmap.org/about\" target=\"copyright\">OpenStreetMap contributors</a>" } }, "layers": [ { "id": "osmgray", "type": "raster", "source": "osmgray" } ], "filter": [ "has", `${attrName}` ] } } const bboxll = urlParams.get('bboxll'); if (bboxll) { mapDefinition.bounds = JSON.parse(bboxll); } map = new mapboxgl.Map(mapDefinition); map.on('load', () => { let layerType, paint; switch (geomType) { case 'MULTIPOLYGON': case 'POLYGON': layerType = 'fill'; paint = { "fill-color": "red", "fill-outline-color": "white", "fill-opacity": 0.8 } break; case 'MULTILINESTRING': case 'LINESTRING': layerType = 'line'; paint = { "line-color": "red", "line-width": 1 } break; case 'MULTIPOINT': case 'POINT': layerType = "circle"; paint = { "circle-radius": 5, "circle-color": "red", "circle-stroke-color": "white", "circle-stroke-width" : 1 } break; default: break; } if (!layerType) { document.querySelector("#layerjson").innerHTML = `Field geom of type: '${geomType}' not supported<br>Supported types: (MULTI-) POINT/LINE/POLYGON<p>` } else { const baseUrl = new URL(`/data`, window.location.href).href; const layer = { "id": "attrlayer", "type": layerType, "source": { "type": "vector", "tiles": [`${baseUrl}/${fullTableName}/mvt/{z}/{x}/{y}?geom_column=${geomColumn}&columns=${attrName}`], }, "source-layer": fullTableName, "paint": paint, "filter": ['has', attrName] } map.addLayer(layer); document.querySelector("#layerjson").innerHTML = `<pre>${JSON.stringify(layer, null, 2)}</pre>`; } }) map.on('mousemove', function (e) { var features = map.queryRenderedFeatures(e.point).map(function(feature){ return {layer: {id: feature.layer.id, type: feature.layer.type}, properties:(feature.properties)};}); document.getElementById('info').innerHTML = JSON.stringify(features.map(feature=>feature.properties), null, 2); }); } function addCanvas() { const container = document.createElement('div'); container.classList.add('canvascontainer'); const canvas = document.createElement('canvas'); container.classList.add('medium'); container.appendChild(canvas); document.querySelector('#attrinfo').appendChild(container); return canvas; } function graphStats(stats) { const nullValues = stats.values.filter(value=>value.value === null).reduce((result, value)=>result+value.count,0); const rowCount = stats.percentiles.reduce((result, percentile)=>result + percentile.count, 0); new Chart(addCanvas(), { type: 'doughnut', data: { labels: ['null','non-null',], datasets: [{ backgroundColor: ['lightgray', 'red'], borderColor: 'white', borderWidth: 0, data: [nullValues, rowCount] }] } }); if (stats.percentiles.length && typeof(stats.percentiles[0].from) !== 'string') { new Chart(addCanvas(), { type: 'line', data: { labels: stats.percentiles.map((percentile, index, arr)=>Math.round((index/(arr.length - 1)) * 100)), datasets: [{ backgroundColor: 'red', borderColor: 'lightred', data: stats.percentiles.map((percentile,index,arr)=>(index===arr.length-1)?percentile.to:percentile.from), fill: false }] }, options : { title: { display: false, text : 'some title' }, legend: { display: false }, scales: { yAxes: [ { scaleLabel: { display: true, labelString: stats.column } } ], xAxes: [ { scaleLabel: { display: true, labelString: 'percentage of rows', padding: 0 } } ] } } }) } const valuesSummary = stats.values.filter(value=>value.value !== null).slice(0,10); const valuesSummeryRowCount = valuesSummary.reduce((result, value)=>result+value.count,0); if (rowCount > valuesSummeryRowCount) { valuesSummary.push({ value:"other", count: rowCount - valuesSummeryRowCount }) } new Chart(addCanvas(), { type: "horizontalBar", data: { labels: valuesSummary.map(value=>value.value), datasets: [ { backgroundColor: "red", data: valuesSummary.map(value=>value.count) } ] }, options : { legend: { display: false, }, scales: { xAxes: [ { ticks: { min: 0 } } ] } } }) } </script> </head> <body onload="init()"> <h1>Attribute information</h1> <h2 id="tablename"></h2> <h2 id="columnname"></h2> <div id="attrinfo"> <h2>Loading statistics...</h2> <div class="loader"> <div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div> <div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div> </div> </div> <div id="map"></div> <div id="info"></div> <div id="layerjson"></div> <div id="back"></div> </body> </html>