From b943724331e608064ffb2240971a6634e47227ec Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Fri, 13 May 2022 11:25:09 -0700 Subject: [PATCH 1/7] Include upstream changes --- dr-app/public/js/query.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index a8f2448f..c56b5e38 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -482,7 +482,7 @@ async function getPlaceSearchResults(pageNum, recordNum, parameters) { ?entity elastic:score ?score. ?entity a ?type; rdfs:label ?label; - OPTIONAL { ?entity kwg-ont:quantifiedName ?quantifiedName. } + OPTIONAL { kwg-ont:quantifiedName ?quantifiedName. } values ?type {kwg-ont:AdministrativeRegion_2 kwg-ont:AdministrativeRegion_3} ?type rdfs:label ?typeLabel } order by desc(?score)`); @@ -943,7 +943,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { hazardEntites.push(row.entity.value.replace('http://stko-kwg.geog.ucsb.edu/lod/resource/','kwgr:')); } - let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?placeQuantName; separator = "||") as ?placeQuantName) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) + let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) { ?entity rdf:type ?type; kwg-ont:hasTemporalScope|sosa:isFeatureOfInterestOf/sosa:phenomenonTime ?time. @@ -962,30 +962,33 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { time:inXSDDateTime|time:inXSDDate ?endTimeLabel. VALUES ?entity {${hazardEntites.join(' ')}} - } GROUP BY ?entity` ; + } GROUP BY ?entity ?placeLabel ?placeQuantName`; queryResults = await query(hazardAttributesQuery); - queryResults.forEach(function(row, counterRow) { + + let counterRow = -1; + for (let row of queryResults) { + counterRow += 1; // If there isn't a quantified name use the regular label if (typeof row.placeQuantName === 'undefined') { // If there isn't a place name, use '' if (typeof row.placeLabel === 'undefined') { formattedResults[counterRow]['place_name'] = ''; } else { - formattedResults[counterRow]['place_name'] = row.placeLabel.value.split('||')[0]; + formattedResults[counterRow]['place_name'] = row.placeLabel.value; } } else { - formattedResults[counterRow]['place_name'] = row.placeQuantName.value.split('||')[0]; + formattedResults[counterRow]['place_name'] = row.placeQuantName.value; } formattedResults[counterRow]['hazard_type'] = row.type.value.split('||'); formattedResults[counterRow]['hazard_type_name'] = row.typeLabel.value.split('||'); - formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||')[0]; + formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||'); + formattedResults[counterRow]['place_name'] = (typeof row.placeLabel === 'undefined') ? '' : row.placeLabel.value.split('||'); formattedResults[counterRow]['start_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['start_date_name'] = row.startTimeLabel.value.split('||')[0]; formattedResults[counterRow]['end_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['end_date_name'] = row.endTimeLabel.value.split('||')[0]; - }) - console.log(formattedResults) + } let countResults = await query(`select (count(*) as ?count) { ` + hazardQuery + ` LIMIT ` + recordNum*10 + `}`); return { 'count': countResults[0].count.value, 'record': formattedResults }; From 7a05500bc2ce8b7cffd7b92a923731cbf5410825 Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Sun, 15 May 2022 11:47:51 -0700 Subject: [PATCH 2/7] Fix optional statements in place search --- dr-app/public/js/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index ea353465..7a503b2c 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -480,7 +480,7 @@ async function getPlaceSearchResults(pageNum, recordNum, parameters) { ?entity elastic:score ?score. ?entity a ?type; rdfs:label ?label; - OPTIONAL { kwg-ont:quantifiedName ?quantifiedName. } + OPTIONAL {?entity kwg-ont:quantifiedName ?quantifiedName. } values ?type {kwg-ont:AdministrativeRegion_2 kwg-ont:AdministrativeRegion_3} ?type rdfs:label ?typeLabel } order by desc(?score)`); From df82b7d546caacf39e973ed26659287ae9827684 Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Sun, 15 May 2022 12:09:26 -0700 Subject: [PATCH 3/7] Fix placeQuantName retrieval issue in hazard search --- dr-app/public/js/query.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index 7a503b2c..d4c0fa5a 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -941,7 +941,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { hazardEntites.push(row.entity.value.replace('http://stko-kwg.geog.ucsb.edu/lod/resource/','kwgr:')); } - let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) + let hazardAttributesQuery = `select distinct ?entity ?placeQuantName (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) { ?entity rdf:type ?type; kwg-ont:hasTemporalScope|sosa:isFeatureOfInterestOf/sosa:phenomenonTime ?time. @@ -960,7 +960,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { time:inXSDDateTime|time:inXSDDate ?endTimeLabel. VALUES ?entity {${hazardEntites.join(' ')}} - } GROUP BY ?entity ?placeLabel ?placeQuantName`; + } GROUP BY ?entity ?placeQuantName`; queryResults = await query(hazardAttributesQuery); @@ -981,7 +981,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { formattedResults[counterRow]['hazard_type'] = row.type.value.split('||'); formattedResults[counterRow]['hazard_type_name'] = row.typeLabel.value.split('||'); formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||'); - formattedResults[counterRow]['place_name'] = (typeof row.placeLabel === 'undefined') ? '' : row.placeLabel.value.split('||'); + //formattedResults[counterRow]['place_name'] = (typeof row.placeLabel === 'undefined') ? '' : row.placeLabel.value.split('||'); formattedResults[counterRow]['start_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['start_date_name'] = row.startTimeLabel.value.split('||')[0]; formattedResults[counterRow]['end_date'] = row.time.value.split('||')[0]; From 2e34a16d6c2c9e010c5c902757be9f7f3d6cac79 Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Sun, 15 May 2022 13:22:53 -0700 Subject: [PATCH 4/7] Fix the missing info issue of placeQuantName retrieval in hazard search --- dr-app/public/js/query.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index d4c0fa5a..3e733667 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -941,7 +941,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { hazardEntites.push(row.entity.value.replace('http://stko-kwg.geog.ucsb.edu/lod/resource/','kwgr:')); } - let hazardAttributesQuery = `select distinct ?entity ?placeQuantName (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) + let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?placeQuantName; separator = "||") as ?placeQuantName) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) { ?entity rdf:type ?type; kwg-ont:hasTemporalScope|sosa:isFeatureOfInterestOf/sosa:phenomenonTime ?time. @@ -960,7 +960,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { time:inXSDDateTime|time:inXSDDate ?endTimeLabel. VALUES ?entity {${hazardEntites.join(' ')}} - } GROUP BY ?entity ?placeQuantName`; + } GROUP BY ?entity`; queryResults = await query(hazardAttributesQuery); @@ -981,7 +981,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { formattedResults[counterRow]['hazard_type'] = row.type.value.split('||'); formattedResults[counterRow]['hazard_type_name'] = row.typeLabel.value.split('||'); formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||'); - //formattedResults[counterRow]['place_name'] = (typeof row.placeLabel === 'undefined') ? '' : row.placeLabel.value.split('||'); + formattedResults[counterRow]['place_name'] = formattedResults[counterRow]['place_name'].split('||'); formattedResults[counterRow]['start_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['start_date_name'] = row.startTimeLabel.value.split('||')[0]; formattedResults[counterRow]['end_date'] = row.time.value.split('||')[0]; From 0e8aba6dc59a1108d91433bb6c6fba2eb4329643 Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Sun, 15 May 2022 16:28:56 -0700 Subject: [PATCH 5/7] Use s2 cell-based search for all hazard search situations --- dr-app/public/js/query.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index 3e733667..15f0fe31 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -808,17 +808,18 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { if (placeEntities.length > 0) { let placesConnectedToS2 = []; - let placesLocatedIn = []; + // let placesLocatedIn = []; for (let i = 0 ; i < placeEntities.length; i++) { - if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) +/* if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) { placesConnectedToS2.push(placeEntities[i]); } if (placeEntities[i].startsWith('Earth') || placeEntities[i].startsWith('NWZone')) { placesLocatedIn.push(placeEntities[i]); - } + } */ + placesConnectedToS2.push(placeEntities[i]); } if (placesConnectedToS2.length > 0) { @@ -828,45 +829,46 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { ?s2cellGNIS kwg-ont:spatialRelation ?placesConnectedToS2. `; } - if (placesLocatedIn.length > 0) +/* if (placesLocatedIn.length > 0) { placeSearchQuery += ` ?s2cellGNIS kwg-ont:spatialRelation ?placesNonConnectedToS2. values ?placesNonConnectedToS2 {kwgr:` + placesLocatedIn.join(' kwgr:') + `} `; - } + } */ } } else if (placeEntities.length > 0) { let placesConnectedToS2 = []; - let placesLocatedIn = []; + //let placesLocatedIn = []; for (let i = 0 ; i < placeEntities.length; i++) { - if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) +/* if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) { placesConnectedToS2.push(placeEntities[i]); } if (placeEntities[i].startsWith('Earth') || placeEntities[i].startsWith('NWZone')) { placesLocatedIn.push(placeEntities[i]); - } + } */ + placesConnectedToS2.push(placeEntities[i]); } if (placesConnectedToS2.length > 0) { placeSearchQuery += ` - ?entity kwg-ont:sfWithin ?s2Cell . + ?entity kwg-ont:spatialRelation ?s2Cell . ?s2Cell rdf:type kwg-ont:KWGCellLevel13 . values ?placesConnectedToS2 {kwgr:` + placesConnectedToS2.join(' kwgr:') + `} ?s2Cell kwg-ont:spatialRelation ?placesConnectedToS2. `; } - if (placesLocatedIn.length > 0) +/* if (placesLocatedIn.length > 0) { placeSearchQuery += ` values ?place {kwgr:` + placesLocatedIn.join(' kwgr:') + `} `; - } + } */ } //Filter by the date hazard occurred From 871e8283dc738b7c20b51ddef38febf03f8148fa Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Mon, 16 May 2022 13:57:36 -0700 Subject: [PATCH 6/7] Remove commented codes in hazard search --- dr-app/public/js/query.js | 69 +++++++-------------------------------- 1 file changed, 11 insertions(+), 58 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index 15f0fe31..e88a5822 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -807,68 +807,21 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { `; if (placeEntities.length > 0) { - let placesConnectedToS2 = []; - // let placesLocatedIn = []; - for (let i = 0 ; i < placeEntities.length; i++) - { -/* if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) - { - placesConnectedToS2.push(placeEntities[i]); - } - if (placeEntities[i].startsWith('Earth') || placeEntities[i].startsWith('NWZone')) - { - placesLocatedIn.push(placeEntities[i]); - } */ - placesConnectedToS2.push(placeEntities[i]); - } - if (placesConnectedToS2.length > 0) - { - placeSearchQuery += ` - ?s2cellGNIS rdf:type kwg-ont:KWGCellLevel13 . - values ?placesConnectedToS2 {kwgr:` + placesConnectedToS2.join(' kwgr:') + `} - ?s2cellGNIS kwg-ont:spatialRelation ?placesConnectedToS2. - `; - } -/* if (placesLocatedIn.length > 0) - { - placeSearchQuery += ` - ?s2cellGNIS kwg-ont:spatialRelation ?placesNonConnectedToS2. - values ?placesNonConnectedToS2 {kwgr:` + placesLocatedIn.join(' kwgr:') + `} - `; - } */ + placeSearchQuery += ` + ?s2cellGNIS rdf:type kwg-ont:KWGCellLevel13 . + values ?placesConnectedToS2 {kwgr:` + placeEntities.join(' kwgr:') + `} + ?s2cellGNIS kwg-ont:spatialRelation ?placesConnectedToS2. + `; } } else if (placeEntities.length > 0) { - let placesConnectedToS2 = []; - //let placesLocatedIn = []; - for (let i = 0 ; i < placeEntities.length; i++) - { -/* if (placeEntities[i].startsWith('zipcode') || placeEntities[i].startsWith('noaaClimateDiv')) - { - placesConnectedToS2.push(placeEntities[i]); - } - if (placeEntities[i].startsWith('Earth') || placeEntities[i].startsWith('NWZone')) - { - placesLocatedIn.push(placeEntities[i]); - } */ - placesConnectedToS2.push(placeEntities[i]); - } - if (placesConnectedToS2.length > 0) - { - placeSearchQuery += ` - ?entity kwg-ont:spatialRelation ?s2Cell . - ?s2Cell rdf:type kwg-ont:KWGCellLevel13 . - values ?placesConnectedToS2 {kwgr:` + placesConnectedToS2.join(' kwgr:') + `} - ?s2Cell kwg-ont:spatialRelation ?placesConnectedToS2. - `; - } -/* if (placesLocatedIn.length > 0) - { - placeSearchQuery += ` - values ?place {kwgr:` + placesLocatedIn.join(' kwgr:') + `} - `; - } */ + placeSearchQuery += ` + ?entity kwg-ont:spatialRelation ?s2Cell . + ?s2Cell rdf:type kwg-ont:KWGCellLevel13 . + values ?placesConnectedToS2 {kwgr:` + placeEntities.join(' kwgr:') + `} + ?s2Cell kwg-ont:spatialRelation ?placesConnectedToS2. + `; } //Filter by the date hazard occurred From e1386d8e5960b360caa0f86b23724ec534d15bb7 Mon Sep 17 00:00:00 2001 From: Zilong Liu Date: Mon, 23 May 2022 13:34:18 -0700 Subject: [PATCH 7/7] Include upstream changes --- dr-app/public/js/query.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/dr-app/public/js/query.js b/dr-app/public/js/query.js index e88a5822..e43b9ab4 100644 --- a/dr-app/public/js/query.js +++ b/dr-app/public/js/query.js @@ -480,7 +480,7 @@ async function getPlaceSearchResults(pageNum, recordNum, parameters) { ?entity elastic:score ?score. ?entity a ?type; rdfs:label ?label; - OPTIONAL {?entity kwg-ont:quantifiedName ?quantifiedName. } + OPTIONAL { ?entity kwg-ont:quantifiedName ?quantifiedName. } values ?type {kwg-ont:AdministrativeRegion_2 kwg-ont:AdministrativeRegion_3} ?type rdfs:label ?typeLabel } order by desc(?score)`); @@ -896,7 +896,7 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { hazardEntites.push(row.entity.value.replace('http://stko-kwg.geog.ucsb.edu/lod/resource/','kwgr:')); } - let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?placeQuantName; separator = "||") as ?placeQuantName) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) + let hazardAttributesQuery = `select distinct ?entity (group_concat(distinct ?placeQuantName; separator = "||") as ?placeQuantName) (group_concat(distinct ?placeLabel; separator = "||") as ?placeLabel) (group_concat(distinct ?type; separator = "||") as ?type) (group_concat(distinct ?typeLabel; separator = "||") as ?typeLabel) (group_concat(distinct ?place; separator = "||") as ?place) (group_concat(distinct ?time; separator = "||") as ?time) (group_concat(distinct ?startTimeLabel; separator = "||") as ?startTimeLabel) (group_concat(distinct ?endTimeLabel; separator = "||") as ?endTimeLabel) { ?entity rdf:type ?type; kwg-ont:hasTemporalScope|sosa:isFeatureOfInterestOf/sosa:phenomenonTime ?time. @@ -915,33 +915,29 @@ async function getHazardSearchResults(pageNum, recordNum, parameters) { time:inXSDDateTime|time:inXSDDate ?endTimeLabel. VALUES ?entity {${hazardEntites.join(' ')}} - } GROUP BY ?entity`; + } GROUP BY ?entity` ; queryResults = await query(hazardAttributesQuery); - - let counterRow = -1; - for (let row of queryResults) { - counterRow += 1; + queryResults.forEach(function(row, counterRow) { // If there isn't a quantified name use the regular label if (typeof row.placeQuantName === 'undefined') { // If there isn't a place name, use '' if (typeof row.placeLabel === 'undefined') { formattedResults[counterRow]['place_name'] = ''; } else { - formattedResults[counterRow]['place_name'] = row.placeLabel.value; + formattedResults[counterRow]['place_name'] = row.placeLabel.value.split('||')[0]; } } else { - formattedResults[counterRow]['place_name'] = row.placeQuantName.value; + formattedResults[counterRow]['place_name'] = row.placeQuantName.value.split('||')[0]; } formattedResults[counterRow]['hazard_type'] = row.type.value.split('||'); formattedResults[counterRow]['hazard_type_name'] = row.typeLabel.value.split('||'); - formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||'); - formattedResults[counterRow]['place_name'] = formattedResults[counterRow]['place_name'].split('||'); + formattedResults[counterRow]['place'] = (typeof row.place === 'undefined') ? '' : row.place.value.split('||')[0]; formattedResults[counterRow]['start_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['start_date_name'] = row.startTimeLabel.value.split('||')[0]; formattedResults[counterRow]['end_date'] = row.time.value.split('||')[0]; formattedResults[counterRow]['end_date_name'] = row.endTimeLabel.value.split('||')[0]; - } + }) let countResults = await query(`select (count(*) as ?count) { ` + hazardQuery + ` LIMIT ` + recordNum*10 + `}`); return { 'count': countResults[0].count.value, 'record': formattedResults };