diff --git a/src/essence/Ancillary/LocalFilterer.js b/src/essence/Ancillary/LocalFilterer.js index 38bce342..e110b28e 100644 --- a/src/essence/Ancillary/LocalFilterer.js +++ b/src/essence/Ancillary/LocalFilterer.js @@ -115,7 +115,7 @@ const LocalFilterer = { refreshFunction(filteredGeoJSON) } else { L_.clearVectorLayer(layerName) - L_.updateVectorLayer(layerName, filteredGeoJSON) + L_.updateVectorLayer(layerName, filteredGeoJSON, null, true) } }, match: function (feature, filter) { diff --git a/src/essence/Tools/Layers/Filtering/ESFilterer.js b/src/essence/Basics/Layers_/Filtering/ESFilterer.js similarity index 96% rename from src/essence/Tools/Layers/Filtering/ESFilterer.js rename to src/essence/Basics/Layers_/Filtering/ESFilterer.js index 44db25b8..c42c7a7e 100644 --- a/src/essence/Tools/Layers/Filtering/ESFilterer.js +++ b/src/essence/Basics/Layers_/Filtering/ESFilterer.js @@ -1,245 +1,245 @@ -// Part of the LayersTool that deals with filtering - -import $ from 'jquery' -import F_ from '../../../Basics/Formulae_/Formulae_' -import L_ from '../../../Basics/Layers_/Layers_' - -const ESFilterer = { - getAggregations: async function (layerName, config) { - const results = await ESFilterer.filter(layerName, null, config) - - const aggs = {} - if (results) { - for (let a in results.aggregations) { - const flatBuckets = {} - results.aggregations[a].buckets.forEach((b) => { - flatBuckets[b.key] = b.doc_count - }) - - aggs[a] = { - aggs: flatBuckets, - type: 'string', - } - } - } - return aggs - }, - filter: async function (layerName, filter, config) { - return new Promise((resolve, reject) => { - let aggs = {} - config.fields = config.fields || {} - for (let f in config.fields) { - aggs[f] = { - terms: { - field: f, - size: config.fields[f] || 10, - }, - } - } - - let must = [] - if (config.must) must = must.concat(config.must) - const filterMust = ESFilterer.getFilterMust(filter) - must = must.concat(filterMust) - - let query = { - query: { - bool: { - must: must, - }, - }, - aggs: aggs, - from: 0, - size: filter == null ? 0 : config.size || 500, - version: true, - } - - if (config.collapse) - query.collapse = { - field: config.collapse, - } - - if (config.sort) query.sort = config.sort - - // Spatial Filter - let spatialFilter - if ( - config.geoshapeProp && - filter?.spatial?.center != null && - filter?.spatial?.feature?.geometry?.type != null - ) { - if (filter.spatial.radius > 0) { - spatialFilter = { - geo_shape: { - [config.geoshapeProp]: { - shape: { - type: filter.spatial.feature.geometry.type.toLowerCase(), - coordinates: - filter.spatial.feature.geometry - .coordinates, - relation: 'intersects', - }, - }, - }, - } - } else { - spatialFilter = { - geo_shape: { - [config.geoshapeProp]: { - shape: { - type: 'point', - coordinates: [ - filter.spatial.center.lng, - filter.spatial.center.lat, - ], - relation: 'contains', - }, - }, - }, - } - } - } - if (spatialFilter) { - query.query.bool.filter = spatialFilter - } - - // Ignore results if no filters set - if (spatialFilter == null && filterMust.length === 0) { - query.size = 0 - } - - // Format query - let body = query - if (config.stringifyBody) body = JSON.stringify(body) - - let finalBody - if (config.bodyWrapper) - finalBody = config.bodyWrapper.replace('{BODY}', body) - else finalBody = body - - // Fetch - const resp = config.esResponses ? 'responses.' : '' - - fetch( - `${config.endpoint}?filter_path=${resp}hits.hits._source,${resp}hits.total,${resp}aggregations`, - { - method: 'POST', - headers: { - accept: 'application/json', - ...(config.headers || {}), - }, - credentials: config.withCredentials ? 'include' : '', - body: finalBody, - } - ) - .then((res) => res.json()) - .then((json) => { - const geojson = F_.getBaseGeoJSON() - const hits = F_.getIn(json, 'responses.0.hits.hits', []) - - hits.forEach((hit) => { - const properties = hit._source || {} - let geometry - for (let p in properties) { - if ( - properties[p] != null && - typeof properties[p] === 'object' && - properties[p].coordinates && - properties[p].type - ) { - geometry = JSON.parse( - JSON.stringify(properties[p]) - ) - delete properties[p] - break - } - } - if (geometry) - geojson.features.push({ - type: 'Feature', - properties, - geometry, - }) - }) - // Set count - $('#layersTool_filtering_count').text( - `(${geojson.features.length} out of ${F_.getIn( - json, - config.esResponses - ? 'responses.0.hits.total.value' - : 'hits.total.value', - 0 - )})` - ) - - // Update layer - L_.clearVectorLayer(layerName) - L_.updateVectorLayer(layerName, geojson) - - resolve( - config.esResponses - ? F_.getIn(json, 'responses.0') - : json - ) - }) - .catch((err) => { - console.log(err) - resolve() - }) - }) - }, - getFilterMust: function (filter) { - let must = [] - if (filter && filter.values && filter.values.length > 0) { - filter.values.forEach((v) => { - if (v == null || v.key == null || v.value == null) return - switch (v.op) { - case '=': - must.push({ - match: { - [v.key]: v.value, - }, - }) - break - case ',': - const stringValue = v.value + '' - must.push({ - bool: { - should: stringValue.split(',').map((sv) => { - return { - match: { - [v.key]: sv, - }, - } - }), - }, - }) - break - case '>': - must.push({ - range: { - [v.key]: { - gt: v.value, - }, - }, - }) - break - case '<': - must.push({ - range: { - [v.key]: { - lt: v.value, - }, - }, - }) - break - default: - break - } - }) - } - return must - }, -} - -export default ESFilterer +// Part of the LayersTool that deals with filtering + +import $ from 'jquery' +import F_ from '../../Formulae_/Formulae_' +import L_ from '../../Layers_/Layers_' + +const ESFilterer = { + getAggregations: async function (layerName, config) { + const results = await ESFilterer.filter(layerName, null, config) + + const aggs = {} + if (results) { + for (let a in results.aggregations) { + const flatBuckets = {} + results.aggregations[a].buckets.forEach((b) => { + flatBuckets[b.key] = b.doc_count + }) + + aggs[a] = { + aggs: flatBuckets, + type: 'string', + } + } + } + return aggs + }, + filter: async function (layerName, filter, config) { + return new Promise((resolve, reject) => { + let aggs = {} + config.fields = config.fields || {} + for (let f in config.fields) { + aggs[f] = { + terms: { + field: f, + size: config.fields[f] || 10, + }, + } + } + + let must = [] + if (config.must) must = must.concat(config.must) + const filterMust = ESFilterer.getFilterMust(filter) + must = must.concat(filterMust) + + let query = { + query: { + bool: { + must: must, + }, + }, + aggs: aggs, + from: 0, + size: filter == null ? 0 : config.size || 500, + version: true, + } + + if (config.collapse) + query.collapse = { + field: config.collapse, + } + + if (config.sort) query.sort = config.sort + + // Spatial Filter + let spatialFilter + if ( + config.geoshapeProp && + filter?.spatial?.center != null && + filter?.spatial?.feature?.geometry?.type != null + ) { + if (filter.spatial.radius > 0) { + spatialFilter = { + geo_shape: { + [config.geoshapeProp]: { + shape: { + type: filter.spatial.feature.geometry.type.toLowerCase(), + coordinates: + filter.spatial.feature.geometry + .coordinates, + relation: 'intersects', + }, + }, + }, + } + } else { + spatialFilter = { + geo_shape: { + [config.geoshapeProp]: { + shape: { + type: 'point', + coordinates: [ + filter.spatial.center.lng, + filter.spatial.center.lat, + ], + relation: 'contains', + }, + }, + }, + } + } + } + if (spatialFilter) { + query.query.bool.filter = spatialFilter + } + + // Ignore results if no filters set + if (spatialFilter == null && filterMust.length === 0) { + query.size = 0 + } + + // Format query + let body = query + if (config.stringifyBody) body = JSON.stringify(body) + + let finalBody + if (config.bodyWrapper) + finalBody = config.bodyWrapper.replace('{BODY}', body) + else finalBody = body + + // Fetch + const resp = config.esResponses ? 'responses.' : '' + + fetch( + `${config.endpoint}?filter_path=${resp}hits.hits._source,${resp}hits.total,${resp}aggregations`, + { + method: 'POST', + headers: { + accept: 'application/json', + ...(config.headers || {}), + }, + credentials: config.withCredentials ? 'include' : '', + body: finalBody, + } + ) + .then((res) => res.json()) + .then((json) => { + const geojson = F_.getBaseGeoJSON() + const hits = F_.getIn(json, 'responses.0.hits.hits', []) + + hits.forEach((hit) => { + const properties = hit._source || {} + let geometry + for (let p in properties) { + if ( + properties[p] != null && + typeof properties[p] === 'object' && + properties[p].coordinates && + properties[p].type + ) { + geometry = JSON.parse( + JSON.stringify(properties[p]) + ) + delete properties[p] + break + } + } + if (geometry) + geojson.features.push({ + type: 'Feature', + properties, + geometry, + }) + }) + // Set count + $('#layersTool_filtering_count').text( + `(${geojson.features.length} out of ${F_.getIn( + json, + config.esResponses + ? 'responses.0.hits.total.value' + : 'hits.total.value', + 0 + )})` + ) + + // Update layer + L_.clearVectorLayer(layerName) + L_.updateVectorLayer(layerName, geojson) + + resolve( + config.esResponses + ? F_.getIn(json, 'responses.0') + : json + ) + }) + .catch((err) => { + console.log(err) + resolve() + }) + }) + }, + getFilterMust: function (filter) { + let must = [] + if (filter && filter.values && filter.values.length > 0) { + filter.values.forEach((v) => { + if (v == null || v.key == null || v.value == null) return + switch (v.op) { + case '=': + must.push({ + match: { + [v.key]: v.value, + }, + }) + break + case ',': + const stringValue = v.value + '' + must.push({ + bool: { + should: stringValue.split(',').map((sv) => { + return { + match: { + [v.key]: sv, + }, + } + }), + }, + }) + break + case '>': + must.push({ + range: { + [v.key]: { + gt: v.value, + }, + }, + }) + break + case '<': + must.push({ + range: { + [v.key]: { + lt: v.value, + }, + }, + }) + break + default: + break + } + }) + } + return must + }, +} + +export default ESFilterer diff --git a/src/essence/Tools/Layers/Filtering/Filtering.css b/src/essence/Basics/Layers_/Filtering/Filtering.css similarity index 95% rename from src/essence/Tools/Layers/Filtering/Filtering.css rename to src/essence/Basics/Layers_/Filtering/Filtering.css index abbf0dff..e8604a1a 100644 --- a/src/essence/Tools/Layers/Filtering/Filtering.css +++ b/src/essence/Basics/Layers_/Filtering/Filtering.css @@ -1,289 +1,289 @@ -#layersTool_filtering { - border-top: 1px solid var(--color-a); - background: var(--color-a2); - display: none; -} -.gears_on #layersTool_filtering { - display: block; -} -#layersTool_filtering_header { - display: flex; - justify-content: space-between; - height: 30px; - line-height: 30px; -} -#layersTool_filtering_title_left { - display: flex; -} -#layersTool_filtering_title { - font-size: 16px; - padding-left: 14px; - line-height: 31px; -} -#layersTool_filtering_count { - font-size: 12px; - padding: 0px 4px; - color: #d6d6d6; -} -#layersTool_filtering_add_value { - padding: 0px 6px; - color: var(--color-a6); -} -#layersTool_filtering_add_value:hover { - background: var(--color-c); - color: var(--color-a7); -} -#layersTool_filtering_add_value > div { - font-size: 11px; - margin-right: 2px; - line-height: 32px; - text-transform: uppercase; -} - -#layerTool_filtering_filters { - border-left: 1px solid var(--color-a2); - border-right: 1px solid var(--color-a2); -} - -#layerTool_filtering_filters_spatial { - display: flex; - border-bottom: 1px solid #353535; -} -#layerTool_filtering_filters_spatial_draw { - flex: 1; - display: flex; - overflow: hidden; - white-space: nowrap; - padding-left: 0px; - margin: 4px 8px; - height: 22px; - line-height: 22px; - border-radius: 4px; - background: var(--color-a3); -} -#layerTool_filtering_filters_spatial_draw > div { - line-height: 24px; - font-size: 13px; -} -#layerTool_filtering_filters_spatial_draw > i { - color: var(--color-a6); - width: 22px; - height: 22px; - line-height: 24px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - padding-left: 2px; - margin-right: 6px; -} -#layerTool_filtering_filters_spatial.drawing - #layerTool_filtering_filters_spatial_draw - > i { - background: var(--color-r7); - color: black; -} -#layerTool_filtering_filters_spatial.drawn - #layerTool_filtering_filters_spatial_draw - > i { - background: var(--color-green2); - color: black; -} -#layerTool_filtering_filters_spatial_draw:hover { - background: var(--color-c); -} -.layerTool_filtering_filters_clear { - width: 30px; - background: var(--color-a1-5); - padding: 0px 6px; - color: #888; -} -.layerTool_filtering_filters_clear:hover { - background: var(--color-a1); - color: #fff; -} -#layerTool_filtering_filters_spatial_radius_wrapper { - display: flex; - background: var(--color-a1-5); - color: #ccc; - width: 131px; - box-sizing: border-box; - border-right: 1px solid var(--color-a2); -} -#layersTool_filtering - #layerTool_filtering_filters_spatial_radius_wrapper - > input { - background: var(--color-a1-5); - padding-left: 0px; - text-align: right; - font-size: 13px; -} -#layersTool_filtering - #layerTool_filtering_filters_spatial_radius_wrapper - > input:hover { - background: var(--color-a1-5); -} -#layerTool_filtering_filters_spatial_radius_wrapper > div:first-child { - line-height: 30px; - padding: 0px 8px; - font-size: 12px; -} -#layerTool_filtering_filters_spatial_radius_wrapper > div:last-child { - line-height: 32px; - height: 30px; - padding: 0px 8px 0px 1px; - font-size: 12px; -} - -#layersTool_filtering_footer { - display: flex; - justify-content: space-between; - height: 30px; - line-height: 30px; -} -#layersTool_filtering_adds { - display: flex; - justify-content: space-between; -} -#layersTool_filtering_adds > div > div { - padding-left: 2px; -} - -.layersTool_filtering_value { - display: flex; - height: 31px; - border-bottom: 1px solid var(--color-a2); -} -.layersTool_filtering_value .layersTool_filtering_value_key { - flex: 1; -} -.layersTool_filtering_value_operator { - box-sizing: border-box; - border-left: 1px solid var(--color-a2); - border-right: 1px solid var(--color-a2); -} -.layersTool_filtering_value .layersTool_filtering_value_value { - flex: 1; - position: relative; -} - -.layersTool_filtering_value_value_type { - position: absolute; - right: 0; - top: 0; - pointer-events: none; - height: 30px; - line-height: 30px; - width: 30px; - text-align: center; - color: #999; - opacity: 0; - transition: opacity 0.2s ease-out; -} -.layersTool_filtering_value_value_type > i:first-child { - color: var(--color-yellow); -} -.layersTool_filtering_value_value_type > i:last-child { - color: var(--color-green); -} - -.layersTool_filtering_value_value_input:hover - + .layersTool_filtering_value_value_type { - opacity: 1; -} - -#layersTool_filtering input { - background: var(--color-a1-5); - color: #ccc; - width: 100%; - box-sizing: border-box; - border-left: 6px solid var(--color-a); - height: 30px; - padding-left: 8px; - border: none; - font-size: 14px; - transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); -} -#layersTool_filtering input:hover { - background: var(--color-a1); -} -.layersTool_filtering_value_value_input { - border-right: 1px solid var(--color-a2) !important; -} -#layersTool_filtering select { - background: #111; - color: #ccc; - height: 30px; -} -.layersTool_filtering_value_operator_select { - background: var(--color-a1); - color: var(--color-f); - border: none; - cursor: pointer; - height: 30px; - transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); -} -.layersTool_filtering_value_operator_select:hover { - background: var(--color-a2); -} - -.layersTool_filtering_value_remove .mmgisButton5 { - width: 22px; -} -#layersTool_filtering_clear { - padding: 0px 14px; -} -#layersTool_filtering_clear:hover { - background: var(--color-red2); -} -#layersTool_filtering_submit { - padding: 0px 8px; - transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); -} -#layersTool_filtering_submit.active { - background: var(--color-h); - color: black; -} -#layersTool_filtering_submit.active:hover { - background: #fffb80; -} - -#layersTool_filtering .dropy, -#layersTool_filtering .dropy__title { - height: 30px; - border: none; -} -#layersTool_filtering .dropy__title span { - padding: 6px; -} -#layersTool_filtering .dropy .dropy__content li a { - padding: 6px; - border-top: 1px solid var(--color-m); - margin-bottom: 0px; - height: 30px; -} -#layersTool_filtering .dropy__content .dropy__header { - display: none; -} -#layersTool_filtering .dropy__content ul { - transition: none; -} - -#layersTool_filtering_submit_loading { - display: none; - padding: 6px 6px 6px 0px; -} -#layersTool_filtering_submit_loading.active { - display: block; -} -#layersTool_filtering_submit_loading.active > div { - width: 18px; - height: 18px; - border-radius: 50%; - border: 3px solid black; - border-top: 3px solid transparent; - animation: submitLoadingSpin 1s ease-in-out infinite; -} -@keyframes submitLoadingSpin { - to { - transform: rotate(360deg); - } -} +#layersTool_filtering { + border-top: 1px solid var(--color-a); + background: var(--color-a2); + display: none; +} +.gears_on #layersTool_filtering { + display: block; +} +#layersTool_filtering_header { + display: flex; + justify-content: space-between; + height: 30px; + line-height: 30px; +} +#layersTool_filtering_title_left { + display: flex; +} +#layersTool_filtering_title { + font-size: 16px; + padding-left: 14px; + line-height: 31px; +} +#layersTool_filtering_count { + font-size: 12px; + padding: 0px 4px; + color: #d6d6d6; +} +#layersTool_filtering_add_value { + padding: 0px 6px; + color: var(--color-a6); +} +#layersTool_filtering_add_value:hover { + background: var(--color-c); + color: var(--color-a7); +} +#layersTool_filtering_add_value > div { + font-size: 11px; + margin-right: 2px; + line-height: 32px; + text-transform: uppercase; +} + +#layerTool_filtering_filters { + border-left: 1px solid var(--color-a2); + border-right: 1px solid var(--color-a2); +} + +#layerTool_filtering_filters_spatial { + display: flex; + border-bottom: 1px solid #353535; +} +#layerTool_filtering_filters_spatial_draw { + flex: 1; + display: flex; + overflow: hidden; + white-space: nowrap; + padding-left: 0px; + margin: 4px 8px; + height: 22px; + line-height: 22px; + border-radius: 4px; + background: var(--color-a3); +} +#layerTool_filtering_filters_spatial_draw > div { + line-height: 24px; + font-size: 13px; +} +#layerTool_filtering_filters_spatial_draw > i { + color: var(--color-a6); + width: 22px; + height: 22px; + line-height: 24px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + padding-left: 2px; + margin-right: 6px; +} +#layerTool_filtering_filters_spatial.drawing + #layerTool_filtering_filters_spatial_draw + > i { + background: var(--color-r7); + color: black; +} +#layerTool_filtering_filters_spatial.drawn + #layerTool_filtering_filters_spatial_draw + > i { + background: var(--color-green2); + color: black; +} +#layerTool_filtering_filters_spatial_draw:hover { + background: var(--color-c); +} +.layerTool_filtering_filters_clear { + width: 30px; + background: var(--color-a1-5); + padding: 0px 6px; + color: #888; +} +.layerTool_filtering_filters_clear:hover { + background: var(--color-a1); + color: #fff; +} +#layerTool_filtering_filters_spatial_radius_wrapper { + display: flex; + background: var(--color-a1-5); + color: #ccc; + width: 131px; + box-sizing: border-box; + border-right: 1px solid var(--color-a2); +} +#layersTool_filtering + #layerTool_filtering_filters_spatial_radius_wrapper + > input { + background: var(--color-a1-5); + padding-left: 0px; + text-align: right; + font-size: 13px; +} +#layersTool_filtering + #layerTool_filtering_filters_spatial_radius_wrapper + > input:hover { + background: var(--color-a1-5); +} +#layerTool_filtering_filters_spatial_radius_wrapper > div:first-child { + line-height: 30px; + padding: 0px 8px; + font-size: 12px; +} +#layerTool_filtering_filters_spatial_radius_wrapper > div:last-child { + line-height: 32px; + height: 30px; + padding: 0px 8px 0px 1px; + font-size: 12px; +} + +#layersTool_filtering_footer { + display: flex; + justify-content: space-between; + height: 30px; + line-height: 30px; +} +#layersTool_filtering_adds { + display: flex; + justify-content: space-between; +} +#layersTool_filtering_adds > div > div { + padding-left: 2px; +} + +.layersTool_filtering_value { + display: flex; + height: 31px; + border-bottom: 1px solid var(--color-a2); +} +.layersTool_filtering_value .layersTool_filtering_value_key { + flex: 1; +} +.layersTool_filtering_value_operator { + box-sizing: border-box; + border-left: 1px solid var(--color-a2); + border-right: 1px solid var(--color-a2); +} +.layersTool_filtering_value .layersTool_filtering_value_value { + flex: 1; + position: relative; +} + +.layersTool_filtering_value_value_type { + position: absolute; + right: 0; + top: 0; + pointer-events: none; + height: 30px; + line-height: 30px; + width: 30px; + text-align: center; + color: #999; + opacity: 0; + transition: opacity 0.2s ease-out; +} +.layersTool_filtering_value_value_type > i:first-child { + color: var(--color-yellow); +} +.layersTool_filtering_value_value_type > i:last-child { + color: var(--color-green); +} + +.layersTool_filtering_value_value_input:hover + + .layersTool_filtering_value_value_type { + opacity: 1; +} + +#layersTool_filtering input { + background: var(--color-a1-5); + color: #ccc; + width: 100%; + box-sizing: border-box; + border-left: 6px solid var(--color-a); + height: 30px; + padding-left: 8px; + border: none; + font-size: 14px; + transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); +} +#layersTool_filtering input:hover { + background: var(--color-a1); +} +.layersTool_filtering_value_value_input { + border-right: 1px solid var(--color-a2) !important; +} +#layersTool_filtering select { + background: #111; + color: #ccc; + height: 30px; +} +.layersTool_filtering_value_operator_select { + background: var(--color-a1); + color: var(--color-f); + border: none; + cursor: pointer; + height: 30px; + transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); +} +.layersTool_filtering_value_operator_select:hover { + background: var(--color-a2); +} + +.layersTool_filtering_value_remove .mmgisButton5 { + width: 22px; +} +#layersTool_filtering_clear { + padding: 0px 14px; +} +#layersTool_filtering_clear:hover { + background: var(--color-red2); +} +#layersTool_filtering_submit { + padding: 0px 8px; + transition: background 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86); +} +#layersTool_filtering_submit.active { + background: var(--color-h); + color: black; +} +#layersTool_filtering_submit.active:hover { + background: #fffb80; +} + +#layersTool_filtering .dropy, +#layersTool_filtering .dropy__title { + height: 30px; + border: none; +} +#layersTool_filtering .dropy__title span { + padding: 6px; +} +#layersTool_filtering .dropy .dropy__content li a { + padding: 6px; + border-top: 1px solid var(--color-m); + margin-bottom: 0px; + height: 30px; +} +#layersTool_filtering .dropy__content .dropy__header { + display: none; +} +#layersTool_filtering .dropy__content ul { + transition: none; +} + +#layersTool_filtering_submit_loading { + display: none; + padding: 6px 6px 6px 0px; +} +#layersTool_filtering_submit_loading.active { + display: block; +} +#layersTool_filtering_submit_loading.active > div { + width: 18px; + height: 18px; + border-radius: 50%; + border: 3px solid black; + border-top: 3px solid transparent; + animation: submitLoadingSpin 1s ease-in-out infinite; +} +@keyframes submitLoadingSpin { + to { + transform: rotate(360deg); + } +} diff --git a/src/essence/Tools/Layers/Filtering/Filtering.js b/src/essence/Basics/Layers_/Filtering/Filtering.js similarity index 92% rename from src/essence/Tools/Layers/Filtering/Filtering.js rename to src/essence/Basics/Layers_/Filtering/Filtering.js index 2d0867cb..7dfebea9 100644 --- a/src/essence/Tools/Layers/Filtering/Filtering.js +++ b/src/essence/Basics/Layers_/Filtering/Filtering.js @@ -1,611 +1,656 @@ -// Part of the LayersTool that deals with filtering - -import $ from 'jquery' -import F_ from '../../../Basics/Formulae_/Formulae_' -import L_ from '../../../Basics/Layers_/Layers_' -import Map_ from '../../../Basics/Map_/Map_' - -import LocalFilterer from '../../../Ancillary/LocalFilterer' -import ESFilterer from './ESFilterer' - -import Help from '../../../Ancillary/Help' -import Dropy from '../../../../external/Dropy/dropy' -import { circle } from '@turf/turf' - -import './Filtering.css' - -const helpKey = 'LayersTool-Filtering' - -const Filtering = { - filters: {}, - current: {}, - mapSpatialLayer: null, - make: async function (container, layerName) { - const layerObj = L_.layers.data[layerName] - - if (layerObj == null) return - - Filtering.filters[layerName] = Filtering.filters[layerName] || { - spatial: { - center: null, - radius: 0, - }, - values: [], - geojson: null, - } - Filtering.current = { - layerName: layerName, - layerObj: layerObj, - type: layerObj.type, - } - - if (Filtering.current.type === 'vector') { - try { - Filtering.filters[layerName].geojson = - Filtering.filters[layerName].geojson || - L_.layers.layer[layerName].toGeoJSON(L_.GEOJSON_PRECISION) - } catch (err) { - console.warn( - `Filtering - Cannot find GeoJSON to filter on for layer: ${layerName}` - ) - return - } - Filtering.filters[layerName].aggs = LocalFilterer.getAggregations( - Filtering.filters[layerName].geojson - ) - } else if (Filtering.current.type === 'query') { - Filtering.filters[layerName].aggs = - await ESFilterer.getAggregations( - layerName, - Filtering.getConfig() - ) - } - const spatialActive = - Filtering.filters[layerName].spatial?.center != null - - // prettier-ignore - const markup = [ - "
", - "
", - "
", - "
Filter
", - Help.getComponent(helpKey), - "
", - "
", - "
", - "
Add
", - "
", - "
", - "
", - "", - `", - "
", - `", - "
", - ].join('\n') - - container.append(markup) - - Filtering.filters[layerName].values.forEach((v) => { - if (v) Filtering.addValue(layerName, v) - }) - - Filtering.attachEvents(layerName) - - Filtering.drawSpatialLayer( - layerName, - Filtering.filters[layerName].spatial.center, - Filtering.filters[layerName].spatial.radius - ) - - // Start with one empty row added - if ( - $('#layerTool_filtering_filters_list .layersTool_filtering_value') - .length === 0 - ) - Filtering.addValue(layerName) - - Help.finalize(helpKey) - }, - destroy: function () { - // Clear Spatial Filter - Map_.rmNotNull(Filtering.mapSpatialLayer) - - $('#layersTool_filtering').remove() - }, - addValue: function (layerName, value) { - let id, key, op, val - if (value) { - id = value.id - key = value.key != null ? ` value='${value.key}'` : '' - op = value.op - val = value.value != null ? ` value='${value.value}'` : '' - } else id = Filtering.filters[layerName].values.length - - // prettier-ignore - const valueMarkup = [ - `
`, - "
", - ``, - "
", - "
", - `
`, - "
", - "
", - ``, - `
`, - ``, - ``, - `
`, - "
", - `
`, - "
", - ].join('\n') - - $('#layerTool_filtering_filters_list').append(valueMarkup) - - if (value == null) { - Filtering.filters[layerName].values.push({ - id: id, - type: null, - key: null, - op: '=', - value: null, - }) - } - - Filtering.attachValueEvents(id, layerName, { op: op }) - - // Show footer iff value rows exist - $('#layersTool_filtering_footer').css( - 'display', - Filtering.filters[layerName].values.length === 0 ? 'none' : 'flex' - ) - }, - drawSpatialLayer: function (layerName, center, radius) { - Map_.rmNotNull(Filtering.mapSpatialLayer) - - Filtering.setSubmitButtonState(true) - if (center == null) return - - const style = { - fillOpacity: 0.1, - fillColor: 'white', - color: 'lime', - weight: 2, - opacity: 1, - className: 'noPointerEventsImportant', - } - - if (radius > 0) { - // Buffered Circle - const geojson = F_.getBaseGeoJSON() - geojson.features.push( - circle( - [center.lng, center.lat], - radius * 0.001 * F_.getEarthToPlanetRatio() - ) - ) - - Filtering.mapSpatialLayer = L.geoJSON(geojson, { - style: style, - }).addTo(Map_.map) - Filtering.filters[layerName].spatial.feature = geojson.features[0] - } else { - // Circle marker - Filtering.mapSpatialLayer = new L.circleMarker( - [center.lat, center.lng], - style - ) - .setRadius(4) - .addTo(Map_.map) - - Filtering.filters[layerName].spatial.feature = { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [center.lng, center.lat], - }, - } - } - Filtering.mapSpatialLayer.bringToFront() - }, - // To highlight the submit button to indicate a change's been made in the form - setSubmitButtonState: function (active) { - if (active) { - $('#layersTool_filtering_submit_text').text('Submit') - $('#layersTool_filtering_submit').addClass('active') - } else if ($('#layersTool_filtering_submit').hasClass('active')) { - $('#layersTool_filtering_submit_text').text('Submitted') - $('#layersTool_filtering_submit').removeClass('active') - } - }, - attachEvents: function (layerName) { - // Add Value - $('#layersTool_filtering_add_value').on('click', function () { - Filtering.addValue(layerName) - }) - - // Draw - $('#layerTool_filtering_filters_spatial_draw').on('click', function () { - Map_.rmNotNull(Filtering.mapSpatialLayer) - $('#map').css('cursor', 'crosshair') - $('#layerTool_filtering_filters_spatial_draw > div').text( - 'Placing Point' - ) - $('#layerTool_filtering_filters_spatial').removeClass('drawn') - $('#layerTool_filtering_filters_spatial').addClass('drawing') - Map_.map.on('click', spatialOnClick) - }) - function spatialOnClick(e) { - Map_.map.off('click', spatialOnClick) - $('#map').css('cursor', 'grab') - $('#layerTool_filtering_filters_spatial_draw > div').text('Active') - $('#layerTool_filtering_filters_spatial').removeClass('drawing') - $('#layerTool_filtering_filters_spatial').addClass('drawn') - - Filtering.filters[layerName].spatial.center = { - lng: e.latlng.lng, - lat: e.latlng.lat, - } - Filtering.drawSpatialLayer( - layerName, - Filtering.filters[layerName].spatial.center, - Filtering.filters[layerName].spatial.radius - ) - } - // Draw - Radius - $('#layerTool_filtering_filters_spatial_radius').on( - 'input', - function (e) { - Filtering.filters[layerName].spatial.radius = parseFloat( - $(this).val() - ) - Filtering.drawSpatialLayer( - layerName, - Filtering.filters[layerName].spatial.center, - Filtering.filters[layerName].spatial.radius - ) - } - ) - // Draw - Clear - $('#layerTool_filtering_filters_spatial_clear').on( - 'click', - function () { - Filtering.filters[layerName].spatial.center = null - Map_.map.off('click', spatialOnClick) - $('#map').css('cursor', 'grab') - $('#layerTool_filtering_filters_spatial_draw > div').text( - 'Place Point' - ) - $('#layerTool_filtering_filters_spatial').removeClass('drawn') - $('#layerTool_filtering_filters_spatial').removeClass('drawing') - - Filtering.drawSpatialLayer( - layerName, - Filtering.filters[layerName].spatial.center, - Filtering.filters[layerName].spatial.radius - ) - } - ) - - // Submit - $(`#layersTool_filtering_submit`).on('click', async () => { - Filtering.setSubmitButtonState(true) - $(`#layersTool_filtering_submit_loading`).addClass('active') - if (Filtering.current.type === 'vector') { - LocalFilterer.filter(layerName, Filtering.filters[layerName]) - } else if (Filtering.current.type === 'query') { - await ESFilterer.filter( - layerName, - Filtering.filters[layerName], - Filtering.getConfig() - ) - } - - $(`#layersTool_filtering_submit_loading`).removeClass('active') - Filtering.setSubmitButtonState(false) - - if (Filtering.mapSpatialLayer) - Filtering.mapSpatialLayer.bringToFront() - }) - - // Clear - $(`#layersTool_filtering_clear`).on('click', async () => { - // Clear Spatial Filter - $('#layerTool_filtering_filters_spatial_clear').click() - $(`#layersTool_filtering_submit_loading`).addClass('active') - - // Clear value filter elements - Filtering.filters[layerName].values = Filtering.filters[ - layerName - ].values.filter((v) => { - if (v) - $( - `#layersTool_filtering_value_${F_.getSafeName( - layerName - )}_${v.id}` - ).remove() - return false - }) - - // Refilter to show all - if (Filtering.current.type === 'vector') { - LocalFilterer.filter(layerName, Filtering.filters[layerName]) - } else if (Filtering.current.type === 'query') { - await ESFilterer.filter( - layerName, - Filtering.filters[layerName], - Filtering.getConfig() - ) - } - - // Reset count - $('#layersTool_filtering_count').text('') - - Filtering.setSubmitButtonState(false) - - $(`#layersTool_filtering_submit_loading`).removeClass('active') - - if (Filtering.mapSpatialLayer) - Filtering.mapSpatialLayer.bringToFront() - }) - }, - attachValueEvents: function (id, layerName, options) { - options = options || {} - - let elmId - - // Expand input boxes on focus - // Contract input boxes on blur - elmId = `#layersTool_filtering_value_key_input_${F_.getSafeName( - layerName - )}_${id}` - $(elmId).on('focus', function () { - $(this).parent().css('flex', '4 1') - }) - $(elmId).on('blur', function () { - $(this).parent().css('flex', '1 1') - }) - elmId = `#layersTool_filtering_value_value_input_${F_.getSafeName( - layerName - )}_${id}` - $(elmId).on('focus', function () { - $(this).parent().css('flex', '4 1') - }) - $(elmId).on('blur', function () { - $(this).parent().css('flex', '1 1') - }) - // Clear - elmId = `#layersTool_filtering_value_clear_${F_.getSafeName( - layerName - )}_${id}` - - $(elmId).on('click', () => { - // Clear value filter element - for ( - let i = 0; - i < Filtering.filters[layerName].values.length; - i++ - ) { - const vId = Filtering.filters[layerName].values[i]?.id - if (vId != null && vId === id) { - $( - `#layersTool_filtering_value_${F_.getSafeName( - layerName - )}_${vId}` - ).remove() - Filtering.filters[layerName].values[i] = null - } - } - Filtering.setSubmitButtonState(true) - }) - - // Property Autocomplete - elmId = `#layersTool_filtering_value_key_input_${F_.getSafeName( - layerName - )}_${id}` - - let arrayToSearch = Object.keys(Filtering.filters[layerName].aggs) - arrayToSearch = arrayToSearch.sort((a, b) => b.localeCompare(a)) - - $(elmId).autocomplete({ - lookup: arrayToSearch, - lookupLimit: 100, - minChars: 0, - transformResult: function (response, originalQuery) { - let resultSuggestions = [] - $.map(response, function (jsonItem) { - if (typeof jsonItem != 'string') { - $.map(jsonItem, function (suggestionItem) { - resultSuggestions.push(suggestionItem) - }) - } - }) - resultSuggestions.sort(function (a, b) { - const aStart = String(a.value).match( - new RegExp(originalQuery, 'i') - ) || { index: -1 }, - bStart = String(b.value).match( - new RegExp(originalQuery, 'i') - ) || { index: -1 } - if (aStart.index != bStart.index) - return aStart.index - bStart.index - else return a > b ? 1 : -1 - }) - response.suggestions = resultSuggestions - return response - }, - onSelect: function (event) { - const property = Filtering.filters[layerName].aggs[event.value] - Filtering.filters[layerName].values[id].type = property.type - Filtering.filters[layerName].values[id].key = event.value - Filtering.updateValuesAutoComplete(id, layerName) - Filtering.setSubmitButtonState(true) - $(this).css('border', 'none') - $(this).css( - 'border-left', - `6px solid ${F_.stringToColor(event.value)}` - ) - }, - }) - - $(elmId).on('blur', function (event) { - const property = - Filtering.filters[layerName].aggs[event.value || $(this).val()] - if (property) { - if ( - Filtering.filters[layerName].values[id] && - Filtering.filters[layerName].values[id].key !== event.value - ) { - Filtering.filters[layerName].values[id].key = event.value - Filtering.filters[layerName].values[id].type = property.type - Filtering.updateValuesAutoComplete(id, layerName) - Filtering.setSubmitButtonState(true) - } - $(this).css('border', 'none') - $(this).css( - 'border-left', - `6px solid ${F_.stringToColor($(this).val())}` - ) - } else $(this).css('border', '1px solid red') - }) - - // Operator Dropdown - elmId = `#layersTool_filtering_value_operator_${F_.getSafeName( - layerName - )}_${id}` - - const ops = ['=', ',', '<', '>'] - const opId = Math.max(ops.indexOf(options.op), 0) - $(elmId).html( - Dropy.construct( - [ - ``, - `
in
`, - ``, - ``, - ], - 'op', - opId, - { openUp: true, hideChevron: true } - ) - ) - Dropy.init($(elmId), function (idx) { - Filtering.filters[layerName].values[id].op = ops[idx] - Filtering.setSubmitButtonState(true) - }) - - // Value AutoComplete - Filtering.updateValuesAutoComplete(id, layerName) - }, - updateValuesAutoComplete: function (id, layerName) { - let elmId = `#layersTool_filtering_value_value_input_${F_.getSafeName( - layerName - )}_${id}` - let arrayToSearch = [] - if ( - Filtering.filters[layerName].values[id].key && - Filtering.filters[layerName].aggs[ - Filtering.filters[layerName].values[id].key - ] - ) - arrayToSearch = Object.keys( - Filtering.filters[layerName].aggs[ - Filtering.filters[layerName].values[id].key - ].aggs || {} - ) - $(elmId).autocomplete({ - lookup: arrayToSearch, - lookupLimit: 150, - minChars: 0, - transformResult: function (response, originalQuery) { - let resultSuggestions = [] - $.map(response, function (jsonItem) { - if (typeof jsonItem != 'string') { - $.map(jsonItem, function (suggestionItem) { - resultSuggestions.push(suggestionItem) - }) - } - }) - resultSuggestions.sort(function (a, b) { - const aStart = String(a.value).match( - new RegExp(originalQuery, 'i') - ) || { index: -1 }, - bStart = String(b.value).match( - new RegExp(originalQuery, 'i') - ) || { index: -1 } - if (aStart.index != bStart.index) - return aStart.index - bStart.index - else return a > b ? 1 : -1 - }) - response.suggestions = resultSuggestions - return response - }, - onSelect: function (event) { - Filtering.filters[layerName].values[id].value = event.value - Filtering.setSubmitButtonState(true) - }, - }) - $(elmId).on('keyup', function (e) { - Filtering.filters[layerName].values[id].value = $(this).val() - Filtering.setSubmitButtonState(true) - }) - - $('.autocomplete-suggestions').css({ - 'max-height': '300px', - 'border-top': 'none', - }) - - // Change type indicator icons too - const numberElmId = `#layersTool_filtering_value_value_type_number_${F_.getSafeName( - layerName - )}_${id}` - const stringElmId = `#layersTool_filtering_value_value_type_string_${F_.getSafeName( - layerName - )}_${id}` - switch (Filtering.filters[layerName].values[id].type) { - case 'number': - $(numberElmId).css('display', 'inherit') - $(stringElmId).css('display', 'none') - break - case 'string': - $(stringElmId).css('display', 'inherit') - $(numberElmId).css('display', 'none') - break - default: - $(numberElmId).css('display', 'none') - $(stringElmId).css('display', 'none') - break - } - }, - getConfig: function () { - if ( - Filtering.current.layerObj.type === 'query' && - Filtering.current.layerObj.query - ) { - return { - endpoint: Filtering.current.layerObj.query.endpoint, - type: Filtering.current.layerObj.query.type || 'elasticsearch', - ...(Filtering.current.layerObj.variables - ? Filtering.current.layerObj.variables.query || {} - : {}), - } - } - return {} - }, -} - -export default Filtering +// Part of the LayersTool that deals with filtering + +import $ from 'jquery' +import F_ from '../../Formulae_/Formulae_' +import L_ from '../../Layers_/Layers_' +import Map_ from '../../Map_/Map_' + +import LocalFilterer from '../../../Ancillary/LocalFilterer' +import ESFilterer from './ESFilterer' + +import Help from '../../../Ancillary/Help' +import Dropy from '../../../../external/Dropy/dropy' +import { circle } from '@turf/turf' + +import './Filtering.css' + +const helpKey = 'LayersTool-Filtering' + +const Filtering = { + filters: {}, + current: {}, + mapSpatialLayer: null, + make: async function (container, layerName) { + const layerObj = L_.layers.data[layerName] + + if (layerObj == null) return + + Filtering.filters[layerName] = Filtering.filters[layerName] || { + spatial: { + center: null, + radius: 0, + }, + values: [], + geojson: null, + } + Filtering.current = { + layerName: layerName, + layerObj: layerObj, + type: layerObj.type, + } + + if (Filtering.current.type === 'vector') { + try { + Filtering.filters[layerName].geojson = + Filtering.filters[layerName].geojson || + L_.layers.layer[layerName].toGeoJSON(L_.GEOJSON_PRECISION) + } catch (err) { + console.warn( + `Filtering - Cannot find GeoJSON to filter on for layer: ${layerName}` + ) + return + } + Filtering.filters[layerName].aggs = LocalFilterer.getAggregations( + Filtering.filters[layerName].geojson + ) + } else if (Filtering.current.type === 'query') { + Filtering.filters[layerName].aggs = + await ESFilterer.getAggregations( + layerName, + Filtering.getConfig() + ) + } + const spatialActive = + Filtering.filters[layerName].spatial?.center != null + + // prettier-ignore + const markup = [ + "
", + "
", + "
", + "
Filter
", + Help.getComponent(helpKey), + "
", + "
", + "
", + "
Add
", + "
", + "
", + "
", + "", + `", + "
", + `", + "
", + ].join('\n') + + container.append(markup) + + Filtering.filters[layerName].values.forEach((v) => { + if (v) Filtering.addValue(layerName, v) + }) + + Filtering.attachEvents(layerName) + + Filtering.drawSpatialLayer( + layerName, + Filtering.filters[layerName].spatial.center, + Filtering.filters[layerName].spatial.radius + ) + + // Start with one empty row added + if ( + $('#layerTool_filtering_filters_list .layersTool_filtering_value') + .length === 0 + ) + Filtering.addValue(layerName) + + Help.finalize(helpKey) + }, + destroy: function () { + // Clear Spatial Filter + Map_.rmNotNull(Filtering.mapSpatialLayer) + + $('#layersTool_filtering').remove() + }, + addValue: function (layerName, value) { + let id, key, op, val + if (value) { + id = value.id + key = value.key != null ? ` value='${value.key}'` : '' + op = value.op + val = value.value != null ? ` value='${value.value}'` : '' + } else id = Filtering.filters[layerName].values.length + + // prettier-ignore + const valueMarkup = [ + `
`, + "
", + ``, + "
", + "
", + `
`, + "
", + "
", + ``, + `
`, + ``, + ``, + `
`, + "
", + `
`, + "
", + ].join('\n') + + $('#layerTool_filtering_filters_list').append(valueMarkup) + + if (value == null) { + Filtering.filters[layerName].values.push({ + id: id, + type: null, + key: null, + op: '=', + value: null, + }) + } + + Filtering.attachValueEvents(id, layerName, { op: op }) + + // Show footer iff value rows exist + $('#layersTool_filtering_footer').css( + 'display', + Filtering.filters[layerName].values.length === 0 ? 'none' : 'flex' + ) + }, + drawSpatialLayer: function (layerName, center, radius) { + Map_.rmNotNull(Filtering.mapSpatialLayer) + + Filtering.setSubmitButtonState(true) + if (center == null) return + + const style = { + fillOpacity: 0.1, + fillColor: 'white', + color: 'lime', + weight: 2, + opacity: 1, + className: 'noPointerEventsImportant', + } + + if (radius > 0) { + // Buffered Circle + const geojson = F_.getBaseGeoJSON() + geojson.features.push( + circle( + [center.lng, center.lat], + radius * 0.001 * F_.getEarthToPlanetRatio() + ) + ) + + Filtering.mapSpatialLayer = L.geoJSON(geojson, { + style: style, + }).addTo(Map_.map) + Filtering.filters[layerName].spatial.feature = geojson.features[0] + } else { + // Circle marker + Filtering.mapSpatialLayer = new L.circleMarker( + [center.lat, center.lng], + style + ) + .setRadius(4) + .addTo(Map_.map) + + Filtering.filters[layerName].spatial.feature = { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [center.lng, center.lat], + }, + } + } + Filtering.mapSpatialLayer.bringToFront() + }, + // To highlight the submit button to indicate a change's been made in the form + setSubmitButtonState: function (active) { + if (active) { + $('#layersTool_filtering_submit_text').text('Submit') + $('#layersTool_filtering_submit').addClass('active') + } else if ($('#layersTool_filtering_submit').hasClass('active')) { + $('#layersTool_filtering_submit_text').text('Submitted') + $('#layersTool_filtering_submit').removeClass('active') + } + }, + attachEvents: function (layerName) { + // Add Value + $('#layersTool_filtering_add_value').on('click', function () { + Filtering.addValue(layerName) + }) + + // Draw + $('#layerTool_filtering_filters_spatial_draw').on('click', function () { + Map_.rmNotNull(Filtering.mapSpatialLayer) + $('#map').css('cursor', 'crosshair') + $('#layerTool_filtering_filters_spatial_draw > div').text( + 'Placing Point' + ) + $('#layerTool_filtering_filters_spatial').removeClass('drawn') + $('#layerTool_filtering_filters_spatial').addClass('drawing') + Map_.map.on('click', spatialOnClick) + }) + function spatialOnClick(e) { + Map_.map.off('click', spatialOnClick) + $('#map').css('cursor', 'grab') + $('#layerTool_filtering_filters_spatial_draw > div').text('Active') + $('#layerTool_filtering_filters_spatial').removeClass('drawing') + $('#layerTool_filtering_filters_spatial').addClass('drawn') + + Filtering.filters[layerName].spatial.center = { + lng: e.latlng.lng, + lat: e.latlng.lat, + } + Filtering.drawSpatialLayer( + layerName, + Filtering.filters[layerName].spatial.center, + Filtering.filters[layerName].spatial.radius + ) + } + // Draw - Radius + $('#layerTool_filtering_filters_spatial_radius').on( + 'input', + function (e) { + Filtering.filters[layerName].spatial.radius = parseFloat( + $(this).val() + ) + Filtering.drawSpatialLayer( + layerName, + Filtering.filters[layerName].spatial.center, + Filtering.filters[layerName].spatial.radius + ) + } + ) + // Draw - Clear + $('#layerTool_filtering_filters_spatial_clear').on( + 'click', + function () { + Filtering.filters[layerName].spatial.center = null + Map_.map.off('click', spatialOnClick) + $('#map').css('cursor', 'grab') + $('#layerTool_filtering_filters_spatial_draw > div').text( + 'Place Point' + ) + $('#layerTool_filtering_filters_spatial').removeClass('drawn') + $('#layerTool_filtering_filters_spatial').removeClass('drawing') + + Filtering.drawSpatialLayer( + layerName, + Filtering.filters[layerName].spatial.center, + Filtering.filters[layerName].spatial.radius + ) + } + ) + + // Submit + $(`#layersTool_filtering_submit`).on('click', async () => { + Filtering.setSubmitButtonState(true) + $(`#layersTool_filtering_submit_loading`).addClass('active') + if (Filtering.current.type === 'vector') { + LocalFilterer.filter(layerName, Filtering.filters[layerName]) + } else if (Filtering.current.type === 'query') { + await ESFilterer.filter( + layerName, + Filtering.filters[layerName], + Filtering.getConfig() + ) + } + + $(`#layersTool_filtering_submit_loading`).removeClass('active') + Filtering.setSubmitButtonState(false) + + if (Filtering.mapSpatialLayer) + Filtering.mapSpatialLayer.bringToFront() + }) + + // Clear + $(`#layersTool_filtering_clear`).on('click', async () => { + // Clear Spatial Filter + $('#layerTool_filtering_filters_spatial_clear').click() + $(`#layersTool_filtering_submit_loading`).addClass('active') + + // Clear value filter elements + Filtering.filters[layerName].values = Filtering.filters[ + layerName + ].values.filter((v) => { + if (v) + $( + `#layersTool_filtering_value_${F_.getSafeName( + layerName + )}_${v.id}` + ).remove() + return false + }) + + // Refilter to show all + if (Filtering.current.type === 'vector') { + LocalFilterer.filter(layerName, Filtering.filters[layerName]) + } else if (Filtering.current.type === 'query') { + await ESFilterer.filter( + layerName, + Filtering.filters[layerName], + Filtering.getConfig() + ) + } + + // Reset count + $('#layersTool_filtering_count').text('') + + Filtering.setSubmitButtonState(false) + + $(`#layersTool_filtering_submit_loading`).removeClass('active') + + if (Filtering.mapSpatialLayer) + Filtering.mapSpatialLayer.bringToFront() + }) + }, + attachValueEvents: function (id, layerName, options) { + options = options || {} + + let elmId + + // Expand input boxes on focus + // Contract input boxes on blur + elmId = `#layersTool_filtering_value_key_input_${F_.getSafeName( + layerName + )}_${id}` + $(elmId).on('focus', function () { + $(this).parent().css('flex', '4 1') + }) + $(elmId).on('blur', function () { + $(this).parent().css('flex', '1 1') + }) + elmId = `#layersTool_filtering_value_value_input_${F_.getSafeName( + layerName + )}_${id}` + $(elmId).on('focus', function () { + $(this).parent().css('flex', '4 1') + }) + $(elmId).on('blur', function () { + $(this).parent().css('flex', '1 1') + }) + // Clear + elmId = `#layersTool_filtering_value_clear_${F_.getSafeName( + layerName + )}_${id}` + + $(elmId).on('click', () => { + // Clear value filter element + for ( + let i = 0; + i < Filtering.filters[layerName].values.length; + i++ + ) { + const vId = Filtering.filters[layerName].values[i]?.id + if (vId != null && vId === id) { + $( + `#layersTool_filtering_value_${F_.getSafeName( + layerName + )}_${vId}` + ).remove() + Filtering.filters[layerName].values[i] = null + } + } + Filtering.setSubmitButtonState(true) + }) + + // Property Autocomplete + elmId = `#layersTool_filtering_value_key_input_${F_.getSafeName( + layerName + )}_${id}` + + let arrayToSearch = Object.keys(Filtering.filters[layerName].aggs) + arrayToSearch = arrayToSearch.sort((a, b) => b.localeCompare(a)) + + $(elmId).autocomplete({ + lookup: arrayToSearch, + lookupLimit: 100, + minChars: 0, + transformResult: function (response, originalQuery) { + let resultSuggestions = [] + $.map(response, function (jsonItem) { + if (typeof jsonItem != 'string') { + $.map(jsonItem, function (suggestionItem) { + resultSuggestions.push(suggestionItem) + }) + } + }) + resultSuggestions.sort(function (a, b) { + const aStart = String(a.value).match( + new RegExp(originalQuery, 'i') + ) || { index: -1 }, + bStart = String(b.value).match( + new RegExp(originalQuery, 'i') + ) || { index: -1 } + if (aStart.index != bStart.index) + return aStart.index - bStart.index + else return a > b ? 1 : -1 + }) + response.suggestions = resultSuggestions + return response + }, + onSelect: function (event) { + const property = Filtering.filters[layerName].aggs[event.value] + Filtering.filters[layerName].values[id].type = property.type + Filtering.filters[layerName].values[id].key = event.value + Filtering.updateValuesAutoComplete(id, layerName) + Filtering.setSubmitButtonState(true) + $(this).css('border', 'none') + $(this).css( + 'border-left', + `6px solid ${F_.stringToColor(event.value)}` + ) + }, + }) + + $(elmId).on('blur', function (event) { + const property = + Filtering.filters[layerName].aggs[event.value || $(this).val()] + if (property) { + if ( + Filtering.filters[layerName].values[id] && + Filtering.filters[layerName].values[id].key !== event.value + ) { + Filtering.filters[layerName].values[id].key = event.value + Filtering.filters[layerName].values[id].type = property.type + Filtering.updateValuesAutoComplete(id, layerName) + Filtering.setSubmitButtonState(true) + } + $(this).css('border', 'none') + $(this).css( + 'border-left', + `6px solid ${F_.stringToColor($(this).val())}` + ) + } else $(this).css('border', '1px solid red') + }) + + // Operator Dropdown + elmId = `#layersTool_filtering_value_operator_${F_.getSafeName( + layerName + )}_${id}` + + const ops = ['=', ',', '<', '>'] + const opId = Math.max(ops.indexOf(options.op), 0) + $(elmId).html( + Dropy.construct( + [ + ``, + `
in
`, + ``, + ``, + ], + 'op', + opId, + { openUp: true, hideChevron: true } + ) + ) + Dropy.init($(elmId), function (idx) { + Filtering.filters[layerName].values[id].op = ops[idx] + Filtering.setSubmitButtonState(true) + }) + + // Value AutoComplete + Filtering.updateValuesAutoComplete(id, layerName) + }, + updateValuesAutoComplete: function (id, layerName) { + let elmId = `#layersTool_filtering_value_value_input_${F_.getSafeName( + layerName + )}_${id}` + let arrayToSearch = [] + if ( + Filtering.filters[layerName].values[id].key && + Filtering.filters[layerName].aggs[ + Filtering.filters[layerName].values[id].key + ] + ) + arrayToSearch = Object.keys( + Filtering.filters[layerName].aggs[ + Filtering.filters[layerName].values[id].key + ].aggs || {} + ) + $(elmId).autocomplete({ + lookup: arrayToSearch, + lookupLimit: 150, + minChars: 0, + transformResult: function (response, originalQuery) { + let resultSuggestions = [] + $.map(response, function (jsonItem) { + if (typeof jsonItem != 'string') { + $.map(jsonItem, function (suggestionItem) { + resultSuggestions.push(suggestionItem) + }) + } + }) + resultSuggestions.sort(function (a, b) { + const aStart = String(a.value).match( + new RegExp(originalQuery, 'i') + ) || { index: -1 }, + bStart = String(b.value).match( + new RegExp(originalQuery, 'i') + ) || { index: -1 } + if (aStart.index != bStart.index) + return aStart.index - bStart.index + else return a > b ? 1 : -1 + }) + response.suggestions = resultSuggestions + return response + }, + onSelect: function (event) { + Filtering.filters[layerName].values[id].value = event.value + Filtering.setSubmitButtonState(true) + }, + }) + $(elmId).on('keyup', function (e) { + Filtering.filters[layerName].values[id].value = $(this).val() + Filtering.setSubmitButtonState(true) + }) + + $('.autocomplete-suggestions').css({ + 'max-height': '300px', + 'border-top': 'none', + }) + + // Change type indicator icons too + const numberElmId = `#layersTool_filtering_value_value_type_number_${F_.getSafeName( + layerName + )}_${id}` + const stringElmId = `#layersTool_filtering_value_value_type_string_${F_.getSafeName( + layerName + )}_${id}` + switch (Filtering.filters[layerName].values[id].type) { + case 'number': + $(numberElmId).css('display', 'inherit') + $(stringElmId).css('display', 'none') + break + case 'string': + $(stringElmId).css('display', 'inherit') + $(numberElmId).css('display', 'none') + break + default: + $(numberElmId).css('display', 'none') + $(stringElmId).css('display', 'none') + break + } + }, + getConfig: function () { + if ( + Filtering.current.layerObj.type === 'query' && + Filtering.current.layerObj.query + ) { + return { + endpoint: Filtering.current.layerObj.query.endpoint, + type: Filtering.current.layerObj.query.type || 'elasticsearch', + ...(Filtering.current.layerObj.variables + ? Filtering.current.layerObj.variables.query || {} + : {}), + } + } + return {} + }, + // Let other places of the code trigger filters as needed + triggerFilter: function (layerName) { + if (Filtering.filters[layerName]) { + if (L_.layers.data[layerName].type === 'vector') + if (Filtering.filters[layerName]?.values?.[0]?.type != null) { + LocalFilterer.filter( + layerName, + Filtering.filters[layerName] + ) + } + } + }, + // Useful for dynamicExtent vector layers so that the geojson and aggs match the visible features + updateGeoJSON: async function (layerName) { + if (Filtering.filters[layerName]) { + if (L_.layers.data[layerName].type === 'vector') { + try { + Filtering.filters[layerName].geojson = L_.layers.layer[ + layerName + ].toGeoJSON(L_.GEOJSON_PRECISION) + } catch (err) { + console.warn( + `Filtering - Cannot find GeoJSON to filter on for layer: ${layerName}` + ) + return + } + Filtering.filters[layerName].aggs = + LocalFilterer.getAggregations( + Filtering.filters[layerName].geojson + ) + } else if (L_.layers.data[layerName].type === 'query') + Filtering.filters[layerName].aggs = + await ESFilterer.getAggregations( + layerName, + Filtering.getConfig() + ) + + if (Filtering.filters[layerName]?.values) { + Filtering.filters[layerName]?.values.forEach((v, idx) => { + // Value AutoComplete + Filtering.updateValuesAutoComplete(idx, layerName) + }) + } + } + }, +} + +export default Filtering diff --git a/src/essence/Basics/Layers_/Layers_.js b/src/essence/Basics/Layers_/Layers_.js index c6c9b88c..6f6d6029 100644 --- a/src/essence/Basics/Layers_/Layers_.js +++ b/src/essence/Basics/Layers_/Layers_.js @@ -914,7 +914,7 @@ const L_ = { L_._refreshAnnotationEvents() }, - addGeoJSONData: function (layer, geojson, keepLastN) { + addGeoJSONData: function (layer, geojson, keepLastN, stopLoops) { if (layer._sourceGeoJSON) { if (layer._sourceGeoJSON.features) if (geojson.features) @@ -953,7 +953,10 @@ const L_ = { L_.Map_.makeLayer( L_.layers.data[layer._layerName], true, - layer._sourceGeoJSON + layer._sourceGeoJSON, + null, + null, + stopLoops ) if (initialOn) { @@ -2695,7 +2698,7 @@ const L_ = { } return true }, - updateVectorLayer: function (layerName, inputData, keepLastN) { + updateVectorLayer: function (layerName, inputData, keepLastN, stopLoops) { layerName = L_.asLayerUUID(layerName) if (layerName in L_.layers.layer) { @@ -2710,7 +2713,7 @@ const L_ = { const updateLayer = L_.layers.layer[layerName] try { - L_.addGeoJSONData(updateLayer, inputData, keepLastN) + L_.addGeoJSONData(updateLayer, inputData, keepLastN, stopLoops) } catch (e) { console.log(e) console.warn( diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 0478d261..808c1e22 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -7,6 +7,7 @@ import { constructVectorLayer, constructSublayers, } from '../Layers_/LayerConstructors' +import Filtering from '../Layers_/Filtering/Filtering' import Viewer_ from '../Viewer_/Viewer_' import Globe_ from '../Globe_/Globe_' import ToolController_ from '../ToolController_/ToolController_' @@ -530,7 +531,14 @@ function makeLayers(layersObj) { } } //Takes the layer object and makes it a map layer -async function makeLayer(layerObj, evenIfOff, forceGeoJSON, id, forceMake) { +async function makeLayer( + layerObj, + evenIfOff, + forceGeoJSON, + id, + forceMake, + stopLoops +) { return new Promise(async (resolve, reject) => { const layerName = L_.asLayerUUID(layerObj.name) if (forceMake !== true && L_._layersBeingMade[layerName] === true) { @@ -579,6 +587,11 @@ async function makeLayer(layerObj, evenIfOff, forceGeoJSON, id, forceMake) { // release hold on layer L_._layersBeingMade[layerName] = false + if (stopLoops !== true && layerObj.type === 'vector') { + Filtering.updateGeoJSON(layerObj.name) + Filtering.triggerFilter(layerObj.name) + } + resolve(true) }) } @@ -871,6 +884,7 @@ async function makeVectorLayer( data.features ) L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() resolve() } diff --git a/src/essence/Tools/Layers/LayersTool.js b/src/essence/Tools/Layers/LayersTool.js index 1bbd8ccd..5fe08e41 100644 --- a/src/essence/Tools/Layers/LayersTool.js +++ b/src/essence/Tools/Layers/LayersTool.js @@ -7,7 +7,7 @@ import Map_ from '../../Basics/Map_/Map_' import DataShaders from '../../Ancillary/DataShaders' import LayerInfoModal from './LayerInfoModal/LayerInfoModal' -import Filtering from './Filtering/Filtering' +import Filtering from '../../Basics/Layers_/Filtering/Filtering' import Help from '../../Ancillary/Help' import CursorInfo from '../../Ancillary/CursorInfo'