Commit bea45a66 authored by Anne Blankert's avatar Anne Blankert

work in progress

parent eb43cd31
// route query
const sql = (params, query) => {
return `
SELECT
ST_Extent(ST_Transform(${query.geom_column}, ${query.srid})) as bbox
FROM
${params.table}
-- Optional where filter
${query.filter ? `WHERE ${query.filter}` : '' }
`
}
module.exports = function(app, pool) {
/**
* @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: strid
* description: 'The SRID for the returned centroid. The default is <em>4326</em> WGS84 Lat/Lng.'
* in: query
* required: false
* type: integer
* - name: filter
* type: string
* description: 'Optional filter parameters for a SQL WHERE statement.'
* in: query
* required: false
* responses:
* 200:
* description: vector tile
* 204:
* description: no data (empty tile)
* 422:
* description: invalid datasource or columnname
*/
app.get('/api/bbox/:table', async (req, res)=>{
if (!req.query.geom_column) {
req.query.geom_column = 'geom'; //default
}
if (!req.query.srid) {
req.query.srid = 4326
}
const sqlString = sql(req.params, req.query);
try {
const result = await pool.query(sqlString);
res.json(result.rows);
} catch(err) {
console.log(err);
res.status(500).json({error: err});
}
})
}
\ No newline at end of file
const {Pool} = require('pg');
const dbconfig = require('./config/dbconfig.json');
const pool = new Pool(dbconfig);
pool.connect();
const sm = require('@mapbox/sphericalmercator')
const merc = new sm({
size: 256
});
// route query
const sql = (params, query) => {
let bounds = query.bounds ? query.bounds.split(',').map(Number) : null;
bounds && bounds.length === 3 ? bounds = merc.bbox(bounds[1], bounds[2], bounds[0]) : null;
return `
SELECT
Row_to_json(fc) as geojson
FROM (
SELECT
'FeatureCollection' AS type,
COALESCE(Array_to_json(Array_agg(f)), '[]'::json) AS features
FROM (
SELECT
'Feature' AS type,
St_asgeojson(ST_Transform(lg.${query.geom_column}, 4326))::json AS geometry,
${query.columns ? `
Row_to_json(
(
SELECT
l
FROM
(SELECT ${query.columns}) AS l
)
) AS properties
` : `'{}'::json AS properties`}
FROM
${params.table} AS lg
${bounds ? `, (SELECT ST_SRID(${query.geom_column}) as srid FROM ${params.table} LIMIT 1) sq` : ''}
-- Optional Filter
${query.filter || bounds ? 'WHERE' : ''}
${query.filter ? `${query.filter}` : '' }
${query.filter && bounds ? 'AND' : ''}
${bounds ? `
${query.geom_column} &&
ST_Transform(
ST_MakeEnvelope(${bounds.join()}, 4326),
srid
)
` : ''}
) AS f
) AS fc;
`
}
module.exports = function(app, pool) {
/**
* @swagger
*
* /data/geojson/{table}:
* get:
* description: return table as geojson
* tags: ['geodata']
* produces:
* - application/json
* parameters:
* - name: table
* description: name of table or view
* in: path
* required: true
* type: string
* - name: geom_column
* description: name of geometry column (default 'geom')
* in: query
* required: false
* - name: columns
* description: optional comma seperated list of attribute columns to be added to the mvt geometries
* in: query
* required: false
* type: string
* - name: filter
* description: 'Optional filter parameters for a SQL WHERE statement.'
* in: query
* type: string
* required: false
* - name: bounds
* description: 'Optionally limit output to features that intersect bounding box. Can be expressed as a bounding box (sw.lng, sw.lat, ne.lng, ne.lat) or a Z/X/Y tile (0,0,0).'
* in: query
* type: string
* pattern: '^-?[0-9]{0,20}.?[0-9]{1,20}?(,-?[0-9]{0,20}.?[0-9]{1,20}?){2,3}$'
* responses:
* 200:
* description: geojson
* 422:
* description: invalid datasource or columnname
*/
app.get('/data/geojson/:table', async (req, res) => {
if (!req.query.geom_column) {
req.query.geom_column = 'geom';
}
const sqlString = sql(req.params, req.query);
try {
const result = await pool.query(sqlString);
res.json(result.rows[0].geojson)
} catch(err) {
console.log(err);
switch (err.code) {
case '42P01':
err.name = `table ${req.params.table} does not exist`;
break;
case '42703':
err.name = `column does not exist`;
break;
}
res.status(422).json({error:err})
}
})
}
\ No newline at end of file
const sql = () => {
return `
SELECT current_database()::character varying(256) AS f_table_catalog,
n.nspname AS f_table_schema,
c.relname AS f_table_name,
a.attname AS f_geometry_column,
COALESCE(postgis_typmod_dims(a.atttypmod), sn.ndims, 2) AS coord_dimension,
COALESCE(NULLIF(postgis_typmod_srid(a.atttypmod), 0), sr.srid, 0) AS srid,
replace(replace(COALESCE(NULLIF(upper(postgis_typmod_type(a.atttypmod)), 'GEOMETRY'::text), st.type, 'GEOMETRY'::text), 'ZM'::text, ''::text), 'Z'::text, ''::text)::character varying(30) AS type
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid AND NOT a.attisdropped
JOIN pg_namespace n ON c.relnamespace = n.oid
JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN ( SELECT s.connamespace,
s.conrelid,
s.conkey,
replace(split_part(s.consrc, ''''::text, 2), ')'::text, ''::text) AS type
FROM pg_constraint s
WHERE s.consrc ~~* '%geometrytype(% = %'::text) st ON st.connamespace = n.oid AND st.conrelid = c.oid AND (a.attnum = ANY (st.conkey))
LEFT JOIN ( SELECT s.connamespace,
s.conrelid,
s.conkey,
replace(split_part(s.consrc, ' = '::text, 2), ')'::text, ''::text)::integer AS ndims
FROM pg_constraint s
WHERE s.consrc ~~* '%ndims(% = %'::text) sn ON sn.connamespace = n.oid AND sn.conrelid = c.oid AND (a.attnum = ANY (sn.conkey))
LEFT JOIN ( SELECT s.connamespace,
s.conrelid,
s.conkey,
replace(replace(split_part(s.consrc, ' = '::text, 2), ')'::text, ''::text), '('::text, ''::text)::integer AS srid
FROM pg_constraint s
WHERE s.consrc ~~* '%srid(% = %'::text) sr ON sr.connamespace = n.oid AND sr.conrelid = c.oid AND (a.attnum = ANY (sr.conkey))
WHERE (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'm'::"char", 'f'::"char", 'p'::"char"])) AND NOT c.relname = 'raster_columns'::name AND t.typname = 'geometry'::name AND NOT pg_is_other_temp_schema(c.relnamespace) AND has_table_privilege(c.oid, 'SELECT'::text);
`;
}
module.exports = function(app, pool) {
/**
* @swagger
*
* /data/list_layers:
* get:
* description: get list of available tables
* tags: ['meta']
* summary: 'list PostGIS layers'
* produces:
* - application/json
* responses:
* 200:
* description: list of layers
*/
app.get('/data/list_layers', async (req, res)=>{
try {
const sqlString = sql()
const result = await pool.query(sqlString);
const layers = result.rows
res.json(layers)
} catch(err) {
res.json({error: err})
}
})
}
\ No newline at end of file
const sm = require('@mapbox/sphericalmercator'); const sm = require('@mapbox/sphericalmercator');
const fs = require('fs'); const fs = require('fs');
const {Pool} = require('pg');
const fsPromises = fs.promises;
const path = require('path');
const merc = new sm({ const merc = new sm({
size: 256 size: 256
}) })
const dbconfig = require('./config/dbconfig.json');
const pool = new Pool(dbconfig);
pool.connect();
// route query // route query
const sql = (params, query) => { const sql = (params, query) => {
let bounds = merc.bbox(params.x, params.y, params.z, false, '900913') let bounds = merc.bbox(params.x, params.y, params.z, false, '900913')
...@@ -55,18 +46,19 @@ const sql = (params, query) => { ...@@ -55,18 +46,19 @@ const sql = (params, query) => {
) q ) q
` `
} } // TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
// TODO add flat-cache // TODO add flat-cache
module.exports = function(app) { module.exports = function(app, pool) {
/** /**
* @swagger * @swagger
* *
* /data/{datasource}/mvt/{z}/{x}/{y}?columns={columns}: * /data/{datasource}/mvt/{z}/{x}/{y}:
* get: * get:
* description: get mapbox vector tile (mvt) * description: get mapbox vector tile (mvt)
* tags: ['geodata']
* produces: * produces:
* - application/x-protobuf * - application/x-protobuf
* parameters: * parameters:
...@@ -93,15 +85,12 @@ module.exports = function(app) { ...@@ -93,15 +85,12 @@ module.exports = function(app) {
* - name: geom_column * - name: geom_column
* description: name of geometry column (default 'geom') * description: name of geometry column (default 'geom')
* in: query * in: query
* default: geom
* required: false * required: false
* - name: columns * - name: columns
* description: optional comma seperated list of attribute columns to be added to the mvt geometries * description: optional comma seperated list of attribute columns to be added to the mvt geometries
* in: query * in: query
* required: false * required: false
* type: string * type: string
* default: null
* allowEmptyValue: true
* responses: * responses:
* 200: * 200:
* description: vector tile * description: vector tile
...@@ -112,11 +101,11 @@ module.exports = function(app) { ...@@ -112,11 +101,11 @@ module.exports = function(app) {
*/ */
app.get('/data/:datasource/mvt/:z/:x/:y', async (req, res)=>{ app.get('/data/:datasource/mvt/:z/:x/:y', async (req, res)=>{
if (!req.query.geom_column) { if (!req.query.geom_column) {
req.query.geom_column = 'geom'; req.query.geom_column = 'geom'; // default
} }
req.params.table = req.params.datasource; req.params.table = req.params.datasource;
const sqlString = sql(req.params, req.query); const sqlString = sql(req.params, req.query);
console.log(sqlString); //console.log(sqlString);
try { try {
const result = await pool.query(sqlString); const result = await pool.query(sqlString);
const mvt = result.rows[0].st_asmvt const mvt = result.rows[0].st_asmvt
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"description": "PGserver receives, converts and serves geo-data between postgis and http-clients.", "description": "PGserver receives, converts and serves geo-data between postgis and http-clients.",
"main": "pgserver.js", "main": "pgserver.js",
"scripts": { "scripts": {
"start": "nodemon pgserver.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "Anne Blankert, Geodan", "author": "Anne Blankert, Geodan",
......
...@@ -6,16 +6,22 @@ const cors = require('cors'); ...@@ -6,16 +6,22 @@ const cors = require('cors');
const app = express(); const app = express();
const swagger = require('./swagger.js')(app);
app.use(logger('dev')); app.use(logger('dev'));
app.use(cors()); app.use(cors());
app.use('/', express.static(__dirname + '/public')); app.use('/', express.static(__dirname + '/public'));
const {Pool} = require('pg');
const dbconfig = require('./config/dbconfig.json');
const readOnlyPool = new Pool(dbconfig);
readOnlyPool.connect();
const swagger = require('./swagger.js')(app);
const login = require('./login.js')(app); const login = require('./login.js')(app);
const upload = require('./upload.js')(app); const upload = require('./upload.js')(app);
const mvt = require('./mvt.js')(app); const mvt = require('./mvt.js')(app, readOnlyPool);
const geojson = require('./geojson.js')(app, readOnlyPool);
const list_layers = require('./list_layers')(app, readOnlyPool);
const bbox = require('./bbox.js')(app, readOnlyPool);
app.listen(pgserverconfig.port); app.listen(pgserverconfig.port);
console.log(`pgserver listening on port ${pgserverconfig.port}`); console.log(`pgserver listening on port ${pgserverconfig.port}`);
......
...@@ -5,15 +5,25 @@ const swaggerDefinition = { ...@@ -5,15 +5,25 @@ const swaggerDefinition = {
info: { info: {
// API informations (required) // API informations (required)
title: 'PGServer', // Title (required) title: 'PGServer', // Title (required)
version: '1.0.0', // Version (required) version: '0.0.1', // Version (required)
description: 'PostGIS http api', // Description (optional) 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) basePath: '/', // Base path (optional)
} }
const swaggerJSDocOptions = { const swaggerJSDocOptions = {
swaggerDefinition, swaggerDefinition,
apis: ['./login.js', './mvt.js'] apis: ['./login.js', './mvt.js', './list_layers.js', './bbox.js', './geojson.js']
} }
const swaggerSpec = swaggerJSDoc(swaggerJSDocOptions); const swaggerSpec = swaggerJSDoc(swaggerJSDocOptions);
......
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