Skip to content

Commit

Permalink
fixed map context search and field search for wfs/odata layers (#602)
Browse files Browse the repository at this point in the history
* fixed map context search and field search for wfs/odata layers

* fixing the bugs of the previous version of the commit, moving code into utilities, tests

* context search supports numeric, date and boolean formats

* fixed test

* included contextual search for numeric, boolean and date fields

* undo changes when forming an empty filter

* objects of one layer are limited to 100, not all found objects

* added limit flag on entries

* improving method safety

Co-authored-by: viatkinviatkin <svyatkin@skyori.ru>
  • Loading branch information
viatkinviatkin and viatkinviatkin authored Aug 17, 2022
1 parent 3dedb48 commit 9a197af
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 72 deletions.
4 changes: 3 additions & 1 deletion addon/components/layer-result-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ export default Ember.Component.extend(LeafletZoomToFeatureMixin, {
result.features.then(
(features) => {
if (features.length > 0) {

let intersectArray = features.filter((item) => {
item.isIntersect = false;
if (!Ember.isNone(item.intersection)) {
Expand Down Expand Up @@ -395,7 +396,8 @@ export default Ember.Component.extend(LeafletZoomToFeatureMixin, {
displayProperties: Ember.get(layerModel, 'settingsAsObject.displaySettings.featuresPropertiesSettings.displayProperty'),
listForms: Ember.A(),
editForms: Ember.A(),
features: Ember.A(features),
features: this.get('maxResultsCount') ? Ember.A(features).slice(0, this.get('maxResultsCount')) : Ember.A(features),
maxResultsLimitOverage: this.get('maxResultsCount') && features.length > this.get('maxResultsCount') ? true : false,
layerModel: layerModel,
hasListForm: hasListForm,
layerIds: layerIds,
Expand Down
92 changes: 60 additions & 32 deletions addon/components/layers/odata-vector-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import DS from 'ember-data';
import jsts from 'npm:jsts';
import { capitalize, camelize } from 'ember-flexberry-data/utils/string-functions';
import isUUID from 'ember-flexberry-data/utils/is-uuid';
const { Builder } = Query;
import moment from 'moment';
import getBooleanFromString from '../../utils/get-boolean-from-string';
import { getDateFormatFromString, createTimeInterval } from '../../utils/get-date-from-string';

/**
For batch reading
*/
const maxBatchFeatures = 10000;

const { Builder } = Query;
/**
Investment layer component for leaflet map.
Expand Down Expand Up @@ -463,41 +465,67 @@ export default BaseVectorLayer.extend({
let leafletObject = this.get('_leafletObject');
if (!Ember.isNone(leafletObject)) {
let type = this.get('layerModel.type');
if (!Ember.isBlank(type)) {
if (!Ember.isBlank(type) && !Ember.isBlank(e.searchOptions.queryString)) {
let store = Ember.getOwner(this).lookup('service:store');
let modelConstructor = store.modelFor(leafletObject.modelName);
let layerProperties = Ember.get(modelConstructor, `attributes`);
searchFields.forEach((field) => {
let property;
let accessProperty = false;
if (field === 'primarykey') {
if (isUUID(e.searchOptions.queryString)) {
equals.push(new Query.SimplePredicate('id', Query.FilterOperator.Eq, e.searchOptions.queryString));
}
} else {
property = layerProperties.get(field);
if (!Ember.isNone(property)) {
switch (property.type) {
case 'decimal':
case 'number':
let searchString = e.searchOptions.queryString.replace('.', ',');
accessProperty = !e.context && !isNaN(Number(searchString));
break;
case 'date':
accessProperty = !e.context && new Date(e.searchOptions.queryString).toString() !== 'Invalid Date';
break;
case 'boolean':
accessProperty = !e.context && Boolean(e.searchOptions.queryString);
break;
default:
equals.push(new Query.StringPredicate(property.name).contains(e.searchOptions.queryString));
break;
}
let property = layerProperties.get(field);
e.searchOptions.queryString = e.searchOptions.queryString.trim();
if (field === 'primarykey' && isUUID(e.searchOptions.queryString)) {
equals.push(new Query.SimplePredicate('id', Query.FilterOperator.Eq, e.searchOptions.queryString));
return;
}

if (accessProperty && property.type !== 'string') {
equals.push(new Query.SimplePredicate(property.name, Query.FilterOperator.Eq, e.searchOptions.queryString));
}
if (!Ember.isNone(property)) {
switch (property.type) {
case 'decimal':
case 'number':
let searchValue = e.searchOptions.queryString ? e.searchOptions.queryString.replace(',', '.') : e.searchOptions.queryString;
if (!isNaN(Number(searchValue))) {
equals.push(new Query.SimplePredicate(property.name, Query.FilterOperator.Eq, searchValue));
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to numeric type`);
}

break;
case 'date':
let dateInfo = getDateFormatFromString(e.searchOptions.queryString);
let searchDate = moment(e.searchOptions.queryString, dateInfo.dateFormat + dateInfo.timeFormat, true);
if (searchDate.isValid()) {
let [startInterval, endInterval] = createTimeInterval(searchDate, dateInfo.dateFormat);

if (endInterval) {
let startIntervalCondition = new Query.DatePredicate(property.name, Query.FilterOperator.Geq, startInterval, false);
let endIntervalCondition = new Query.DatePredicate(property.name, Query.FilterOperator.Le, endInterval, false);
equals.push(new Query.ComplexPredicate(Query.Condition.And, startIntervalCondition, endIntervalCondition));
} else if (dateInfo.timeFormat === 'THH:mm:ss.SSSSZ') {
equals.push(new Query.DatePredicate(property.name, Query.FilterOperator.Eq, startInterval, false));
} else {
equals.push(new Query.DatePredicate(property.name, Query.FilterOperator.Eq, startInterval, true));
}
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to date type`);
}

break;
case 'boolean':
let booleanValue = getBooleanFromString(e.searchOptions.queryString);

if (typeof booleanValue === 'boolean') {
equals.push(new Query.SimplePredicate(property.name, Query.FilterOperator.Eq, booleanValue));
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to boolean type`);
}

break;
default:
equals.push(new Query.StringPredicate(property.name).contains(e.searchOptions.queryString));

break;
}
} else {
console.error(`The field name: \"${field}\" is incorrect, check the name of the search attribute in the layer settings`);
}
});
}
Expand All @@ -512,7 +540,7 @@ export default BaseVectorLayer.extend({
filter = new Query.ComplexPredicate(Query.Condition.Or, ...equals);
}

let featuresPromise = this._getFeature(filter, e.searchOptions.maxResultsCount);
let featuresPromise = this._getFeature(filter, e.searchOptions.maxResultsCount + 1);

return featuresPromise;
},
Expand Down
93 changes: 64 additions & 29 deletions addon/components/layers/wfs-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import BaseVectorLayer from '../base-vector-layer';
import { checkMapZoom } from '../../utils/check-zoom';
import { intersectionArea } from '../../utils/feature-with-area-intersect';
import jsts from 'npm:jsts';
import isUUID from 'ember-flexberry-data/utils/is-uuid';
import isUUID from 'ember-flexberry-data/utils/is-uuid';
import state from '../../utils/state';
import moment from 'moment';
import { getDateFormatFromString, createTimeInterval } from '../../utils/get-date-from-string';
import getBooleanFromString from '../../utils/get-boolean-from-string';

/**
WFS layer component for leaflet map.
Expand Down Expand Up @@ -96,6 +99,10 @@ export default BaseVectorLayer.extend({

let wfsLayer = this.get('_leafletObject');

let maxFeatures = Ember.get(options, 'maxFeatures');

Ember.set(Ember.get(wfsLayer, 'options'), 'maxFeatures', maxFeatures ? maxFeatures : 1000);

if (Ember.isNone(wfsLayer)) {
resolve(Ember.A());
return;
Expand Down Expand Up @@ -526,52 +533,80 @@ export default BaseVectorLayer.extend({
let leafletObject = this.get('_leafletObject');
if (!Ember.isNone(leafletObject)) {
let fieldsType = Ember.get(leafletObject, 'readFormat.featureType.fieldTypes');
if (!Ember.isBlank(fieldsType)) {
if (!Ember.isBlank(fieldsType) && !Ember.isBlank(e.searchOptions.queryString)) {
searchFields.forEach((field) => {
e.searchOptions.queryString = e.searchOptions.queryString.trim();
let typeField = fieldsType[field];
if (!Ember.isBlank(typeField)) {
let accessProperty = false;
if (field !== 'primarykey') {
switch (typeField) {
case 'number':
accessProperty = !e.context && !isNaN(Number(e.searchOptions.queryString));
break;
case 'date':
accessProperty = !e.context && new Date(e.searchOptions.queryString).toString() !== 'Invalid Date';
break;
case 'boolean':
accessProperty = !e.context && Boolean(e.searchOptions.queryString);
break;
default:
equals.push(new L.Filter.Like(field, '*' + e.searchOptions.queryString + '*', {
matchCase: false
}));
break;
}
if (field === 'primarykey' && isUUID(e.searchOptions.queryString)) {
equals.push(new L.Filter.EQ(field, e.searchOptions.queryString));
return;
}

if (accessProperty && typeField !== 'string') {
equals.push(new L.Filter.EQ(field, e.searchOptions.queryString));
}
} else {
if (isUUID(e.searchOptions.queryString)) {
equals.push(new L.Filter.EQ(field, e.searchOptions.queryString));
}
switch (typeField) {
case 'decimal':
case 'number':
let searchValue = e.searchOptions.queryString ? e.searchOptions.queryString.replace(',', '.') : e.searchOptions.queryString;
if (!isNaN(Number(searchValue))) {
equals.push(new L.Filter.EQ(field, searchValue, false));
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to numeric type`);
}

break;
case 'date':
let dateInfo = getDateFormatFromString(e.searchOptions.queryString);
let searchDate = moment.utc(e.searchOptions.queryString, dateInfo.dateFormat + dateInfo.timeFormat, true);

if (searchDate.isValid()) {
let [startInterval, endInterval] = createTimeInterval(searchDate, dateInfo.dateFormat);

if (endInterval) {
let startIntervalCondition = new L.Filter.GEQ(field, startInterval, false);
let endIntervalCondition = new L.Filter.LT(field, endInterval, false);
equals.push(new L.Filter.And(startIntervalCondition, endIntervalCondition));
} else {
equals.push(new L.Filter.EQ(field, startInterval, false));
}
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to date type`);
}

break;
case 'boolean':
let booleanValue = getBooleanFromString(e.searchOptions.queryString);

if (typeof booleanValue === 'boolean') {
equals.push(new L.Filter.EQ(field, booleanValue, false));
} else {
console.error(`Failed to convert \"${e.searchOptions.queryString}\" to boolean type`);
}

break;
default:
equals.push(new L.Filter.Like(field, '*' + e.searchOptions.queryString + '*', { matchCase: false }));
break;
}

} else {
console.error(`The field name: \"${field}\" is incorrect, check the name of the search attribute in the layer settings`);
}
});
}
}

let filter;
if (equals.length === 1) {
if (equals.length === 0) {
return Ember.RSVP.resolve(Ember.A());
} else if (equals.length === 1) {
filter = equals[0];
} else {
filter = new L.Filter.Or(...equals);
}

let featuresPromise = this._getFeature({
filter,
maxFeatures: e.searchOptions.maxResultsCount,
maxFeatures: e.searchOptions.maxResultsCount + 1,
fillOpacity: 0.3,
style: {
color: 'yellow',
Expand Down
7 changes: 7 additions & 0 deletions addon/styles/themes/ghost/elements/list.overrides
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@
left: 4.7em;
}

.ui.list:last-child {
.max-limit {
margin: 10px 0 10px 10px;
color: @labelColor;
}
}

.feature-result-item-list+.feature-result-item-upload+.layer-result-list-toggler .title .flexberry-toggler-caption {
margin-left: 5.7em;
max-width: ~"calc(100% - 6em)";
Expand Down
27 changes: 17 additions & 10 deletions addon/templates/components/layer-result-list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<div class="nodata">
{{t "components.layer-result-list.nodata"}}
</div>
{{/if}}
{{#each _displayResults as |result|}}
<div class="feature-result-item-group">
{{else}}
{{#each _displayResults as |result|}}
<div class="feature-result-item-group">
{{#if result.isIntersect}}
<div class="feature-result-item-buttons hidden">
{{#if (eq intersection false)}}
Expand Down Expand Up @@ -248,13 +248,20 @@
}}
{{/if}}
{{/each}}
{{#if result.maxResultsLimitOverage}}
<div class="max-limit">
Найдено более {{maxResultsCount}} объектов по запросу. Пожалуйста, уточните поисковый запрос
</div>
{{/if}}
</div>
{{/flexberry-toggler}}
{{/if}}
</div>
{{/each}}
{{feature-export
visible=exportDialogVisible
result=exportResult
availableCRS=availableCRS
}}
</div>
{{/each}}
{{feature-export
visible=exportDialogVisible
result=exportResult
availableCRS=availableCRS
}}
{{/if}}

14 changes: 14 additions & 0 deletions addon/utils/get-boolean-from-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

export default function getBooleanFromString(str = '') {
let result = null;
switch (str.toLowerCase()) {
case '1' : case 'да': case 'true': case 'yes':
result = true;
break;
case '0' : case 'нет': case 'false': case 'no':
result = false;
break;

}
return result;
}
46 changes: 46 additions & 0 deletions addon/utils/get-date-from-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const dateFormats = ['DD.MM.YYYY', 'DD-MM-YYYY', 'DD/MM/YYYY', 'YYYY/MM/DD', 'YYYY.MM.DD', 'YYYY-MM-DD'];
const timeFormats = [' HH:mm', ' HH:mm:ss', 'THH:mm:ss.SSSSZ', ''];

let getDateFormatFromString = (str) => {

let dateFormat = dateFormats.find(dateFormat => moment(str.split(/[\sT]+/)[0], dateFormat, true).isValid());

let timeFormat = timeFormats.find(timeFormat=> moment(str, dateFormat + timeFormat, true).isValid());

return { dateFormat, timeFormat };
};

let createTimeInterval = (date, dateFormat) => {
if (!date || !dateFormat) {
return [];
}

let startInterval = date.toISOString();
let endInterval = null;
switch (date.creationData().format) {
case dateFormat:

// Search the entire day
endInterval = date.add(1, 'days').toISOString();
break;

case dateFormat + ' HH:mm':

// Search by the exact time in the interval of one minute
endInterval = date.add(60 - date.seconds(), 'seconds').toISOString();
break;
case dateFormat + ' HH:mm:ss':

// Search by the exact time in the interval of one second
endInterval = date.add(1, 'seconds').toISOString();
break;
default:
return [startInterval];
}

return [startInterval, endInterval];
};

export {
getDateFormatFromString, createTimeInterval
};
1 change: 1 addition & 0 deletions app/utils/get-boolean-from-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-flexberry-gis/utils/get-boolean-from-string';
1 change: 1 addition & 0 deletions app/utils/get-date-from-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-flexberry-gis/utils/get-date-from-string';
Loading

0 comments on commit 9a197af

Please sign in to comment.