import custom_utils from './utils' import MappingConfigType from "./mapping-config.type"; import Formatters from "./formatters"; const {debugLog} = custom_utils let listOfBooleanKeys = [ "prise_type_ef", "prise_type_2", "prise_type_combo_ccs", "prise_type_chademo", "gratuit", "paiement_acte", "paiement_cb", "cable_t2_attache" ] function boolToAddable(someBooleanValue: boolean) { return someBooleanValue ? 1 : 0 } export default class { mapping_config: any = {} private jardinage = false; public stats: any; private current_converted_geojson_point: any; private current_geojson_point: any; // currently converting point private list_of_points: any; // list of geojson points constructor(mappingConfig: MappingConfigType) { this.setConfig(mappingConfig) this.stats = { filtered_by_excluded_tags: 0, phones_updated: 0, power_output: 0, phones_updated_list: [], phones_not_updated: 0 } } setConfig(mappingConfig: MappingConfigType) { console.log('load config', mappingConfig.config_name) debugLog('load config', mappingConfig.config_name) this.mapping_config = mappingConfig } getConfig() { return this.mapping_config } mapFeaturePoint(featurePointGeoJson: any) { let geoJSONConvertedPoint: any = {} geoJSONConvertedPoint.properties = {...this.mapping_config.default_properties_of_point} geoJSONConvertedPoint.type = featurePointGeoJson.type geoJSONConvertedPoint.geometry = featurePointGeoJson.geometry this.current_converted_geojson_point = geoJSONConvertedPoint return geoJSONConvertedPoint } /** * TODO convert to mapping config property to transform_truthy * @param pointKeyName * @returns {boolean} */ isBooleanKey(pointKeyName: string): boolean { return listOfBooleanKeys.indexOf(pointKeyName) !== -1 } truthyValues = [true, 'true', 'True', 'TRUE', '1', 'yes', 1] falsyValues = [false, 'false', 'False', 'FALSE', '0', 'no', 0] /** * filter: reduce number of features * @param offsetCount * @param listOfFeatures */ filterFeaturesByOffset(offsetCount: number, listOfFeatures: any): Array { let filteredList = listOfFeatures // TODO return filteredList } /** * filterFeaturesByPropertyRegex * TODO * @param propertyName * @param criteriaRegex * @param listOfFeatures */ filterFeaturesByPropertyRegex(propertyName: string, criteriaRegex: any, listOfFeatures: any) { let filteredList = listOfFeatures.filter((feature: any) => { return criteriaRegex.test(feature?.properties[propertyName]) }) return filteredList } /** * filter a list of geojson points if one of the given exludedKeys is present in their properties. * Example, we do not want to convert already present OSM point which have an osm_id value in their properties. * @param list * @param excludedKeys */ filterListOfPointsByExcludingIfKeyFilled(list: any, excludedKeys: Array):any[] { let newList: Array = [] list.forEach((geojsonPoint: any) => { let pointProperties = Object.keys(geojsonPoint.properties) let addPoint = true; excludedKeys.forEach((key: any) => { debugLog(key, 'pointProperties[key]', pointProperties[key]) let foundProperty:string = pointProperties[key] if ( foundProperty && foundProperty !== 'null') { addPoint = false } }) if (addPoint) { // only add points that pass the not null filter newList.push(geojsonPoint) } else { this.stats.filtered_by_excluded_tags++ } }) return newList; } /** * retuns the converted element from mapping config if present, null otherwise */ mapElementFromConf(featurePoint: any): any { debugLog('mapElementFromConf: mapElementFromConf', featurePoint) if (!this.mapping_config) { throw new Error('no config was loaded in the mapping engine. use setConfig(my_mapping_config) on this instance of mapping engine before using this. Your config should be typed to MappingConfigType Type.') } debugLog('mapElementFromConf: config_name', this.mapping_config.config_name) let mappingKeys = Object.keys(this.mapping_config.tags) let featurePointPropertiesKeys = [] if (this.mapping_config.osmose) { // only creation of new points are handled by now [2023-10-07] featurePointPropertiesKeys = Object.keys(featurePoint.properties.fixes[0][0].create) // debugLog('featurePointPropertiesKeys', featurePointPropertiesKeys) } else { featurePointPropertiesKeys = Object.keys(featurePoint.properties) } debugLog('mapElementFromConf: ============= keys mappingKeys:', this.mapping_config.tags.length, mappingKeys.length) debugLog('mapElementFromConf: ============= keys featurePointPropertiesKeys :', featurePoint.properties.length, featurePointPropertiesKeys.length) let newProperties = {...this.mapping_config.default_properties_of_point} // reinit properties of current point let basePoint = Object.create(featurePoint) basePoint.type = featurePoint.type basePoint.geometry = featurePoint.geometry basePoint.properties = {...this.mapping_config.default_properties_of_point} // apply new properties if found in mapping config featurePointPropertiesKeys.forEach(pointKeyName => { debugLog('mapElementFromConf: convert', pointKeyName) debugLog('mapElementFromConf: mapping keys:', mappingKeys) this.convertProperty(pointKeyName, mappingKeys, featurePoint, newProperties) }) basePoint.properties = newProperties // debugLog('mapElementFromConf: basePoint', basePoint) return basePoint } /** * convertit une propriété en une autre selon la config de mapping * @param pointKeyName * @param mappingKeys * @param featurePoint * @param newProperties */ convertProperty(pointKeyName: string, mappingKeys: any, featurePoint: any, newProperties: any) { this.current_geojson_point = featurePoint let originalValue = '' if (this.mapping_config.osmose) { originalValue = featurePoint.properties.fixes[0][0].create[pointKeyName] } else { originalValue = featurePoint.properties[pointKeyName] } let intOriginalValue = parseInt(originalValue) let mappingValueObject: any = ''; if (mappingKeys.indexOf(pointKeyName) !== -1) { mappingValueObject = this.mapping_config.tags[pointKeyName] debugLog('convertProperty: mappingValueObject ', mappingValueObject) } debugLog(' ------ convertProperty: pointKeyName', pointKeyName) // debugLog('convertProperty: mappingKeys', mappingKeys) let remove_original_key = false; if (this.mapping_config.tags_to_ignore_if_value_is.indexOf(originalValue) !== -1) { debugLog('(x) => ignore', originalValue, ' in ', pointKeyName) remove_original_key = true; } if (this.jardinage) { debugLog(' ------ on fait du jardinage') debugLog(' ------ mode mise en qualité activé') debugLog(' ------ les données en entrée sont des infos geojson extraites depuis overpass turbo.') debugLog(' ------ les clés des objets sont donc déjà dans le format de tag OSM,' + 'ne pas les convertir pour les mettre en qualité selon le modèle de mapping.') } if (this.mapping_config.add_not_mapped_tags_too && (mappingKeys.indexOf(pointKeyName) === -1)) { /** * add all unmapped tags is enabled */ debugLog(' ------ add all unmapped tags is enabled') newProperties[pointKeyName] = originalValue; } else { /** * only use existing keys */ debugLog("only use existing keys,", pointKeyName) if (mappingKeys.indexOf(pointKeyName) !== -1) { let valueConvertedFromMapping = featurePoint.properties[pointKeyName] let keyConvertedFromMapping = mappingKeys[mappingKeys.indexOf(pointKeyName)] let mappingConfigOfTag = this.mapping_config.tags[pointKeyName] debugLog('========== mappingConfigOfTag', mappingConfigOfTag) debugLog('convertProperty: found element', pointKeyName, '=>', keyConvertedFromMapping, 'value : ', valueConvertedFromMapping) let convertedValue = originalValue let typeOfConfigForKey = typeof mappingConfigOfTag let isStringValue = typeOfConfigForKey === 'string' let isConfigMappingObject = typeOfConfigForKey === 'object' debugLog('convertProperty: - typeofValue', typeOfConfigForKey) debugLog('convertProperty: - pointKeyName', pointKeyName) debugLog('convertProperty: - valueConvertedFromMapping', valueConvertedFromMapping) debugLog('typeof valueConvertedFromMapping === \'string\'', typeOfConfigForKey) debugLog('convertProperty: isStringValue?', valueConvertedFromMapping, isStringValue) debugLog('convertProperty: isStringValue?', valueConvertedFromMapping, isStringValue) debugLog('mappingConfigOfTag', mappingConfigOfTag) debugLog('typeOfConfigForKey', typeOfConfigForKey) /** * conversion si la clé à une config d'une string, on ne change que la clé, pas la valeur */ if (isStringValue) { debugLog('convertProperty: -- string value') debugLog('convertProperty: -- string value') debugLog('convertProperty: -- simple conversion : ', pointKeyName, '=> ', mappingConfigOfTag, '_', originalValue, '=>', valueConvertedFromMapping) debugLog('convertProperty: -- convertedValue', convertedValue) convertedValue = valueConvertedFromMapping if (convertedValue) { newProperties[mappingConfigOfTag] = convertedValue } } else { debugLog('convertProperty: no string value') } let configObject = mappingConfigOfTag if (isConfigMappingObject) { debugLog('convertProperty: is config object', configObject) let newKey: any = '' + pointKeyName if (configObject.key_converted) { newKey = configObject.key_converted } if (configObject.transform_function) { convertedValue = configObject.transform_function(originalValue) } if (configObject.truthy_value) { // convertir la valeur, si elle est truthy, la transformer en ce que donne la propriété truthy_value // exemple: le jeu de données dit que la colonne cable_t2_attache vaut "True", mais on veut le convertir en "1". // on met donc truthy_value: '1' debugLog('truthy_value', originalValue) if (this.truthyValues.indexOf(originalValue) !== -1) { convertedValue = configObject.truthy_value } } if (configObject.falsy_value) { if (this.falsyValues.indexOf(originalValue) !== -1) { convertedValue = configObject.falsy_value } } /** * conversion booléenne */ if (mappingValueObject.convert_to_boolean_value) { debugLog('convertProperty: is boolean_value_conversion') convertedValue = this.convertToYesOrNo(originalValue) } else { debugLog('convertProperty: is NOT having boolean_value_conversion', mappingValueObject) } // gestion des puissances de bornes // avec une fonction de transformation des valeurs // parmi le domaine du jeu de données // nécessite une clé conditionnelle à la valeur true d'autres clés converties. if (configObject.socket_output_find_correspondances) { // trouver à quel socket ça correspond // si y'a plusieurs sockets, utiliser socket:max:output let we_use_max_output = false; let has_prise_type_2: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_2) || false let has_prise_type_combo_ccs: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_combo_ccs) || false let prise_type_chademo: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_chademo) || false let prise_type_ef: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_ef) || false let prise_type_e: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_e) || false let prise_type_autre: boolean = this.isTruthyValue(this.current_geojson_point.properties.prise_type_autre) || false let countOfSockets = (boolToAddable(has_prise_type_2) + boolToAddable(has_prise_type_combo_ccs) + boolToAddable(prise_type_chademo) + boolToAddable(prise_type_ef) + boolToAddable(prise_type_autre) + boolToAddable(prise_type_e) ); if (countOfSockets > 0) { we_use_max_output = true; } // ajouter les tags de socket newProperties let converted_value = originalValue.replace(/[^\d\.\,]/g, '').replace(',', '.') let max_output = 401 // do not limit accepted values let out = '' if (intOriginalValue < max_output) { // rajouter l'unité de puissance kW dans la valeur out = converted_value + ' kW' } else { // prise en charge des valeurs en Watts et non en kW. debugLog('too high kW value detected', originalValue) if (intOriginalValue > 1000 && intOriginalValue < 401000) { let kilowatts = (parseFloat(converted_value) / 1000).toFixed(2).replace('.00', ''); out = ('' + kilowatts + ' kW').replace('.00', '') debugLog('valeurs en Watts out', out, 'original:', originalValue) this.stats.power_output++ } } out = (out).replace('.00', '') // debug land if (has_prise_type_combo_ccs) { newProperties['socket:type2_combo:output'] = out; this.stats.power_output++ } if (we_use_max_output) { newProperties['charging_station:output'] = out; } else { if (has_prise_type_2 && prise_type_e) { newProperties['socket:type_2:output'] = out; this.stats.power_output++ debugLog('2 prises, attribuer la plus haute valeur à la type 2', out) } if (countOfSockets === 1) { if (has_prise_type_2) { newProperties['socket:type_2:output'] = out; newProperties['socket:type_2'] = 1; this.stats.power_output++ } if (has_prise_type_combo_ccs) { newProperties['socket:type2_combo:output'] = out; newProperties['socket:type2_combo'] = 1; this.stats.power_output++ } if (prise_type_chademo) { newProperties['socket:chademo:output'] = out; newProperties['socket:chademo'] = 1; this.stats.power_output++ } if (prise_type_e) { newProperties['socket:typee:output'] = out; newProperties['socket:typee'] = 1; this.stats.power_output++ } } else { debugLog('no sockets', this.current_geojson_point.properties.ref) // console.log('sockets', countOfSockets, this.current_geojson_point.properties) } } return out } if (configObject.invert_boolean_value) { convertedValue = !this.convertToBoolean(originalValue) ? 'yes' : 'no' debugLog('invert boolean', convertedValue, originalValue) } if (configObject.convert_to_phone) { convertedValue = Formatters.convertToPhone(originalValue) if (originalValue !== convertedValue) { this.stats.phones_updated++ this.stats.phones_updated_list.push(convertedValue) } else { this.stats.phones_not_updated++ } } if (configObject.remove_original_key) { remove_original_key = true } if (configObject.ignore_if_falsy && this.falsyValues.indexOf(originalValue) !== -1) { remove_original_key = true } if (configObject.ignore_if_truthy && this.truthyValues.indexOf(originalValue) !== -1) { remove_original_key = true } /** * config pour une clé * nous pouvons renseigner une string ou un objet décrivant les transformations à réaliser */ if (configObject.conditional_values) { let keysConditionnalValues: any = Object.keys(configObject.conditional_values) let isFoundValue = keysConditionnalValues.indexOf(originalValue) let conditionnalConfig: any = configObject.conditional_values[keysConditionnalValues[isFoundValue]] debugLog('convertProperty: conditional_values__________', configObject.conditional_values) debugLog('isFoundValue', isFoundValue, originalValue) debugLog('keysConditionnalValues', keysConditionnalValues) if (!remove_original_key) { if (isFoundValue > -1) { /** ---------------------- * gestion des valeurs conditionnelles * ---------------------- */ debugLog('conditionnalConfig', conditionnalConfig) if (conditionnalConfig.ignore_this_data) { debugLog(`on ignore cette clé car sa valeur "${originalValue}" est à exclure: `, pointKeyName, '=>', newKey) remove_original_key = true; } if (conditionnalConfig.truthy_value) { // convertir la valeur, si elle est truthy, la transformer en ce que donne la propriété truthy_value // exemple: le jeu de données dit que la colonne cable_t2_attache vaut "True", mais on veut le convertir en "1". // on met donc truthy_value: '1' if (this.truthyValues.indexOf(originalValue) !== -1) { convertedValue = conditionnalConfig.truthy_value } } if (conditionnalConfig.falsy_value) { if (this.falsyValues.indexOf(originalValue) !== -1) { convertedValue = conditionnalConfig.falsy_value } } // use the value converted else if (conditionnalConfig.value_converted) { convertedValue = conditionnalConfig.value_converted } } } if (conditionnalConfig?.tags_to_add) { debugLog('on ajoute des tags', conditionnalConfig.tags_to_add) // on peut définir un ensemble de tags à rajouter let tagKeys = Object.keys(conditionnalConfig.tags_to_add) debugLog('conditionnalConfig.tags_to_add', conditionnalConfig.tags_to_add) conditionnalConfig.tags_to_add.forEach((object: any, pair: any) => { debugLog('object', object) debugLog('pair', pair) let key: any = Object.keys(object) key = key[0] let value = object[key] debugLog('key', key) debugLog('value', value) newProperties[key] = value }) } } debugLog('convertProperty: convertedValue ==========> {', newKey, ':', convertedValue, '}') debugLog(' =============== remove_original_key', newKey, remove_original_key) if (!remove_original_key && newKey && convertedValue && !configObject.ignore_this_data) { debugLog('convertProperty: added', newKey, (`${convertedValue}`).trim()) newProperties[newKey] = (`${convertedValue}`).trim() } } } else { debugLog('!!!!!! property not found in mappingKeys: ', pointKeyName) } } debugLog('newProperties', newProperties) return newProperties; } private isTruthyValue(someValue: string) { let convertedValue; if (this.truthyValues.indexOf(someValue) !== -1) { convertedValue = true } if (this.falsyValues.indexOf(someValue) !== -1) { convertedValue = false } return convertedValue } private convertToYesOrNo(originalValue: any) { debugLog('convertProperty: ==========> original value', originalValue) let convertedValue = ''; if (this.truthyValues.indexOf(originalValue) !== -1) { convertedValue = 'yes' } else { debugLog('convertProperty: ==========> !!! NOT in truthy values', originalValue) } if (this.falsyValues.indexOf(originalValue) !== -1) { convertedValue = 'no' } else { debugLog('convertProperty: ==========> !!! NOT in falsy values', originalValue) } return convertedValue; } private convertToBoolean(originalValue: any) { debugLog('convertProperty: ==========> original value', originalValue) let convertedValue; if (this.truthyValues.indexOf(originalValue) !== -1) { convertedValue = true } else { debugLog('convertProperty: ==========> !!! NOT in truthy values', originalValue) } if (this.falsyValues.indexOf(originalValue) !== -1) { convertedValue = false } else { debugLog('convertProperty: ==========> !!! NOT in falsy values', originalValue) } return convertedValue; } }