Commit 21a93c82 authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Remove unused pgserver components

parent 3d536e0c
// based on https://raw.githubusercontent.com/tobinbradley/dirt-simple-postgis-http-api/master/routes/bbox.js
const sqlTableName = require('./utils/sqltablename.js');
function splitTableName(params) {
const parts = params.table.split('.');
if (parts.length === 1) {
return {
namespace: 'public',
tablename: parts[0]
}
} else {
return {
namespace: parts[0],
tablename: parts[1]
}
}
}
const getSqlEstimateRows = (params) =>{
const ts = splitTableName(params);
return `select ((c.reltuples/case when c.relpages=0 then 1 else c.relpages end) * (pg_relation_size(c.oid) / (current_setting('block_size')::integer)))::integer as estimated_rows
from pg_class c JOIN pg_namespace n on c.relnamespace = n.oid
where n.nspname='${ts.namespace}' and c.relname='${ts.tablename}'
`;
}
const sqlEstimateBbox = (params, query, estimatedRows) => {
const ts = splitTableName(params);
return `
with srid as
(select st_srid(${query.geom_column}) srid, geometrytype(${query.geom_column}) geomtype
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null limit 1)
,bboxsrid as
(select ST_EstimatedExtent('${ts.namespace}', '${ts.tablename}', '${query.geom_column}') as bboxsrid)
,bboxll as
(select st_extent(st_transform(st_setsrid(st_envelope(bboxsrid), srid),4326)) bboxll
from bboxsrid, srid)
select ${estimatedRows} as allrows, ${estimatedRows} as geomrows,bboxll,srid,bboxsrid,geomtype
from bboxll,srid,bboxsrid
`;
}
const sqlBbox = (params, query) => {
return `
with srid as
(select st_srid(${query.geom_column}) srid, geometrytype(${query.geom_column}) geomtype
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null limit 1)
,bboxll as
(select ST_Extent(ST_Transform(${query.geom_column}, 4326)) as bboxll, count(*) allrows, count(${query.geom_column}) geomrows
from ${sqlTableName(params.table)}
-- Optional where filter
${query.filter ? `WHERE ${query.filter}` : '' }
)
,bboxsrid as
(select st_extent(st_transform(st_setsrid(st_envelope(bboxll),4326),srid)) bboxsrid
from bboxll,srid)
select allrows, geomrows, bboxll,srid,bboxsrid,geomtype
from bboxll,srid,bboxsrid
`;
}
module.exports = function(app, pool, cache) {
let cacheMiddleware = async(req, res, next) => {
if (!cache) {
next();
return;
}
const cacheDir = `${req.params.table}/bbox`
const key = ((req.query.geom_column?req.query.geom_column:'geom')
+ (req.query.columns?'_'+req.query.columns:'')
+ (req.query.filter?'_'+req.query.filter:''))
.replace(/[\W]+/g, '_');
const bbox = await cache.getCachedFile(cacheDir, key);
if (bbox) {
console.log(`bbox cache hit for ${cacheDir}?${key}`);
res.header('Content-Type', 'application/json').send(bbox);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
cache.setCachedFile(cacheDir, key, body);
}
res.sendResponse(body);
}
next();
}
}
/**
* @swagger
*
* /api/bbox/{table}:
* get:
* description: Gets the bounding box of a feature(s)
* tags: ['api']
* produces:
* - application/json
* parameters:
* - name: table
* description: name of postgis table
* in: path
* required: true
* type: string
* - name: geom_column
* description: name of geometry column (default 'geom')
* in: query
* required: false
* - name: filter
* type: string
* description: 'Optional filter parameters for a SQL WHERE statement.'
* in: query
* required: false
* responses:
* 200:
* description: boundingboxes and row counters
* content:
* application/json
* schema:
* type: object
* properties:
* estimated:
* type: boolean
* description: result is estimated (true) or precise (false)
* allrows:
* type: integer
* description: number of rows in table
* geomrows:
* type: integer
* description: number of non-null geometries in table
* bboxll:
* description: boundingbox of geometries defined in latitude and longitude
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: number
* srid:
* type: integer
* description: id of spatial reference system (EPSG code) used by geometries
* bboxsrid:
* description: boundingbox of geometries defined using geometry srid
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: number
* 422:
* description: invalid datasource or columnname
*/
app.get('/api/bbox/:table', cacheMiddleware, async (req, res)=>{
if (!req.query.geom_column) {
req.query.geom_column = 'geom'; //default
}
if (!req.query.srid) {
req.query.srid = 4326
}
let sql = getSqlEstimateRows(req.params);
let estimated = false;
try {
const estimateResult = await pool.query(sql);
const estimatedRows = estimateResult.rows[0].estimated_rows;
if (estimatedRows < 5000000 || req.query.filter) {
sql = sqlBbox(req.params, req.query);
} else {
sql = sqlEstimateBbox(req.params, req.query, estimatedRows);
estimated = true;
}
const result = await pool.query(sql);
if (result.rows.length === 1) {
const row = result.rows[0];
res.json({
estimated: estimated,
allrows: Number(row.allrows),
geomtype: row.geomtype,
geomrows: Number(row.geomrows),
srid: row.srid,
bboxll: row.bboxll?row.bboxll.match(/BOX\((.*)\)/)[1].split(',').map(coord=>coord.split(' ').map(c=>parseFloat(c))):null,
bboxsrid: row.bboxsrid?row.bboxsrid.match(/BOX\((.*)\)/)[1].split(',').map(coord=>coord.split(' ').map(c=>parseFloat(c))):null
})
} else if (result.rows.length === 0) {
res.json({
allrows: 0,
geomrows: 0,
bboxll: null,
srid: 0,
bboxsrid: null
})
} else {
throw(new Error('bbox query returned more than 1 row'));
}
} catch(err) {
console.log(err);
res.status(500).json({error: err.message});
}
})
}
\ No newline at end of file
const sqlTableName = require('./utils/sqltablename.js');
// get top 100 most frequent values and null values
const sql = (params, query) => {
return `
with sortedrecords as
(select count(1)::integer as "count", "${params.column}" as "value"
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null
group by "${params.column}" order by count(1) desc)
,toprecords as
(select * from sortedrecords limit 100)
,resultset as
(select * from toprecords
union
select * from sortedrecords where value is null)
select distinct * from resultset order by count desc;`
} // TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
// https://leafo.net/guides/postgresql-calculating-percentile.html
const sqlPercentiles = (params, query) => {
return `
select min(buckets.value) "from", max(buckets.value) "to", count(ntile)::integer "count", ntile as percentile
from
(select "${params.column}" as value, ntile(100) over (order by "${params.column}")
from ${sqlTableName(params.table)}
where "${params.column}" is not null and ${query.geom_column} is not null)
as buckets
group by ntile order by ntile;
`
}
const sqlPercentilesBoolean = (params, query) => {
return `
select case when min(buckets.value) = 0 then false else true end "from", case when max(buckets.value) = 0 then false else true end "to", count(ntile)::integer "count", ntile as percentile
from
(select "${params.column}"::integer as value, ntile(100) over (order by "${params.column}")
from ${sqlTableName(params.table)}
where "${params.column}" is not null and ${query.geom_column} is not null)
as buckets
group by ntile order by ntile;
`
}
let typeMap = null;
async function getTypeName(id, pool) {
if (!typeMap) {
const sql = "select oid,typname from pg_type where oid < 1000000 order by oid";
try {
const queryResult = await pool.query(sql);
typeMap = new Map(queryResult.rows.map(row=>[row.oid, row.typname]));
} catch(err) {
console.log(`error loading types: ${err}`);
return id.toString();
}
}
const result = typeMap.get(id);
if (!result) {
return id.toString();
}
return result;
}
module.exports = function(app, pool, cache) {
let cacheMiddleWare = async(req, res, next) => {
if (!cache) {
next();
return;
}
const cacheDir = `${req.params.table}/colstats/`;
const key = ((req.query.geom_column?req.query.geom_column:'geom') + (req.params.column?','+req.params.column:''))
.replace(/[\W]+/g, '_');
const stats = await cache.getCachedFile(cacheDir, key);
if (stats) {
console.log(`stats cache hit for ${cacheDir}?${key}`);
if (stats.length === 0) {
res.status(204)
}
res.header('Content-Type', 'application/json').send(stats);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
cache.setCachedFile(cacheDir, key, body);
}
res.sendResponse(body);
}
next();
}
}
/**
* @swagger
*
* /data/{table}/colstats/{column}:
* get:
* description: get statistics for column
* tags: ['meta']
* produces:
* - application/json
* parameters:
* - name: table
* description: name of postgis table
* in: path
* required: true
* type: string
* - name: column
* description: name of column
* in: path
* type: string
* required: true
* - name: geom_column
* description: name of geometry column (default 'geom')
* in: query
* required: false
* responses:
* 200:
* description: json statistics
* content:
* application/json
* schema:
* type: object
* properties:
* table:
* type: string
* description: name of table
* column:
* type: string
* description: name of column
* numvalues:
* description: number of different values, null means unknown (>2000)
* type: integer
* uniquevalues:
* description: whether or not all values are unique
* type: boolean
* values:
* description: array of values sorted by highest count first
* type: array
* maxItems: 2000
* items:
* type: object
* properties:
* value:
* description: encountered value for column (any type)
* type: string
* count:
* description: number of geometries with this value
* type: integer
* 204:
* description: no data
* 422:
* description: invalid table or column
* 500:
* description: unexpected error
*/
app.get('/data/:table/colstats/:column', cacheMiddleWare, async (req, res)=>{
if (!req.query.geom_column) {
req.query.geom_column = 'geom'; // default
}
let sqlString = sql(req.params, req.query);
//console.log(sqlString);
try {
let queryResult = await pool.query(sqlString);
let datatype = await getTypeName(queryResult.fields[1].dataTypeID, pool);
if (datatype === "numeric" || datatype === "int8") {
// numeric datatype, try to convert to Number
try {
queryResult.rows = queryResult.rows.map(row=>{row.value=row.value?Number(row.value):row.value; return row});
} catch(err) {
// failed Numeric conversion
}
}
const stats = queryResult.rows
const result = {
table: req.params.table,
column: req.params.column,
datatype: datatype,
numvalues: stats.length < 2000?stats.length:null,
uniquevalues: stats.length?stats[0].value !== null?stats[0].count === 1:stats.length>1?stats[1].count === 1:false:[],
values: stats
}
if (stats.length === 0) {
result.percentiles = [];
res.json(result);
return;
}
if (datatype === "bool") {
sqlString = sqlPercentilesBoolean(req.params, req.query);
} else {
sqlString = sqlPercentiles(req.params, req.query);
}
queryResult = await pool.query(sqlString);
if (datatype === "numeric" || datatype === "int8") {
// numeric datatype, try to convert to Number
try {
queryResult.rows = queryResult.rows.map(row=>{row.from=Number(row.from); row.to=Number(row.to); return row});
} catch(err) {
// failed Numeric conversion
}
}
result.percentiles = queryResult.rows;
res.json(result);
} catch(err) {
console.log(err);
let status = 500;
switch (err.code) {
case '42P01':
// table does not exist
status = 422;
break;
case '42703':
// column does not exist
status = 422;
break;
default:
}
res.status(status).json({error:err.message})
}
})
}
\ No newline at end of file
const sql = (params) => {
return `
SELECT
attname as field_name,
typname as field_type
FROM
pg_namespace, pg_attribute, pg_type, pg_class
WHERE
pg_type.oid = atttypid AND
pg_class.oid = attrelid AND
relnamespace = pg_namespace.oid AND
attnum >= 1 AND
relname = '${params.table}'
${params.schema?` AND nspname= '${params.schema}'`:''}
`
}
module.exports = function (app, pool) {
/**
* @swagger
*
* /data/layer_columns/:table:
* get:
* description: Returns a list of columns in the specified table.
* tags: ['meta']
* summary: 'list table columns'
* produces:
* - application/json
* parameters:
* - name: table
* description: The name of the table
* in: path
* required: true
* type: string
* responses:
* 200:
* description: list of columns (names and types)
* 422:
* description: table not found or not accessible
*/
app.get('/data/layer_columns/:table', async (req, res)=> {
let tableName, schemaName;
const table = req.params.table;
if (table) {
const parts = table.split('.');
if (parts.length === 1) {
tableName = parts[0];
schemaName = null;
} else {
schemaName = parts[0];
tableName = parts[1];
}
req.params.table = tableName;
req.params.schema = schemaName;
const sqlString = sql(req.params, req.query);
try {
const result = await pool.query(sqlString);
res.json(result.rows);
} catch (err) {
console.log(err);
let status = 500;
switch (err.code) {
case '42P01':
// table does not exist
status = 422;
break;
case '42703':
// column does not exist
status = 422;
break;
default:
}
res.status(status).json({error:err.message})
}
} else {
res.status(422).json({error:"missing parameter 'table'"})
}
});
}
\ No newline at end of file
function login(app) {
/**
* @swagger
*
* /login:
* post:
* description: Login to the application
* produces:
* - application/json
* parameters:
* - name: username
* description: Username to use for login.
* in: formData
* required: true
* type: string
* - name: password
* description: User's password.
* in: formData
* required: true
* type: string
* responses:
* 200:
* description: login
*/
app.post('/login', (req, res) => {
// Your implementation comes here ...
});
app.get('/login', (req, res) => {
res.json({result:"ok"})
});
}
module.exports = login
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<!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>
<script>
let layerInfo = allLayerInfo = null;
let sortFieldName = '';
let sortAscending = true;
function sortBy(name) {
let fieldname;
switch(name) {
case 'schema':
fieldname = 'f_table_schema';
break;
case 'name':
fieldname = 'f_table_name';
break;
case 'geom_column':
fieldname = 'f_geometry_column';
break;
case 'geom_type':
fieldname = 'type';
break;
case 'dim':
fieldname = 'coord_dimension';
break;
case 'type':
fieldname = 'table_type';
break;
case 'count':
fieldname = 'estimated_rows'
break;
default:
fieldname = name;
}
if (sortFieldName != fieldname) {
sortFieldName = fieldname;
sortAscending = true;
} else {
sortAscending = !sortAscending;
}
updateLayerInfo(fieldname, sortAscending);
}
function updateLayerInfo(field, ascending) {
const table = document.querySelector('#layerinfo');
switch (field) {
case 'f_table_schema':
layerInfo.sort((item1, item2) => ('' + item1.f_table_schema + item1.f_table_name).localeCompare(item2.f_table_schema + item2.f_table_name))
break;
case 'f_table_name':
layerInfo.sort((item1, item2) => ('' + item1[field]).localeCompare(item2[field]));
break;
case 'f_geometry_column':
case 'type':
case 'table_type':
case 'coord_dimension':
layerInfo.sort((item1, item2) => ('' + item1[field] + '.' + item1.f_table_schema + '.' + item1.f_table_name).localeCompare(item2[field] + '.' + item2.f_table_schema + '.' + item2.f_table_name));
break;
case 'srid':
layerInfo.sort((item1, item2)=>item1.srid-item2.srid?item1.srid-item2.srid:('' + item1.f_table_schema + item1.f_table_name).localeCompare(item2.f_table_schema + item2.f_table_name));
break;
case 'estimated_rows':
layerInfo.sort((item1, item2)=>item1.estimated_rows-item2.estimated_rows);
}
if (!ascending) {
layerInfo.reverse();
}
table.innerHTML = '<tr><th>schema</th><th>name</th><th>geom_column</th><th>srid</th><th>geom_type</th><th>dim</th><th>type</th><th>count</th></tr>' +
layerInfo.map(item=>`<tr>
<td>${item.f_table_schema}</td>
<td><a href="tableinfo.html?table=${item.f_table_schema}.${item.f_table_name}&geom_column=${item.f_geometry_column}&srid=${item.srid}&geomtype=${item.type}&dimensions=${item.coord_dimension}&estimated_rows=${item.estimated_rows}">${item.f_table_name}</a></td>
<td>${item.f_geometry_column}</td>
<td>${item.srid}</td>
<td>${item.type}</td>
<td>${item.coord_dimension}D</td>
<td>${item.table_type}</td>
<td>${item.estimated_rows}</td></tr>`).join('\n');
const tableHeaders = document.querySelectorAll('tr > th');
for (let i = 0; i < tableHeaders.length; i++) {
tableHeaders[i].innerHTML = `<a href="#" onclick="sortBy('${tableHeaders[i].textContent}')">${tableHeaders[i].textContent}</a>`
}
}
function filterLayerInfo() {
const filterWords = document.querySelector('#filter')
.value.split(' ')
.filter(word=>word !== '')
.map(word=>word.toLocaleLowerCase());
if (filterWords.length) {
layerInfo = allLayerInfo.filter(layer=>{
const layerText = Object.values(layer).join(' ').toLocaleLowerCase();
return filterWords.filter(word=>layerText.indexOf(word)>-1).length == filterWords.length;
})
} else {
layerInfo = allLayerInfo;
}
updateLayerInfo(sortFieldName, sortAscending);
}
function init() {
fetch('data/list_layers').then(response=>{
if (response.ok) {
response.json().then(json=> {
layerInfo = allLayerInfo = json;
filterLayerInfo();
sortBy('schema');
})
} else {
layerInfo = allLayerInfo = [];
response.json().then(json =>{
document.querySelector('#layerinfo').innerHTML = `<tr><td>Error fetching layers: ${json.error}</td></tr>`
}).catch(err => {
document.querySelector('#layerinfo').innerHTML = `<tr><td>Error fetching layers: ${err}</td></tr>`
});
}
})
}
</script>
</head>
<body onload="init()">
<h1>Layers in database</h1>
<input id="filter" type="text" placeholder="Search..." size="20" oninput="filterLayerInfo()"><br>
<table id="layerinfo"></table>
</body>
</html>
\ No newline at end of file
/* source https://codepen.io/TheDutchCoder/pen/gholk */
/* The loader container */
.loader {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
margin-top: -100px;
margin-left: -100px;
perspective: 400px;
/* transform-type: preserve-3d; */
}
/* The dot */
.dot {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
width: 40px;
height: 40px;
margin-top: -20px;
margin-left: -80px;
/* transform-type: preserve-3d;*/
transform-origin: 80px 50%;
transform: rotateY(0);
background-color: #1e3f57;
animation: dot1 2000ms cubic-bezier(.56,.09,.89,.69) infinite;
}
.dot:nth-child(2) {
z-index: 9;
animation-delay: 150ms;
}
.dot:nth-child(3) {
z-index: 8;
animation-delay: 300ms;
}
.dot:nth-child(4) {
z-index: 7;
animation-delay: 450ms;
}
.dot:nth-child(5) {
z-index: 6;
animation-delay: 600ms;
}
.dot:nth-child(6) {
z-index: 5;
animation-delay: 750ms;
}
.dot:nth-child(7) {
z-index: 4;
animation-delay: 900ms;
}
.dot:nth-child(8) {
z-index: 3;
animation-delay: 1050ms;
}
@keyframes dot1 {
0% {
transform: rotateY(0) rotateZ(0) rotateX(0);
background-color: #1e3f57;
}
45% {
transform: rotateZ(180deg) rotateY(360deg) rotateX(90deg);
background-color: #6bb2cd;
animation-timing-function: cubic-bezier(.15,.62,.72,.98);
}
90%, 100% {
transform: rotateY(0) rotateZ(360deg) rotateX(180deg);
background-color: #1e3f57;
}
}
\ No newline at end of file
body,html {
font-family: Verdana, Geneva, Tahoma, sans-serif
}
<!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>
<script>
function replaceGeomType(url, geomtype) {
if (geomtype) {
const parts = url.split('&')
.map(part=>{
if (part.startsWith('geomtype=')) {
part = `geomtype=${geomtype}`
}
return part;
})
url = parts.join('&');
}
return url;
}
function addBboxllToLinks(bboxll,geomtype)
{
if (bboxll) {
document.querySelectorAll('#columns > li > a')
.forEach(a=>(a.setAttribute('href', replaceGeomType(a.getAttribute('href'),geomtype) + '&bboxll='+JSON.stringify(bboxll))))
}
}
function init() {
const urlParams = new URLSearchParams(window.location.search);
const fullTableName = urlParams.get('table');
const geomType = urlParams.get('geomtype');
const geomcolumn = urlParams.get('geom_column');
let bboxll = null;
document.querySelector('#tablename').innerHTML = fullTableName;
const parts = fullTableName.split('.');
const tableName = (parts.length > 1) ? parts[1] : parts[0];
fetch(`data/layer_columns/${fullTableName}`).then(response=>{
const list = document.querySelector('#columns');
if (response.ok) {
response.json().then(json=> {
for (item of json) {
const li = document.createElement('li');
li.innerHTML = `<a href="./attrinfo.html?table=${fullTableName}&geom_column=${geomcolumn?geomcolumn:''}&column=${item.field_name}&columntype=${item.field_type}&geomtype=${geomType}"><b>${item.field_name}</b></a> (${item.field_type})`
list.appendChild(li);
}
fetch(`api/bbox/${fullTableName}${geomcolumn?`?geom_column=${geomcolumn}`:''}`).then(response=>{
const bbox = document.querySelector('#bbox');
if (response.ok) {
response.json().then(json=> {
addBboxllToLinks(json.bboxll,json.geomtype);
bbox.innerHTML = `number of rows: ${json.allrows}<br>
number of geometries: ${json.geomrows}<br>
srid: EPSG:${json.srid}<br>
bbox lon/lat: ${json.bboxll?`sw: ${json.bboxll[0][0]},${json.bboxll[0][1]}, ne: ${json.bboxll[1][0]},${json.bboxll[1][1]}`: 'not defined'}<br>
bbox (EPSG:${json.srid}): ${json.srid?`ll: ${json.bboxsrid[0][0]},${json.bboxsrid[0][1]}, tr: ${json.bboxsrid[1][0]},${json.bboxsrid[1][1]}`: 'not defined'}<br>
`
})
} else {
response.json().then(json=>{
bbox.innerHTML = `Error getting bbox: ${json.error}`;
}).catch(err => {
bbox.innerHTML = `Error parsing bbox: ${err}`;
})
}
})
})
} else {
const li = document.createElement('li');
response.json().then(json=>{
li.innerHTML = `Error getting column info: ${json.error}`;
}).catch(err => {
li.innerHTML = `Error parsing column info: ${err}`;
})
list.appendChild(li);
}
})
}
</script>
</head>
<body onload="init()">
<h1>Layer info</h1>
<h2 id="tablename"></h2>
<div id="bbox">waiting for table stats...</div>
<ul id="columns"></ul>
<a href="info.html">Back to layer overview</a>
</body>
</html>
\ No newline at end of file
function roundToPrecision(number, precision, direction) {
let negative = (number < 0);
if (negative) {
number = -number;
direction = -direction;
}
let roundFunc = (direction < 0 ? Math.floor : direction === 0 ? Math.round : Math.ceil);
let exponent = (number === 0)?1:Math.floor(Math.log10(number));
let decimals = (exponent < precision)? precision - exponent : 0;
let fraction = number / Math.pow(10,exponent);
return Number((Math.pow(10, exponent) * roundFunc(fraction * Math.pow(10, precision)) / Math.pow(10, precision) * (negative ? -1 : 1)).toFixed(decimals));
}
function getIntervalClassTicks (min, max, classCount) {
let niceMin = roundToPrecision(min, 2, -1);
let niceMax = roundToPrecision(max, 2, 1);
let interval = (niceMax - niceMin) / classCount;
let result = [];
for (let i = 0; i < classCount; i++) {
result.push(roundToPrecision(niceMin + i * interval, 2, -1))
}
return {
min: niceMin,
max: niceMax,
classes: result
};
}
<!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>Upload</title>
<!-- Filepond stylesheet -->
<link href="pgserver.css" rel="stylesheet">
</head>
<body>
<form action="upload" method="post" encType="multipart/form-data">
<input name="uploadfile" type="file"><br>
<input type="submit" value="upload">
</form>
</body>
</html>
\ No newline at end of file
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerDefinition = {
info: {
// API informations (required)
title: 'PGServer', // Title (required)
version: '0.0.1', // Version (required)
description: 'PostGIS http api', // Description (optional)
},
tags: [
{
name: 'meta',
description: 'meta information for tables and views'
},
{
name: 'geodata',
description: 'features in common formats for direct mapping'
}
],
basePath: '/', // Base path (optional)
}
const swaggerJSDocOptions = {
swaggerDefinition,
apis: [
'./login.js',
'./mvt.js',
'./list_layers.js',
'./layer_columns.js',
'./bbox.js',
'./geojson.js',
'./geobuf.js',
'./query.js',
'./column_stats.js'
]
}
const swaggerSpec = swaggerJSDoc(swaggerJSDocOptions);
module.exports = function(app) {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
}
const express = require('express');
const fs = require('fs');
const fileUpload = require('express-fileupload');
module.exports = function(app) {
app.use(fileUpload({
useTempFiles: true,
tempFileDir : `${__dirname}/public/files/`
}));
app.post('/upload', (req, res) => {
let uploadFile = req.files.uploadfile;
const fileName = uploadFile.name;
res.json({
file: `${fileName}`
})
uploadFile.mv(
`${__dirname}/public/files/${fileName}`,
function (err) {
if (err) {
return res.status(500).send(err);
}
}
)
});
app.get('/upload', (req, res) =>{
url = req.query.fetch;
console.log(url);
res.json({
file: 'index.html'
});
})
app.delete('/upload', express.json({type: '*/*'}), (req, res) => {
fs.unlinkSync(`${__dirname}/public/files/${req.body.file}`);
res.json({
file: 'done'
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment