<!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> .canvascontainer {width: 500px; height: auto; border: 1px solid lightblue} #map {display: inline-block; width: 500px; height: 500px; border: 1 px solid lightblue;} #info {display: inline-block;} </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' /> <script> "use strict"; let chart1 = null, chart2 = null; function quantile(arr, p) { let len = arr.length, id; if ( p === 0.0 ) { return arr[ 0 ]; } if ( p === 1.0 ) { return arr[ len-1 ]; } id = ( len*p ) - 1; // [2] Is the index an integer? if ( id === Math.floor( id ) ) { // Value is the average between the value at id and id+1: return ( arr[ id ] + arr[ id+1 ] ) / 2.0; } // [3] Round up to the next index: id = Math.ceil( id ); return arr[ id ]; } function niceNumbers (range, round) { const exponent = Math.floor(Math.log10(range)); const fraction = range / Math.pow(10, exponent); let niceFraction; if (round) { if (fraction < 1.5) niceFraction = 1; else if (fraction < 3) niceFraction = 2; else if (fraction < 7) niceFraction = 5; else niceFraction = 10; } else { if (fraction <= 1.0) niceFraction = 1; else if (fraction <= 2) niceFraction = 2; else if (fraction <= 5) niceFraction = 5; else niceFraction = 10; } return niceFraction * Math.pow(10, exponent); } function getLinearTicks (min, max, maxTicks) { const range = niceNumbers(max - min, false); let tickSpacing; if (range === 0) { tickSpacing = 1; } else { tickSpacing = niceNumbers(range / (maxTicks), true); } return { min: Math.floor(min / tickSpacing) * tickSpacing, max: Math.ceil(max / tickSpacing) * tickSpacing, tickWidth: tickSpacing }; } let map = null; function initMap() { map = new mapboxgl.Map({ container: 'map', "style": { "version": 8, "name": "DefaultBaseStyle", "id": "defaultbasestyle", "glyphs": `https://free.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=`, "sources": { "osmgrijs": { "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": "osmgrijs", "type": "raster", "source": "osmgrijs" } ] } }); 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); }); } const colors1 = ["#686868", "#e60000", "#000000", "#faaa00", "#734c00", "#b2b2b2", "#267300", "#beffdc", "#ffff73", "#a3ff73", "#e9ffbe", "#b4d79e", "#7ed2fc", "#ffffff"]; const colors = ["#14a4ab", "#a9a9a9", "#4e4e4e", "#67ae00", '#febe00', "#e2fe5f", "#ada4fe", '#e8beff', '#d7d79e', '#0afeb3', '#a83800', '#73dfff','#69d5b4', '#bee8ff', '#97dbf2', '#feee00', '#e9ffbe', '#97dbf2', '#b1d600', '#73b2ff', '#97dbf2', '#fefac2', '#fe8419', '#7ab6f5', '#000000', '#00e6a9', '#b39d3d', '#97fe00', '#97fe00', '#ffeabe', '#ae974b', '#97dbf2', '#bed2ff', '#73dfff' ,'#97dbf2', '#ffbebe', '#ac7a9d'] const greenScheme = [{red:247, green:252, blue:253}, {red:0, green:68, blue:27}]; function hex (i) { let result = i.toString(16); if (result.length < 2) { result = '0' + result; } return result; } function createRangeColors(classLabels) { const length = classLabels.length; const result = []; if (length > 1) { const redStep = (greenScheme[0].red - greenScheme[1].red) / (length - 1); const greenStep = (greenScheme[0].green - greenScheme[1].green) / (length - 1); const blueStep = (greenScheme[0].blue - greenScheme[1].blue) / (length - 1); for (let i = 0; i < length; i++) { result.push('#' + hex(greenScheme[0].red - Math.round(redStep * i)) + hex(greenScheme[0].green - Math.round(greenStep * i)) + hex(greenScheme[0].blue - Math.round(blueStep * i))); result.push(classLabels[i]); } result.push('rgba(0,0,0,1)'); } return result; } function showMapLayer(fullTableName, geomType, attrName, attrType, classLabels) { let layerType, paint; const classLabelAndColors = classLabels.reduce((result, key, index)=>{ result.push(key); result.push(colors[index % colors.length]) return result; }, []); const rangeColors = createRangeColors(classLabels); let styledColors; if (classLabels.length < 2) { styledColors = colors[0]; } else { switch (attrType) { case 'varchar': styledColors = [ "match", ["get", attrName], ...classLabelAndColors, colors[0] ]; break; case 'numeric': styledColors = [ "step", ["to-number", ["get", attrName]], ...rangeColors ] break; default: styledColors = [ "step", ["get", attrName], ...rangeColors ] } } switch (geomType) { case 'MULTIPOLYGON': case 'POLYGON': layerType = 'fill'; paint = { "fill-color": styledColors, "fill-outline-color": "black", "fill-opacity": 0.8 } break; case 'MULTILINESTRING': case 'LINESTRING': layerType = 'line'; paint = { "line-color": styledColors, "line-width": 1 } break; case 'MULTIPOINT': case 'POINT': layerType = 'circle'; paint = { "circle-radius": 5, "circle-color": styledColors, "circle-stroke-color": "black", "circle-stroke-width" : 1 } break; default: document.querySelector("#layerjson").innerHTML = `Field geom of type: '${geomType}' not supported<br>Supported types: (MULTI-) POINT/LINE/POLYGON<p>` } if (layerType){ const baseUrl = new URL(`/data`, window.location.href).href; const url = `${baseUrl}/${fullTableName}/mvt/{z}/{x}/{y}?columns="${attrName}"` console.log(`url: ${url}`); const layer = { "id": "attrlayer", "type": layerType, "source": { "type": "vector", "tiles": [url], }, "source-layer": fullTableName, "paint": paint, "filter": ['has', attrName] } map.addLayer(layer); document.querySelector("#layerjson").innerHTML = `<pre>${JSON.stringify(layer, null, 2)}</pre>`; } } function init() { initMap(); 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'); document.querySelector('#tablename').innerHTML = fullTableName; document.querySelector('#columnname').innerHTML = `${attrName} (${attrType})`; document.querySelector('#back').innerHTML = `<a href="tableinfo.html?table=${fullTableName}">Terug naar layer informatie</a>` const parts = fullTableName.split('.'); const tableName = (parts.length > 1) ? parts[1] : parts[0]; fetch(`data/query/${fullTableName}?columns=count("${attrName}"),count(distinct+"${attrName}")+as+distinct,min("${attrName}"),max("${attrName}")`).then(response=>{ if (response.ok) { response.json().then(json=> { const div = document.querySelector('#attrinfo'); div.innerHTML = `<b>aantal</b>:${json[0].count}<br> <b>verschillend</b>:${json[0].distinct}<br> <b>min</b>:${json[0].min}<br> <b>max</b>:${json[0].max}<br>`; }) } }) fetch(`data/query/${fullTableName}?columns="${attrName}"`).then(response=>{ if (response.ok) { response.json().then(json=> { let histogram, verdeling; let histLabels = [], verdLabels = [], classBorders = []; let arr; switch(attrType) { case "varchar": arr = json .map(item=>item[attrName]) .filter(value=>value !== null) .sort() histogram = new Map(); for (let i = 0; i < arr.length; i++) { const value = arr[i]; const count = histogram.get(value); histogram.set(value, count?count+1:1); } histLabels = Array.from(histogram.keys()); classBorders = histLabels; break; case "int4": case "int8": case "float8": case "numeric": verdeling = new Map(); histogram = new Map(); arr = json .map(item=>{ let result = item[attrName]; if (typeof result === "string") { result = Number(result); if (isNaN(result)) { result = undefined; } } return result; }) .filter(value=>value !== null) .filter(value=>value !== undefined) .sort((value1,value2)=>value1-value2) const sampleInterval = Math.ceil(arr.length / 100); for (let i = 0; i * sampleInterval < arr.length; i++) { verdeling.set(i, arr[i*sampleInterval]); verdLabels.push(`${1 + i * sampleInterval}`); } const min = arr[0]; const max = arr[arr.length - 1]; const iqr = quantile(arr, 0.75) - quantile(arr, 0.25); let bins; if (max === min) { bins = 1 } else { bins = Math.round((max - min) / (2 * (iqr/Math.pow(arr.length, 1/3)))); } if (bins > 100) { bins = 100; } const linearTicks = getLinearTicks(min, max, bins); const binwidth = linearTicks.tickWidth; console.log(`count: ${arr.length}, min: ${min}, max: ${max}, iqr: ${iqr}, bins: ${bins}, binwidth: ${binwidth} linearTicks.min: ${linearTicks.min}, linearTicks.max: ${linearTicks.max}, linearTicks.tickWidth: ${linearTicks.tickWidth}`) bins = Math.ceil((linearTicks.max - linearTicks.min) / binwidth); if (bins < 1) { bins = 1; } for (let i = 0; i < bins; i++) { histogram.set(i, 0); const start = Math.round(10000 * (linearTicks.min + i * binwidth)) / 10000; const end = Math.round(10000 * (linearTicks.min + (i+1) * binwidth)) / 10000; if (linearTicks.max === linearTicks.min) { histLabels.push(`${start}`) classBorders.push(start); } else { histLabels.push(`${start} - ${end}`); classBorders.push(end); } } for (let i = 0; i < arr.length; i++) { const value = arr[i]; const bin = Math.floor((value - linearTicks.min)/binwidth); histogram.set(bin, histogram.get(bin) + 1); } break; } if (histogram) { const values = Array.from(histogram.values()); const canvas = document.querySelector('#histogram'); const ctx = canvas.getContext('2d'); if (chart1) { chart1.destroy(); } chart1 = new Chart(ctx, { type: 'bar', data: { labels: histLabels, datasets: [{ label: `aantal ${tableName}`, data: values, backgroundColor: "blue", borderColor: "white", borderWidth: 1 }] }, options: { title: { display: true, position: 'bottom', padding: 0, text: attrName }, scales: { yAxes: [{ ticks: { beginAtZero: true } }] } } }); showMapLayer(fullTableName, geomType, attrName, attrType, classBorders); } if (verdeling) { const values = Array.from(verdeling.values()); const canvas = document.querySelector('#verdeling'); const ctx = canvas.getContext('2d'); if (chart2) { chart2.destroy(); } chart2 = new Chart(ctx, { type: 'line', data: { labels: verdLabels, datasets: [{ label: `${attrName}`, data: values, backgroundColor: "blue", borderColor: "white", borderWidth: 1 }] }, options: { title: { display: true, position: 'bottom', padding: 0, text: tableName }, scales: { yAxes: [{ ticks: { beginAtZero: true } }] } } }); } }) } }) } </script> </head> <body onload="init()"> <h1>Attribuut-informatie</h1> <h2 id="tablename"></h2> <h2 id="columnname"></h2> <div id="attrinfo"></div> <div class="canvascontainer"><canvas id="histogram" width=1000 height=500></canvas></div> <div class="canvascontainer"><canvas id="verdeling" width=1000 height=500></canvas></div> <div id="map"></div> <div id="info"></div> <div id="layerjson"></div> <div id="back"></div> </body> </html>