mapping-geojson-osm/mappings/engine.ts

600 lines
27 KiB
TypeScript

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]
/**
* reduce number of features
* @param offsetCount
* @param listOfFeatures
*/
filterFeaturesByOffset(offsetCount: number, listOfFeatures: any): Array<any> {
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) {
// une transformation de la valeur
// apply transformation to value
convertedValue = configObject.transform_function(originalValue)
// console.log('transform_function: originalValue', originalValue, convertedValue)
// this.stats.power_output++
}
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:type_2_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 original_array = originalValue.split('')
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;
}
}