diff --git a/src/essence/Tools/Layers/Filtering/LocalFilterer.js b/src/essence/Ancillary/LocalFilterer.js similarity index 93% rename from src/essence/Tools/Layers/Filtering/LocalFilterer.js rename to src/essence/Ancillary/LocalFilterer.js index f6692065..eabb90ee 100644 --- a/src/essence/Tools/Layers/Filtering/LocalFilterer.js +++ b/src/essence/Ancillary/LocalFilterer.js @@ -1,214 +1,220 @@ -// Part of the LayersTool that deals with filtering - -import $ from 'jquery' -import F_ from '../../../Basics/Formulae_/Formulae_' -import L_ from '../../../Basics/Layers_/Layers_' - -import flat from 'flat' -import { booleanIntersects, booleanContains } from '@turf/turf' - -const LocalFilterer = { - make: function (container, layerName) { - const layerObj = L_.layers.data[layerName] - - if (layerObj == null) return - - const type = layerObj.type - - if (type === 'vector') { - } else if (type === 'query') { - } - }, - destroy: function (layerName) {}, - getAggregations: function (geojson) { - const aggs = { - 'geometry.type': { type: 'string', aggs: {} }, - } - - geojson.features.forEach((feature) => { - const flatProps = flat.flatten(feature.properties) - for (let p in flatProps) { - let value = flatProps[p] - let type - - if (!isNaN(value) && !isNaN(parseFloat(value))) type = 'number' - else if (typeof value === 'string') type = 'string' - else if (typeof value === 'number') type = 'number' - - if (type != null) { - // First type will be from index 0 - aggs[p] = aggs[p] || { type: type, aggs: {} } - // Because of that, strings can usurp numbers (ex. ["1", "2", "Melon", "Pastry"]) - if (aggs[p].type === 'number' && type === 'string') - aggs[p].type = type - aggs[p].aggs[flatProps[p]] = aggs[p].aggs[flatProps[p]] || 0 - aggs[p].aggs[flatProps[p]]++ - } - } - // feature.geometry.type - aggs['geometry.type'].aggs[feature.geometry.type] = - aggs['geometry.type'].aggs[feature.geometry.type] || 0 - aggs['geometry.type'].aggs[feature.geometry.type]++ - }) - return aggs - }, - filter: function (layerName, filter) { - const geojson = filter.geojson - const filteredGeoJSON = JSON.parse(JSON.stringify(geojson)) - filteredGeoJSON.features = [] - - // Filter - const halfFilteredGeoJSONFeatures = [] - geojson.features.forEach((f) => { - if (LocalFilterer.match(f, filter)) - halfFilteredGeoJSONFeatures.push(f) - }) - - // Spatial Filter (after filter to make it quicker) - halfFilteredGeoJSONFeatures.forEach((f) => { - if (filter.spatial.center == null) { - filteredGeoJSON.features.push(f) - } else { - if (filter.spatial.radius > 0) { - // Circle Intersects - if ( - booleanIntersects( - filter.spatial.feature.geometry, - f.geometry - ) - ) - filteredGeoJSON.features.push(f) - } else { - // Point Contained - if ( - booleanContains( - f.geometry, - filter.spatial.feature.geometry - ) - ) - filteredGeoJSON.features.push(f) - } - } - }) - - // Set count - $('#layersTool_filtering_count').text( - `(${filteredGeoJSON.features.length}/${geojson.features.length})` - ) - - // Update layer - L_.clearVectorLayer(layerName) - L_.updateVectorLayer(layerName, filteredGeoJSON) - }, - match: function (feature, filter) { - if (filter.values.length === 0) return true - - // Perform the per row match - for (let i = 0; i < filter.values.length; i++) { - const v = filter.values[i] - if (v && v.key != null) { - const featureValue = - v.key === 'geometry.type' - ? feature.geometry.type - : F_.getIn(feature.properties, v.key) - let filterValue = v.value - if (v.type === 'number' && v.op != ',') - filterValue = parseFloat(filterValue) - if (featureValue != null) { - switch (v.op) { - case '=': - if (featureValue == filterValue) v.matches = true - else v.matches = false - break - case ',': - if (filterValue != null) { - const stringFilterValue = filterValue + '' - const stringFeatureValue = featureValue + '' - if ( - stringFilterValue - .split(',') - .includes(stringFeatureValue) - ) - v.matches = true - else v.matches = false - } else v.matches = false - break - case '<': - if ( - v.type === 'string' - ? featureValue.localeCompare(filterValue) > - 0 - : featureValue < filterValue - ) - v.matches = true - else v.matches = false - break - case '>': - if ( - v.type === 'string' - ? featureValue.localeCompare(filterValue) < - 0 - : featureValue > filterValue - ) - v.matches = true - else v.matches = false - break - default: - break - } - //if (!matches) return false - } - } - } - - // Now group together all matching keys and process - // Filter values with the same key are ORed together if = and ANDed if not - // i.e. sol = 50, sol = 51 becomes sol == 50 OR sol == 51 - // sol > 50, sol < 100 becomes sol > 50 AND sol < 100 - // sol > 50, sol < 100, sol = 200 becomes (sol > 50 AND sol < 100) OR sol == 101 - const groupedValuesByKey = {} - filter.values.forEach((v) => { - if (v && v.key != null) { - groupedValuesByKey[v.key] = groupedValuesByKey[v.key] || [] - groupedValuesByKey[v.key].push(v) - } - }) - - const matches = [] - Object.keys(groupedValuesByKey).forEach((key) => { - // For grouped values to pass, (all the >,< ANDed must be true) OR (all the rest ORed must be true) - - // To facilitate that, first group by operator - const groupedValuesByOp = {} - groupedValuesByKey[key].forEach((v) => { - let op = v.op - if (op === '<' || op === '>') op = '<>' - - groupedValuesByOp[op] = groupedValuesByOp[op] || [] - groupedValuesByOp[op].push(v) - }) - - const opMatches = [] - Object.keys(groupedValuesByOp).forEach((op) => { - let match = null - groupedValuesByOp[op].forEach((v) => { - if (op === '<>') { - if (match === null) match = true - match = match && v.matches - } else { - if (match === null) match = false - match = match || v.matches - } - }) - opMatches.push(match) - }) - // If at least one true, the op match passes - matches.push(opMatches.includes(true)) - }) - - // If all are true - return matches.filter(Boolean).length === matches.length - }, -} - -export default LocalFilterer +// Part of the LayersTool that deals with filtering + +import $ from 'jquery' +import F_ from '../Basics/Formulae_/Formulae_' +import L_ from '../Basics/Layers_/Layers_' + +import flat from 'flat' +import { booleanIntersects, booleanContains } from '@turf/turf' + +const LocalFilterer = { + make: function (container, layerName) { + const layerObj = L_.layers.data[layerName] + + if (layerObj == null) return + + const type = layerObj.type + + if (type === 'vector') { + } else if (type === 'query') { + } + }, + destroy: function (layerName) {}, + getAggregations: function (geojson) { + const aggs = { + 'geometry.type': { type: 'string', aggs: {} }, + } + + geojson.features.forEach((feature) => { + const flatProps = flat.flatten(feature.properties) + for (let p in flatProps) { + let value = flatProps[p] + let type + + if (!isNaN(value) && !isNaN(parseFloat(value))) type = 'number' + else if (typeof value === 'string') type = 'string' + else if (typeof value === 'number') type = 'number' + + if (type != null) { + // First type will be from index 0 + aggs[p] = aggs[p] || { type: type, aggs: {} } + // Because of that, strings can usurp numbers (ex. ["1", "2", "Melon", "Pastry"]) + if (aggs[p].type === 'number' && type === 'string') + aggs[p].type = type + aggs[p].aggs[flatProps[p]] = aggs[p].aggs[flatProps[p]] || 0 + aggs[p].aggs[flatProps[p]]++ + } + } + // feature.geometry.type + aggs['geometry.type'].aggs[feature.geometry.type] = + aggs['geometry.type'].aggs[feature.geometry.type] || 0 + aggs['geometry.type'].aggs[feature.geometry.type]++ + }) + return aggs + }, + filter: function (layerName, filter, refreshFunction) { + const geojson = filter.geojson + const filteredGeoJSON = JSON.parse(JSON.stringify(geojson)) + filteredGeoJSON.features = [] + + // Filter + const halfFilteredGeoJSONFeatures = [] + geojson.features.forEach((f) => { + if (LocalFilterer.match(f, filter)) + halfFilteredGeoJSONFeatures.push(f) + }) + + // Spatial Filter (after filter to make it quicker) + halfFilteredGeoJSONFeatures.forEach((f) => { + if (filter.spatial == null || filter.spatial.center == null) { + filteredGeoJSON.features.push(f) + } else { + if (filter.spatial.radius > 0) { + // Circle Intersects + if ( + booleanIntersects( + filter.spatial.feature.geometry, + f.geometry + ) + ) + filteredGeoJSON.features.push(f) + } else { + // Point Contained + if ( + booleanContains( + f.geometry, + filter.spatial.feature.geometry + ) + ) + filteredGeoJSON.features.push(f) + } + } + }) + + // Set count + $('#layersTool_filtering_count').text( + `(${filteredGeoJSON.features.length}/${geojson.features.length})` + ) + + // Update layer + if (typeof refreshFunction === 'function') { + refreshFunction(filteredGeoJSON) + } else { + L_.clearVectorLayer(layerName) + L_.updateVectorLayer(layerName, filteredGeoJSON) + } + }, + match: function (feature, filter) { + if (filter.values.length === 0) return true + + // Perform the per row match + for (let i = 0; i < filter.values.length; i++) { + const v = filter.values[i] + if (v && v.key != null) { + const featureValue = + v.key === 'geometry.type' + ? feature.geometry.type + : F_.getIn(feature.properties, v.key) + let filterValue = v.value + if (v.type === 'number' && v.op != ',') + filterValue = parseFloat(filterValue) + if (featureValue != null) { + switch (v.op) { + case '=': + if (featureValue == filterValue) v.matches = true + else v.matches = false + break + case ',': + if (filterValue != null) { + const stringFilterValue = filterValue + '' + const stringFeatureValue = featureValue + '' + if ( + stringFilterValue + .split(',') + .includes(stringFeatureValue) + ) + v.matches = true + else v.matches = false + } else v.matches = false + break + case '<': + if ( + v.type === 'string' + ? featureValue.localeCompare(filterValue) > + 0 + : featureValue < filterValue + ) + v.matches = true + else v.matches = false + break + case '>': + if ( + v.type === 'string' + ? featureValue.localeCompare(filterValue) < + 0 + : featureValue > filterValue + ) + v.matches = true + else v.matches = false + break + default: + break + } + //if (!matches) return false + } else { + v.matches = false + } + } + } + + // Now group together all matching keys and process + // Filter values with the same key are ORed together if = and ANDed if not + // i.e. sol = 50, sol = 51 becomes sol == 50 OR sol == 51 + // sol > 50, sol < 100 becomes sol > 50 AND sol < 100 + // sol > 50, sol < 100, sol = 200 becomes (sol > 50 AND sol < 100) OR sol == 101 + const groupedValuesByKey = {} + filter.values.forEach((v) => { + if (v && v.key != null) { + groupedValuesByKey[v.key] = groupedValuesByKey[v.key] || [] + groupedValuesByKey[v.key].push(v) + } + }) + + const matches = [] + Object.keys(groupedValuesByKey).forEach((key) => { + // For grouped values to pass, (all the >,< ANDed must be true) OR (all the rest ORed must be true) + + // To facilitate that, first group by operator + const groupedValuesByOp = {} + groupedValuesByKey[key].forEach((v) => { + let op = v.op + if (op === '<' || op === '>') op = '<>' + + groupedValuesByOp[op] = groupedValuesByOp[op] || [] + groupedValuesByOp[op].push(v) + }) + + const opMatches = [] + Object.keys(groupedValuesByOp).forEach((op) => { + let match = null + groupedValuesByOp[op].forEach((v) => { + if (op === '<>') { + if (match === null) match = true + match = match && v.matches + } else { + if (match === null) match = false + match = match || v.matches + } + }) + opMatches.push(match) + }) + // If at least one true, the op match passes + matches.push(opMatches.includes(true)) + }) + + // If all are true + return matches.filter(Boolean).length === matches.length + }, +} + +export default LocalFilterer diff --git a/src/essence/Tools/Layers/Filtering/Filtering.js b/src/essence/Tools/Layers/Filtering/Filtering.js index d9e2b705..2d0867cb 100644 --- a/src/essence/Tools/Layers/Filtering/Filtering.js +++ b/src/essence/Tools/Layers/Filtering/Filtering.js @@ -5,7 +5,7 @@ import F_ from '../../../Basics/Formulae_/Formulae_' import L_ from '../../../Basics/Layers_/Layers_' import Map_ from '../../../Basics/Map_/Map_' -import LocalFilterer from './LocalFilterer' +import LocalFilterer from '../../../Ancillary/LocalFilterer' import ESFilterer from './ESFilterer' import Help from '../../../Ancillary/Help' diff --git a/src/external/Dropy/dropy.css b/src/external/Dropy/dropy.css index a7fb2435..1757ca93 100644 --- a/src/external/Dropy/dropy.css +++ b/src/external/Dropy/dropy.css @@ -171,3 +171,6 @@ dd { .dropy.openUp .dropy__content > ul { transform: translateY(-100%); } +.dropy.openHorizontal .dropy__content > ul { + display: flex; +} \ No newline at end of file diff --git a/src/external/Dropy/dropy.js b/src/external/Dropy/dropy.js index d6cc3b0c..36e9e1b7 100644 --- a/src/external/Dropy/dropy.js +++ b/src/external/Dropy/dropy.js @@ -16,15 +16,15 @@ export default { options = options || {} // prettier-ignore return [ - `
`, + `
`, `
`, `${options.forcePlaceholder ? placeholder : items[selectedIndex] != null ? items[selectedIndex] : placeholder}`, `${options.hideChevron === true ? '' : ``}`, `
`, '
', - '
    ', + ``, placeholder ? `
  • ${placeholder}
  • ` : '', - items.map((item, i) => `
  • ${item}
  • `).join('\n'), + items.map((item, i) => `${item}`).join('\n'), '
', '
', '',