Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#578 Next/Previous Feature #579

Merged
merged 10 commits into from
Aug 15, 2024
104 changes: 87 additions & 17 deletions API/Backend/Geodatasets/routes/geodatasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function get(reqtype, req, res, next) {
if (result) {
let table = result.dataValues.table;
if (type == "geojson") {
let q = `SELECT properties, ST_AsGeoJSON(geom) FROM ${table}`;
let q = `SELECT properties, ST_AsGeoJSON(geom), id FROM ${table}`;

if (req.query?.limited) {
q += ` ORDER BY id DESC LIMIT 3`;
Expand Down Expand Up @@ -124,6 +124,8 @@ function get(reqtype, req, res, next) {
let geojson = { type: "FeatureCollection", features: [] };
for (let i = 0; i < results.length; i++) {
let properties = results[i].properties;
properties._ = properties._ || {};
properties._.idx = results[i].id;
let feature = {};
feature.type = "Feature";
feature.properties = properties;
Expand Down Expand Up @@ -365,6 +367,9 @@ router.post("/entries", function (req, res, next) {
* req.body.layer
* req.body.key
* req.body.value
* req.body.id (specific feature id instead of key:value)
* req.body.orderBy
* req.body.offset (i.e. if -1, then return feature previous to key:val) (can also be 'first' or 'last')
*/
router.post("/search", function (req, res, next) {
//First Find the table name
Expand All @@ -373,34 +378,99 @@ router.post("/search", function (req, res, next) {
if (result) {
let table = result.dataValues.table;

let offset = req.body.offset;
const origOffset = offset;
if (offset === "first") offset = -1;
else if (offset === "last") offset = 1;

let featureId = req.body.id;

if (offset != null && featureId == null) {
res.send({
status: "failure",
message: "If 'offset' is set, 'id' must also be set.",
});
return;
}
offset = parseInt(offset);
featureId = parseInt(featureId);

let orderBy = "id";
if (req.body.orderBy != null)
orderBy = `properties->>'${req.body.orderBy}'`;

let minx = req.body?.minx;
let miny = req.body?.miny;
let maxx = req.body?.maxx;
let maxy = req.body?.maxy;
let where = "";
if (minx != null && miny != null && maxx != null && maxy != null) {
// ST_MakeEnvelope is (xmin, ymin, xmax, ymax, srid)
where = ` WHERE ST_Intersects(ST_MakeEnvelope(${parseFloat(
minx
)}, ${parseFloat(miny)}, ${parseFloat(maxx)}, ${parseFloat(
maxy
)}, 4326), geom)`;
}

let q =
"SELECT properties, ST_AsGeoJSON(geom), id FROM " +
table +
(req.body.last || offset != null
? `${where} ORDER BY id ${offset != null ? "ASC" : "DESC LIMIT 1"}`
: " WHERE properties ->> :key = :value");

sequelize
.query(
"SELECT properties, ST_AsGeoJSON(geom) FROM " +
table +
(req.body.last
? " ORDER BY id DESC LIMIT 1;"
: " WHERE properties ->> :key = :value;"),
{
replacements: {
key: req.body.key,
value:
typeof req.body.value === "string"
? req.body.value.replace(/[`;'"]/gi, "")
: null,
},
}
)
.query(q + ";", {
replacements: {
key: req.body.key,
value:
typeof req.body.value === "string"
? req.body.value.replace(/[`;'"]/gi, "")
: null,
},
})
.then(([results]) => {
let r = [];
for (let i = 0; i < results.length; i++) {
let properties = results[i].properties;
properties._ = properties._ || {};
properties._.idx = results[i].id;
let feature = {};
feature.type = "Feature";
feature.properties = properties;
feature.geometry = JSON.parse(results[i].st_asgeojson);
r.push(feature);
}

if (offset != null) {
if (orderBy != "id") {
r.sort((a, b) => {
let sign = 1;
if (offset > 0) sign = -1;
const af = Utils.getIn(a, `properties.${orderBy}`, 0);
const bf = Utils.getIn(b, `properties.${orderBy}`, 1);
if (typeof af === "string" || typeof bf === "string") {
return af.localeCompare(bf) * sign;
} else return (af - bf) * sign;
});
}

const rLen = r.length;
if (origOffset === "first" || origOffset === "last") {
r = [r[rLen - 1]];
} else {
for (let i = 0; i < rLen; i++) {
if (r[i].properties._.idx === featureId) {
r = [
r[Math.min(Math.max(0, i + Math.abs(offset)), rLen - 1)],
]; //abs because we already sort differently by it
break;
}
}
}
}

res.send({
status: "success",
body: r,
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-query-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@
"defaultChecked": false
},
{
"field": "variables.dynamicExtentThreshold",
"field": "variables.dynamicExtentMoveThreshold",
"name": "Threshold",
"description": "If dynamicExtent is true, only requery if the map was panned past the stated threshold. Unit is in meters. If a zoom-dependent threshold is desired, set this value to a string ending in '/z'. This will then internally use 'dynamicExtentMoveThreshold / Math.pow(2, zoom)' as the threshold value.",
"type": "text",
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-vector-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@
"defaultChecked": false
},
{
"field": "variables.dynamicExtentThreshold",
"field": "variables.dynamicExtentMoveThreshold",
"name": "Threshold",
"description": "If dynamicExtent is true, only requery if the map was panned past the stated threshold. Unit is in meters. If a zoom-dependent threshold is desired, set this value to a string ending in '/z'. This will then internally use 'dynamicExtentMoveThreshold / Math.pow(2, zoom)' as the threshold value.",
"type": "text",
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-vectortile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@
"defaultChecked": false
},
{
"field": "variables.dynamicExtentThreshold",
"field": "variables.dynamicExtentMoveThreshold",
"name": "Threshold",
"description": "If dynamicExtent is true, only requery if the map was panned past the stated threshold. Unit is in meters. If a zoom-dependent threshold is desired, set this value to a string ending in '/z'. This will then internally use 'dynamicExtentMoveThreshold / Math.pow(2, zoom)' as the threshold value.",
"type": "text",
Expand Down
131 changes: 131 additions & 0 deletions src/essence/Ancillary/Description.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#mainDescNavBar {
display: flex;
background: var(--color-a1);
margin-right: 8px;
}
#mainDescNavBarMenu {
width: 22px;
height: 30px;
text-align: center;
line-height: 30px;
background: var(--color-a);
color: var(--color-a6);
cursor: pointer;
transition: background 0.2s ease-in-out;
}
#mainDescNavBarMenu:hover {
background: rgba(255, 255, 255, 0.1);
}
#mainDescNavBarPrevious {
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
border-right: 1px solid var(--color-a);
cursor: pointer;
transition: background 0.2s ease-in-out;
}
#mainDescNavBarPrevious:hover {
background: rgba(255, 255, 255, 0.1);
}
#mainDescNavBarNext {
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
cursor: pointer;
transition: background 0.2s ease-in-out;
}
#mainDescNavBarNext:hover {
background: rgba(255, 255, 255, 0.1);
}

#mainDescNavPopover {
display: none;
background: var(--color-a);
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.4);
width: 240px;
border-top: 1px solid var(--color-a1);
transition: left 0.2s ease-out;
}
#mainDescNavPopoverTitle {
background: var(--color-a2);
text-align: center;
padding: 4px 0px;
font-size: 14px;
letter-spacing: 1px;
border-bottom: 1px solid var(--color-a);
}
#mainDescNavPopoverField {
display: flex;
justify-content: space-between;
height: 30px;
line-height: 30px;
font-size: 14px;
border-bottom: 1px solid var(--color-a1);
}
#mainDescNavPopoverFieldField {
width: 100%;
}
#mainDescNavPopoverFieldInfo {
display: flex;
justify-content: space-between;
padding: 8px 6px 6px 16px;
border-bottom: 1px solid var(--color-a1);
}
#mainDescNavPopoverFieldInfo > div:first-child {
color: var(--color-a6);
margin-right: 8px;
font-size: 12px;
line-height: 18px;
}
#mainDescNavPopoverFieldInfo > div:last-child {
color: var(--color-h);
font-size: 14px;
font-weight: bold;
font-family: monospace;
word-break: break-all;
}
#mainDescNavPopoverExtent,
#mainDescNavPopoverTimeExtent,
#mainDescNavPopoverPanTo {
display: flex;
justify-content: space-between;
height: 30px;
line-height: 30px;
font-size: 14px;
padding-left: 8px;
border-bottom: 1px solid var(--color-a1);
}
#mainDescNavPopoverBottom {
display: flex;
justify-content: center;
height: 30px;
line-height: 30px;
}
#mainDescNavPopoverBottom > div {
width: 30px;
height: 30px;
cursor: pointer;
text-align: center;
transition: background 0.2s ease-in-out;
}
#mainDescNavPopoverBottom > div:hover {
background: var(--color-a1);
}

#mainDescNavPopoverFieldField .dropy__title {
height: 30px;
border: none;
background: var(--color-a1);
line-height: 14px;
font-size: 14px;
}
#mainDescNavPopoverFieldField .dropy__title > i {
line-height: 15px;
}
#mainDescNavPopoverFieldField .dropy__content a {
height: 30px;
line-height: 30px;
padding: 2px 8px;
}
Loading
Loading