Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
fordecyt_2019
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Rodrigo Tapia-McClung
fordecyt_2019
Commits
169d5717
Commit
169d5717
authored
Aug 17, 2019
by
Anne Blankert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
globalize cache, add column_stats
parent
efe0c9de
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
223 additions
and
54 deletions
+223
-54
README.md
README.md
+1
-1
column_stats.js
column_stats.js
+144
-0
mvt.js
mvt.js
+37
-30
pgserver.js
pgserver.js
+12
-5
attrinfo.html
public/attrinfo.html
+1
-1
tableinfo.html
public/tableinfo.html
+17
-16
swagger.js
swagger.js
+11
-1
No files found.
README.md
View file @
169d5717
...
@@ -19,7 +19,7 @@ If you don't have git, you can donwload [a zip file](https://github.com/anneb/pg
...
@@ -19,7 +19,7 @@ If you don't have git, you can donwload [a zip file](https://github.com/anneb/pg
npm install
npm install
cp config/dbconfig.example.json config/dbconfig.json
cp config/dbconfig.example.json config/dbconfig.json
# now edit config/dbconfig.json for your PostGis database
# now edit config/dbconfig.json for your PostGis database
n
pm start
n
ode pgserver.js
# point your browser to localost:8090 for more info
# point your browser to localost:8090 for more info
For interactive data browsing, preview, administration and api documentation, head to
[
http://localhost:8090
](
http://localhost:8090
)
.
For interactive data browsing, preview, administration and api documentation, head to
[
http://localhost:8090
](
http://localhost:8090
)
.
...
...
column_stats.js
0 → 100644
View file @
169d5717
const
sqlTableName
=
require
(
'./utils/sqltablename.js'
);
const
sql
=
(
params
,
query
)
=>
{
return
`
select count(1)::integer as "count",
${
params
.
column
}
as "value"
from
${
params
.
table
}
where
${
query
.
geom_column
}
is not null
group by
${
params
.
column
}
order by count(1) desc limit 2000;
`
}
// TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
module
.
exports
=
function
(
app
,
pool
,
cache
)
{
let
cacheMiddleWare
=
async
(
req
,
res
,
next
)
=>
{
if
(
!
cache
)
{
next
();
return
;
}
const
cacheDir
=
`
${
req
.
params
.
table
}
/attrstats/`
;
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
}
const
sqlString
=
sql
(
req
.
params
,
req
.
query
);
//console.log(sqlString);
try
{
const
result
=
await
pool
.
query
(
sqlString
);
const
stats
=
result
.
rows
if
(
stats
.
length
===
0
)
{
res
.
status
(
204
).
json
({});
return
;
}
res
.
json
({
table
:
req
.
params
.
table
,
column
:
req
.
params
.
column
,
numvalues
:
stats
.
length
<
2000
?
stats
.
length
:
null
,
uniquevalues
:
stats
[
0
].
value
!==
null
?
stats
[
0
].
count
===
1
:
stats
.
length
>
1
?
stats
[
1
].
count
===
1
:
false
,
values
:
stats
})
}
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
mvt.js
View file @
169d5717
// based on https://raw.githubusercontent.com/tobinbradley/dirt-simple-postgis-http-api/master/routes/mvt.js
// based on https://raw.githubusercontent.com/tobinbradley/dirt-simple-postgis-http-api/master/routes/mvt.js
const
sqlTableName
=
require
(
'./utils/sqltablename.js'
);
const
sqlTableName
=
require
(
'./utils/sqltablename.js'
);
const
DirCache
=
require
(
'./utils/dircache.js'
)
const
cache
=
new
DirCache
(
'./cache'
);
const
sm
=
require
(
'@mapbox/sphericalmercator'
);
const
sm
=
require
(
'@mapbox/sphericalmercator'
);
const
merc
=
new
sm
({
const
merc
=
new
sm
({
size
:
256
size
:
256
})
})
let
cacheMiddleWare
=
async
(
req
,
res
,
next
)
=>
{
function
queryColumnsNotNull
(
queryColumns
)
{
const
cacheDir
=
`
${
req
.
params
.
datasource
}
/mvt/
${
req
.
params
.
z
}
/
${
req
.
params
.
x
}
/
${
req
.
params
.
y
}
`
;
if
(
queryColumns
)
{
const
key
=
((
req
.
query
.
geom_column
?
req
.
query
.
geom_column
:
'geom'
)
+
(
req
.
query
.
columns
?
','
+
req
.
query
.
columns
:
''
))
return
` and (
${
queryColumns
.
split
(
','
).
map
(
column
=>
`
${
column
}
is not null`
).
join
(
' or '
)}
)`
.
replace
(
/
[\W]
+/g
,
'_'
);
}
return
''
const
mvt
=
await
cache
.
getCachedFile
(
cacheDir
,
key
);
if
(
mvt
)
{
console
.
log
(
`cache hit for
${
cacheDir
}
?
${
key
}
`
);
if
(
mvt
.
length
===
0
)
{
res
.
status
(
204
)
}
res
.
header
(
'Content-Type'
,
'application/x-protobuf'
).
send
(
mvt
);
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
();
}
}
}
const
sql
=
(
params
,
query
)
=>
{
const
sql
=
(
params
,
query
)
=>
{
...
@@ -57,17 +36,45 @@ const sql = (params, query) => {
...
@@ -57,17 +36,45 @@ const sql = (params, query) => {
ST_MakeEnvelope(
${
bounds
.
join
()}
, 3857),
ST_MakeEnvelope(
${
bounds
.
join
()}
, 3857),
srid
srid
) &&
${
query
.
geom_column
}
) &&
${
query
.
geom_column
}
${
queryColumnsNotNull
(
query
.
columns
)}
-- Optional Filter
-- Optional Filter
${
query
.
filter
?
`AND
${
query
.
filter
}
`
:
''
}
${
query
.
filter
?
`AND
${
query
.
filter
}
`
:
''
}
) r
) r
) q
) q
`
`
}
// TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
}
// TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
// TODO add flat-cache
module
.
exports
=
function
(
app
,
pool
,
cache
)
{
module
.
exports
=
function
(
app
,
pool
)
{
let
cacheMiddleWare
=
async
(
req
,
res
,
next
)
=>
{
if
(
!
cache
)
{
next
();
return
;
}
const
cacheDir
=
`
${
req
.
params
.
datasource
}
/mvt/
${
req
.
params
.
z
}
/
${
req
.
params
.
x
}
/
${
req
.
params
.
y
}
`
;
const
key
=
((
req
.
query
.
geom_column
?
req
.
query
.
geom_column
:
'geom'
)
+
(
req
.
query
.
columns
?
','
+
req
.
query
.
columns
:
''
))
.
replace
(
/
[\W]
+/g
,
'_'
);
const
mvt
=
await
cache
.
getCachedFile
(
cacheDir
,
key
);
if
(
mvt
)
{
console
.
log
(
`cache hit for
${
cacheDir
}
?
${
key
}
`
);
if
(
mvt
.
length
===
0
)
{
res
.
status
(
204
)
}
res
.
header
(
'Content-Type'
,
'application/x-protobuf'
).
send
(
mvt
);
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
* @swagger
*
*
...
@@ -121,7 +128,7 @@ module.exports = function(app, pool) {
...
@@ -121,7 +128,7 @@ module.exports = function(app, pool) {
}
}
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
...
...
pgserver.js
View file @
169d5717
...
@@ -15,18 +15,25 @@ const dbconfig = require('./config/dbconfig.json');
...
@@ -15,18 +15,25 @@ const dbconfig = require('./config/dbconfig.json');
const
readOnlyPool
=
new
Pool
(
dbconfig
);
const
readOnlyPool
=
new
Pool
(
dbconfig
);
readOnlyPool
.
connect
();
readOnlyPool
.
connect
();
const
DirCache
=
require
(
'./utils/dircache.js'
)
const
cache
=
new
DirCache
(
`./cache/
${
dbconfig
.
database
?
dbconfig
.
database
:
process
.
env
.
PGDATABASE
?
process
.
env
.
PGDATABASE
:
''
}
`
);
const
swagger
=
require
(
'./swagger.js'
)(
app
);
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
,
readOnlyPool
);
const
mvt
=
require
(
'./mvt.js'
)(
app
,
readOnlyPool
,
cache
);
const
geojson
=
require
(
'./geojson.js'
)(
app
,
readOnlyPool
);
const
geojson
=
require
(
'./geojson.js'
)(
app
,
readOnlyPool
);
const
geobuf
=
require
(
'./geobuf.js'
)(
app
,
readOnlyPool
);
const
geobuf
=
require
(
'./geobuf.js'
)(
app
,
readOnlyPool
);
const
list
_l
ayers
=
require
(
'./list_layers.js'
)(
app
,
readOnlyPool
);
const
list
L
ayers
=
require
(
'./list_layers.js'
)(
app
,
readOnlyPool
);
const
layer
_c
olumns
=
require
(
'./layer_columns.js'
)(
app
,
readOnlyPool
);
const
layer
C
olumns
=
require
(
'./layer_columns.js'
)(
app
,
readOnlyPool
);
const
bbox
=
require
(
'./bbox.js'
)(
app
,
readOnlyPool
);
const
bbox
=
require
(
'./bbox.js'
)(
app
,
readOnlyPool
,
cache
);
const
query
=
require
(
'./query.js'
)(
app
,
readOnlyPool
);
const
query
=
require
(
'./query.js'
)(
app
,
readOnlyPool
);
const
columnStats
=
require
(
'./column_stats.js'
)(
app
,
readOnlyPool
,
cache
);
const
server
=
app
.
listen
(
pgserverconfig
.
port
);
server
.
setTimeout
(
600000
);
app
.
listen
(
pgserverconfig
.
port
);
console
.
log
(
`pgserver listening on port
${
pgserverconfig
.
port
}
`
);
console
.
log
(
`pgserver listening on port
${
pgserverconfig
.
port
}
`
);
module
.
exports
=
app
;
module
.
exports
=
app
;
public/attrinfo.html
View file @
169d5717
...
@@ -245,7 +245,7 @@
...
@@ -245,7 +245,7 @@
const
geomColumn
=
urlParams
.
get
(
'geom_column'
);
const
geomColumn
=
urlParams
.
get
(
'geom_column'
);
document
.
querySelector
(
'#tablename'
).
innerHTML
=
fullTableName
;
document
.
querySelector
(
'#tablename'
).
innerHTML
=
fullTableName
;
document
.
querySelector
(
'#columnname'
).
innerHTML
=
`
${
attrName
}
(
${
attrType
}
)`
;
document
.
querySelector
(
'#columnname'
).
innerHTML
=
`
${
attrName
}
(
${
attrType
}
)`
;
document
.
querySelector
(
'#back'
).
innerHTML
=
`<a href="tableinfo.html?table=
${
fullTableName
}
">Terug naar layer informatie</a>`
document
.
querySelector
(
'#back'
).
innerHTML
=
`<a href="tableinfo.html?table=
${
fullTableName
}
&geom_column=
${
geomColumn
}
">Terug naar layer informatie</a>`
const
parts
=
fullTableName
.
split
(
'.'
);
const
parts
=
fullTableName
.
split
(
'.'
);
const
tableName
=
(
parts
.
length
>
1
)
?
parts
[
1
]
:
parts
[
0
];
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
=>
{
fetch
(
`data/query/
${
fullTableName
}
?columns=count("
${
attrName
}
"),count(distinct+"
${
attrName
}
")+as+distinct,min("
${
attrName
}
"),max("
${
attrName
}
")`
).
then
(
response
=>
{
...
...
public/tableinfo.html
View file @
169d5717
...
@@ -33,6 +33,22 @@
...
@@ -33,6 +33,22 @@
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
}
)`
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
);
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
);
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
{
bbox
.
innerHTML
=
`Error getting bbox, response: response:
${
response
.
status
}
${
response
.
statusText
?
response
.
statusText
:
''
}
${
response
.
url
}
`
}
})
})
})
}
else
{
}
else
{
const
li
=
document
.
createElement
(
'li'
);
const
li
=
document
.
createElement
(
'li'
);
...
@@ -40,22 +56,7 @@
...
@@ -40,22 +56,7 @@
list
.
appendChild
(
li
);
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
);
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
{
bbox
.
innerHTML
=
`Error getting bbox, response: response:
${
response
.
status
}
${
response
.
statusText
?
response
.
statusText
:
''
}
${
response
.
url
}
`
}
})
}
}
</script>
</script>
</head>
</head>
...
...
swagger.js
View file @
169d5717
...
@@ -23,7 +23,17 @@ const swaggerDefinition = {
...
@@ -23,7 +23,17 @@ const swaggerDefinition = {
const
swaggerJSDocOptions
=
{
const
swaggerJSDocOptions
=
{
swaggerDefinition
,
swaggerDefinition
,
apis
:
[
'./login.js'
,
'./mvt.js'
,
'./list_layers.js'
,
'./layer_columns.js'
,
'./bbox.js'
,
'./geojson.js'
,
'./geobuf.js'
,
'./query.js'
]
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
);
const
swaggerSpec
=
swaggerJSDoc
(
swaggerJSDocOptions
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment