Skip to content

Commit c785727

Browse files
committed
#666 Add Dataset behavior to Geodatasets
1 parent a61779f commit c785727

File tree

17 files changed

+970
-264
lines changed

17 files changed

+970
-264
lines changed

API/Backend/Geodatasets/routes/geodatasets.js

Lines changed: 276 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,25 @@ function get(reqtype, req, res, next) {
2828
let xyz = {};
2929
let _source = null; // Works just like ES _source
3030
let noDuplicates = false;
31+
let get_group_id = null;
32+
let get_id = null;
33+
let filters = null;
34+
let spatialFilter = null; // Not implemented
3135

3236
if (reqtype === "post") {
3337
layer = req.body.layer;
3438
type = req.body.type || type;
3539
if (req.body._source && Array.isArray(req.body._source))
3640
_source = req.body._source;
41+
3742
if (req.body.noDuplicates === true || req.body.noDuplicates === "true")
3843
noDuplicates = true;
44+
45+
if (req.body.group_id != null) get_group_id = req.body.group_id;
46+
if (req.body.id != null) get_id = req.body.id;
47+
if (req.body.filters != null) filters = req.body.filters;
48+
if (req.body.spatialFilter != null) spatialFilter = req.body.spatialFilter;
49+
3950
if (type === "mvt") {
4051
xyz = {
4152
x: parseInt(req.body.x),
@@ -53,6 +64,31 @@ function get(reqtype, req, res, next) {
5364

5465
if (req.query.noDuplicates === true || req.query.noDuplicates === "true")
5566
noDuplicates = true;
67+
68+
if (req.query.group_id != null) get_group_id = req.query.group_id;
69+
if (req.query.id != null) get_id = req.query.id;
70+
if (req.query.filters != null) {
71+
const filterSplit = req.query.filters.split(",");
72+
filters = [];
73+
filterSplit.forEach((f) => {
74+
const fSplit = f.split("+");
75+
filters.push({
76+
key: fSplit[0],
77+
op: fSplit[1],
78+
type: fSplit[2],
79+
value: fSplit[3],
80+
});
81+
});
82+
}
83+
if (req.query.spatialFilter != null) {
84+
const spatialFilterSplit = req.query.spatialFilter.split(",");
85+
spatialFilter = {
86+
lat: spatialFilterSplit[0],
87+
lng: spatialFilterSplit[1],
88+
radius: spatialFilterSplit[2],
89+
};
90+
}
91+
5692
if (type === "mvt") {
5793
xyz = {
5894
x: parseInt(req.query.x),
@@ -61,6 +97,10 @@ function get(reqtype, req, res, next) {
6197
};
6298
}
6399
}
100+
101+
console.log(spatialFilter);
102+
console.log(filters);
103+
64104
//First Find the table name
65105
Geodatasets.findOne({ where: { name: layer } })
66106
.then((result) => {
@@ -88,7 +128,13 @@ function get(reqtype, req, res, next) {
88128
else distinct = ` DISTINCT ON (geom)`;
89129
}
90130

91-
let q = `SELECT${distinct} ${properties}, ST_AsGeoJSON(geom), id, group_id, feature_id FROM ${Utils.forceAlphaNumUnder(
131+
let cols = ["id"];
132+
if (result.dataValues.group_id_field != null) cols.push("group_id");
133+
if (result.dataValues.feature_id_field != null)
134+
cols.push("feature_id");
135+
cols = cols.join(", ");
136+
137+
let q = `SELECT${distinct} ${properties}, ST_AsGeoJSON(geom), ${cols} FROM ${Utils.forceAlphaNumUnder(
92138
table
93139
)}`;
94140

@@ -147,7 +193,7 @@ function get(reqtype, req, res, next) {
147193
endProp = Utils.forceAlphaNumUnder(req.query.endProp || endProp);
148194
// prettier-ignore
149195
t += [
150-
`(`,
196+
`((`,
151197
`${startProp} IS NOT NULL AND ${endProp} IS NOT NULL AND`,
152198
` ${startProp} >= ${start_time}`,
153199
` AND ${endProp} <= ${end_time}`,
@@ -157,24 +203,86 @@ function get(reqtype, req, res, next) {
157203
`${startProp} IS NULL AND ${endProp} IS NOT NULL AND`,
158204
` ${endProp} >= ${start_time}`,
159205
` AND ${endProp} <= ${end_time}`,
160-
`)`
206+
`))`
161207
].join('')
162208
q += t;
163209
}
164-
q += `;`;
210+
211+
if (get_group_id != null) {
212+
q += `${
213+
q.indexOf(" WHERE ") == -1 ? " WHERE " : " AND "
214+
}group_id = :get_group_id`;
215+
} else if (get_id != null) {
216+
q += `${
217+
q.indexOf(" WHERE ") == -1 ? " WHERE " : " AND "
218+
}id = :get_id`;
219+
}
165220

166221
const replacements = {
167222
startProp: startProp,
168223
start_time: start_time,
169224
endProp: endProp,
170225
end_time: end_time,
226+
get_group_id: get_group_id,
227+
get_id: get_id,
171228
};
229+
172230
if (Array.isArray(_source)) {
173231
_source.forEach((v, i) => {
174232
replacements[`prop_${i}`] = v;
175233
});
176234
}
177235

236+
// Filters
237+
if (filters != null && filters.length > 0) {
238+
let filterSQL = [];
239+
filters.forEach((f, i) => {
240+
replacements[`filter_key_${i}`] = f.key;
241+
replacements[`filter_value_${i}`] = f.value;
242+
let op = "=";
243+
switch (f.op) {
244+
case ">":
245+
op = ">";
246+
break;
247+
case "<":
248+
op = "<";
249+
break;
250+
case "in":
251+
op = "IN";
252+
break;
253+
case "=":
254+
default:
255+
break;
256+
}
257+
let value = "";
258+
if (op === "IN") {
259+
const valueSplit = f.value.split("$");
260+
const values = [];
261+
valueSplit.forEach((v) => {
262+
replacements[`filter_value_${i}_${v}`] = v;
263+
values.push(`:filter_value_${i}_${v}`);
264+
});
265+
value = `(${values.join(",")})`;
266+
} else {
267+
replacements[`filter_value_${i}`] = f.value;
268+
value = `:filter_value_${i}`;
269+
}
270+
if (f.type === "number") {
271+
filterSQL.push(
272+
`(properties->>:filter_key_${i})::FLOAT ${op} ${value}`
273+
);
274+
} else {
275+
filterSQL.push(`properties->>:filter_key_${i} ${op} ${value}`);
276+
}
277+
});
278+
q += `${
279+
q.indexOf(" WHERE ") == -1 ? " WHERE " : " AND "
280+
}${filterSQL.join(` AND `)}`;
281+
}
282+
283+
q += `;`;
284+
285+
console.log(q);
178286
sequelize
179287
.query(q, {
180288
replacements: replacements,
@@ -200,6 +308,10 @@ function get(reqtype, req, res, next) {
200308
feature.geometry = JSON.parse(results[i].st_asgeojson);
201309
geojson.features.push(feature);
202310
}
311+
if (get_id != null)
312+
geojson.feature_id_field = result.dataValues.feature_id_field;
313+
if (get_group_id != null)
314+
geojson.group_id_field = result.dataValues.group_id_field;
203315

204316
res.setHeader("Access-Control-Allow-Origin", "*");
205317

@@ -211,6 +323,7 @@ function get(reqtype, req, res, next) {
211323
} else {
212324
res.send(geojson);
213325
}
326+
214327
return null;
215328
})
216329
.catch((err) => {
@@ -338,10 +451,168 @@ function get(reqtype, req, res, next) {
338451
})
339452
.catch((err) => {
340453
logger("error", "Failure finding geodataset.", req.originalUrl, req, err);
341-
res.send({ status: "failure", message: "d" });
454+
res.send({ status: "failure", message: "Failure finding geodataset." });
342455
});
343456
}
344457

458+
/*
459+
req.query.limit
460+
req.query.minx
461+
req.query.miny
462+
req.query.maxx
463+
req.query.maxy
464+
req.query.starttime
465+
req.query.endtime
466+
*/
467+
router.get("/aggregations", function (req, res, next) {
468+
//First Find the table name
469+
Geodatasets.findOne({ where: { name: req.query.layer } })
470+
.then((result) => {
471+
if (result) {
472+
let table = result.dataValues.table;
473+
let q = `SELECT properties FROM ${Utils.forceAlphaNumUnder(table)}`;
474+
475+
let hasBounds = false;
476+
let minx = req.query?.minx;
477+
let miny = req.query?.miny;
478+
let maxx = req.query?.maxx;
479+
let maxy = req.query?.maxy;
480+
if (minx != null && miny != null && maxx != null && maxy != null) {
481+
// ST_MakeEnvelope is (xmin, ymin, xmax, ymax, srid)
482+
q += ` WHERE ST_Intersects(ST_MakeEnvelope(${Utils.forceAlphaNumUnder(
483+
parseFloat(minx)
484+
)}, ${Utils.forceAlphaNumUnder(
485+
parseFloat(miny)
486+
)}, ${Utils.forceAlphaNumUnder(
487+
parseFloat(maxx)
488+
)}, ${Utils.forceAlphaNumUnder(parseFloat(maxy))}, 4326), geom)`;
489+
hasBounds = true;
490+
}
491+
let startProp = "start_time";
492+
let start_time = "";
493+
let endProp = "end_time";
494+
let end_time = "";
495+
if (req.query?.endtime != null) {
496+
const format = req.query?.format || "YYYY-MM-DDTHH:MI:SSZ";
497+
let t = ` `;
498+
if (!hasBounds) t += `WHERE `;
499+
else t += `AND `;
500+
501+
if (
502+
req.query?.starttime == null ||
503+
req.query?.starttime.indexOf(`'`) != -1 ||
504+
req.query?.endtime == null ||
505+
req.query?.endtime.indexOf(`'`) != -1 ||
506+
format.indexOf(`'`) != -1
507+
) {
508+
res.send({
509+
status: "failure",
510+
message: "Missing inner or malformed time parameters.",
511+
});
512+
return;
513+
}
514+
515+
start_time = new Date(
516+
req.query.starttime || "1970-01-01T00:00:00Z"
517+
).getTime();
518+
end_time = new Date(req.query.endtime).getTime();
519+
520+
startProp = Utils.forceAlphaNumUnder(
521+
req.query.startProp || startProp
522+
);
523+
endProp = Utils.forceAlphaNumUnder(req.query.endProp || endProp);
524+
// prettier-ignore
525+
t += [
526+
`(`,
527+
`${startProp} IS NOT NULL AND ${endProp} IS NOT NULL AND`,
528+
` ${startProp} >= ${start_time}`,
529+
` AND ${endProp} <= ${end_time}`,
530+
`)`,
531+
` OR `,
532+
`(`,
533+
`${startProp} IS NULL AND ${endProp} IS NOT NULL AND`,
534+
` ${endProp} >= ${start_time}`,
535+
` AND ${endProp} <= ${end_time}`,
536+
`)`
537+
].join('')
538+
q += t;
539+
}
540+
541+
q += ` ORDER BY id DESC LIMIT :limit;`;
542+
543+
sequelize
544+
.query(q, {
545+
replacements: {
546+
limit: req.query.limit != null ? parseInt(req.query.limit) : 100,
547+
startProp: startProp,
548+
start_time: start_time,
549+
endProp: endProp,
550+
end_time: end_time,
551+
},
552+
})
553+
.then(([results]) => {
554+
let aggs = {};
555+
results.forEach((feature) => {
556+
const flatProps = feature.properties;
557+
for (let p in flatProps) {
558+
let value = flatProps[p];
559+
let type = null;
560+
561+
if (!isNaN(value) && !isNaN(parseFloat(value))) type = "number";
562+
else if (typeof value === "string") type = "string";
563+
else if (typeof value === "number") type = "number";
564+
else if (typeof value === "boolean") type = "boolean";
565+
566+
if (type != null) {
567+
// First type will be from index 0
568+
aggs[p] = aggs[p] || { type: type, aggs: {} };
569+
// Because of that, strings can usurp numbers (ex. ["1", "2", "Melon", "Pastry"])
570+
if (aggs[p].type === "number" && type === "string")
571+
aggs[p].type = type;
572+
aggs[p].aggs[flatProps[p]] = aggs[p].aggs[flatProps[p]] || 0;
573+
aggs[p].aggs[flatProps[p]]++;
574+
}
575+
}
576+
});
577+
578+
// sort
579+
Object.keys(aggs).forEach((agg) => {
580+
const sortedAggs = {};
581+
Object.keys(aggs[agg].aggs)
582+
.sort()
583+
.reverse()
584+
.forEach((agg2) => {
585+
sortedAggs[agg2] = aggs[agg].aggs[agg2];
586+
});
587+
aggs[agg].aggs = sortedAggs;
588+
});
589+
590+
res.send({ status: "success", aggregations: aggs });
591+
})
592+
.catch((err) => {
593+
logger(
594+
"error",
595+
"Failure querying geodataset aggregations.",
596+
req.originalUrl,
597+
req,
598+
err
599+
);
600+
res.send({
601+
status: "failure",
602+
message: "Failure querying geodataset aggregations.",
603+
});
604+
});
605+
} else {
606+
res.send({ status: "failure", message: "Not Found" });
607+
}
608+
return null;
609+
})
610+
.catch((err) => {
611+
logger("error", "Failure finding geodataset.", req.originalUrl, req, err);
612+
res.send({ status: "failure", message: "Failure finding geodataset." });
613+
});
614+
});
615+
345616
//Returns a list of entries in the geodatasets table
346617
router.post("/entries", function (req, res, next) {
347618
Geodatasets.findAll()

0 commit comments

Comments
 (0)