import custom_utils from './utils' import MappingConfigType from "./mapping-config.type"; import {debuglog} from "util"; 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 = true; public stats: any; private current_converted_geojson_point: any; private current_geojson_point: any; constructor(mappingConfig: MappingConfigType) { this.setConfig(mappingConfig) this.stats = { phones_updated: 0, power_output: 0, phones_updated_list: [], phones_not_updated: 0 } } setConfig(mappingConfig: MappingConfigType) { 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(bboxConfig:any, listOfFeatures:any) { // debugLog('bboxConfig', bboxConfig) // let filteredList = listOfFeatures // // TODO // return filteredList // } filterFeaturesByPropertyRegex(propertyName: string, criteriaRegex: any, listOfFeatures: any) { let filteredList = listOfFeatures.filter((feature: any) => { return criteriaRegex.test(feature?.properties[propertyName]) }) return filteredList } /** * 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) 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 */ 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') } if (isConfigMappingObject) { let configObject = mappingConfigOfTag debugLog('convertProperty: is config object', configObject) let newKey: any = '' + pointKeyName let remove_original_key = false; 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) ); // console.log('this.current_geojson_point.properties.prise_type_2', this.current_geojson_point.properties.prise_type_2, this.isTruthyValue(this.current_geojson_point.properties.prise_type_2), 'countOfSockets:', countOfSockets) 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 accepted_values = [3, 7, 22, 50, 150, 300] // let accepted_values = [3, 7, 22, 50, 150, 300] // if (accepted_values.includes(converted_value)) { let out = '' if (intOriginalValue < max_output) { // enlever les lettres dans la valeur out = converted_value + ' kW' } else { // console.log('too high kW value detected', originalValue) // prise en charge des valeurs en Watts et non en kW. if (intOriginalValue > 1000 && intOriginalValue < 401000) { let kilowatts = (parseFloat(converted_value) / 1000).toFixed(2).replace('.00', ''); out = ('' + kilowatts + ' kW').replace('.00', '') // console.log('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) { /** * nettoyer les numéros de téléphone en ne gardant que les nombres et le préfixe de pays */ // debugLog('originalValue', originalValue.substring(1)) if (!originalValue) { originalValue = '' } let original_without_spaces = originalValue.replace(/ /g, '') let cleaned_value = `${original_without_spaces}` cleaned_value = cleaned_value .trim() .replace('Stations-e', '') .replace(/[a-zA-Zéèà]/ig, '') .replace(/[\(\)\.\- ]/g, '') let add_prefix = false; if ( /^\d/.test(cleaned_value) && !/^\+33 /.test(original_without_spaces) ) { add_prefix = true } cleaned_value = cleaned_value.replace('+33', '') if (/^0/.test(cleaned_value)) { cleaned_value = cleaned_value.substring(1) } let array_of_numbers = cleaned_value .split('') let ii = 0; if (cleaned_value.length == 4) { ii = 1 } convertedValue = '' array_of_numbers.forEach((num: string) => { if (ii % 2) { convertedValue += ' '; } convertedValue += num; ii++; }) convertedValue = convertedValue.replace(' ', ' ').trim(); debugLog('convertedValue', convertedValue) if ( /^\d/.test(convertedValue) && !/^\+33 /.test(convertedValue) ) { add_prefix = true } if (add_prefix) { convertedValue = `+33 ` + convertedValue } debugLog('phone: ', originalValue, '=>', convertedValue) 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 (!remove_original_key && configObject.conditional_values) { debugLog('convertProperty: conditional_values__________', configObject.conditional_values) let keysConditionnalValues: any = Object.keys(configObject.conditional_values) let isFoundValue = keysConditionnalValues.indexOf(originalValue) debugLog('isFoundValue', isFoundValue, originalValue) debugLog('keysConditionnalValues', keysConditionnalValues) if (isFoundValue > -1) { let conditionnalConfig: any = configObject.conditional_values[keysConditionnalValues[isFoundValue]] /** ---------------------- * 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.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 }) } 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 } } } 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) newProperties[newKey] = (`${convertedValue}`).trim() } } } else { debugLog('!!!!!! property not found in mappingKeys: ', pointKeyName) } } 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; } }