diff --git a/README.md b/README.md index a309505..a5d4d15 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,11 @@ Résultat: `output/my_converted_data_set__mappingIssy2Roues.json`[makefile](make ## Examen des jeux de données Osmose Osmose permet la détection et suggestion de corrections de données dans OSM. On peut utiliser un export de ces données à la conversion d'un Mapper. Pour cela, récupérez un jeu de données et utilisez le convertisseur Osmose de votre choix sur ces données afin d'en faire un nouveau jeu de données. + +## Création d'une configuration de conversion +Utilisez le script `create_mapping.ts`, celui-ci vous posera quelques questions et se chargera d'ajouter la nouvelle classe dans le fichier principal de conversion. +`ts-node create_mapping.ts` + [doc sur l'ajout d'un jeu de données](ajout_jeu_de_données.md) ## Bornes de recharge - IRVE diff --git a/convert_to_osm_tags.ts b/convert_to_osm_tags.ts index ac8d3e4..53b4ec8 100644 --- a/convert_to_osm_tags.ts +++ b/convert_to_osm_tags.ts @@ -53,9 +53,11 @@ import MappingAskAngela from "./mappings/converters/configAskAngela"; import MappingPlanningFamlial from "./mappings/converters/configPlanningFamilial"; import MappingSurveillanceRouen from "./mappings/converters/configSurveillance"; import MappingRnb from "./mappings/converters/configRnb"; +import MappingArbresRemarquablesRouen from './mappings/converters/configArbresRemarquablesRouen' const limitWarningPercentageChangeInPoints = 5; // show a warning when more than N percent of the number of points changed const allowed_configs: any = { + MappingArbresRemarquablesRouen, MappingRnb, mappingIssy2Roues, mappingConfigIRVE, diff --git a/mappings/converters/configArbresRemarquablesRouen.ts b/mappings/converters/configArbresRemarquablesRouen.ts new file mode 100644 index 0000000..80cb77f --- /dev/null +++ b/mappings/converters/configArbresRemarquablesRouen.ts @@ -0,0 +1,74 @@ +/** + * arbres monumentaux de Rouen + */ +import MappingConfigType from "../mapping-config.type"; + +const MappingArbresRemarquablesRouen: MappingConfigType = { + config_name: "MappingArbresRemarquablesRouen", + config_author: "tykayn contact@cipherbliss.com", + default_properties_of_point: { + natural: 'tree', + source: 'Métropole de Rouen', + historic: 'monument' + }, + source: { + geojson_path: 'https://data.metropole-rouen-normandie.fr/api/explore/v2.1/catalog/datasets/arbres-remarquables-metropole-rouen-normandie-2019/exports/geojson?lang=fr&timezone=Europe%2FBerlin', + url: '' + }, + filters: { + // exclude_point_if_tag_not_empty: ['id_osm'], // exclure les points ayant déjà un id_osm pour éviter les doublons + // offset: 1 + }, + add_not_mapped_tags_too: false, + boolean_keys: [], + tags_to_ignore_if_value_is: ['Non renseigne'], + tags: { + // Mapping des champs du fichier source vers les tags OSM + // 'champ_source': 'tag_osm_cible', + // 'champ_source_id': 'ref:FR:votre_id', + + // Exemple de transformation plus complexe + // nom: { + // key_converted: 'name', + // convert_to_name: true, + // }, + + // "geo_point_2d": "", + "gml_id": { + key_converted: 'ref:FR:rouen:gml_id', + convert_to_name: true, + }, + // "objectid": "", + "nom": "name", + // "foret": "", + // "variete": "", + // "diametre_pied": "", + // "hauteur": "", + // "age": "", + // "parcelle": "", + // "photo": "", + // "pdf": "", + // "gps": "", + // "x": "", + // "y": "", + + "description": "description", + "essence_forestiere": { + conditional_values: { + "Feuillu ": { + 'tags_to_add': { + "leaf_type": "broadleaved", + } + }, + "Résineux": { + 'tags_to_add': { + "leaf_type": "needleleaved", + } + }, + } + }, + + } +} + +export default MappingArbresRemarquablesRouen; diff --git a/osm_output/my_converted_data_set__irve-latest-osmose.osm b/osm_output/my_converted_data_set__irve-latest-osmose.osm new file mode 100644 index 0000000..e69de29 diff --git a/propose_mapping_from_data.py b/propose_mapping_from_data.py deleted file mode 100644 index 3b7e31b..0000000 --- a/propose_mapping_from_data.py +++ /dev/null @@ -1,33 +0,0 @@ -import json -import sys -import json -# examiner un jeu de données geojson, et proposer des tags à mettre dans une config -# comme celle ci mappings/converters/configIRVE.ts -# -# exemple de lancement de commande: -# python propose_mapping_from_data.py mon_fichier.geojson - - -# Vérifie si un argument est fourni -if len(sys.argv) < 2: - print("Usage: python propose_mapping_from_data.py ") - sys.exit(1) - -# Ouvre le fichier GeoJSON donné -with open(sys.argv[1]) as f: - data = json.load(f) - -# Extraire les propriétés de tous les points de la FeatureCollection -properties = [] -for feature in data['features']: - props = feature['properties'] - properties.extend(props.keys()) - -# Créer un dictionnaire avec toutes les clés comme clés et des valeurs par défaut vides -defaults = {prop: '' for prop in properties} - -# Remplacer les clés contenant "web" ou "téléphone" par "contact:website" ou "contact:phone" -defaults.update({prop: 'contact:website' if 'web' in prop else 'contact:phone' if 'téléphone' in prop else '' for prop in properties}) - -# Convertir le dictionnaire en JSON et l'afficher -print(json.dumps(defaults, indent=2)) diff --git a/propose_mapping_from_geojson.ts b/propose_mapping_from_geojson.ts new file mode 100644 index 0000000..317e535 --- /dev/null +++ b/propose_mapping_from_geojson.ts @@ -0,0 +1,195 @@ +/** + * propose_mapping_from_data.ts + * Examine un jeu de données GeoJSON et propose des tags à mettre dans une config + * comme celle dans mappings/converters/configIRVE.ts + * + * Exemple de lancement: + * npx ts-node propose_mapping_from_data.ts mon_fichier.geojson + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +interface GeoJSONFeature { + type: string; + geometry: any; + properties: Record; +} + +interface GeoJSONData { + type: string; + features: GeoJSONFeature[]; +} + +/** + * Vérifie les arguments de la ligne de commande + */ +function checkArguments(): string { + if (process.argv.length < 3) { + console.error("Usage: npx ts-node propose_mapping_from_data.ts "); + process.exit(1); + } + return process.argv[2]; +} + +/** + * Charge et parse le fichier GeoJSON + */ +function loadGeoJSONFile(filePath: string): GeoJSONData { + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const data = JSON.parse(fileContent); + + // Vérifie que le fichier est bien un GeoJSON avec des features + if (!data.features || !Array.isArray(data.features)) { + console.error("Le fichier ne semble pas être un GeoJSON valide avec des features"); + process.exit(1); + } + + return data; + } catch (error) { + console.error(`Erreur lors du chargement du fichier: ${error}`); + process.exit(1); + } +} + +/** + * Extrait toutes les propriétés uniques des features + */ +function extractProperties(data: GeoJSONData): Set { + const properties: Set = new Set(); + + for (const feature of data.features) { + if (feature.properties) { + Object.keys(feature.properties).forEach(key => properties.add(key)); + } + } + + return properties; +} + +/** + * Compte les occurrences des différentes valeurs pour chaque propriété + */ +function countPropertyValues(data: GeoJSONData): Record> { + const valuesCounts: Record> = {}; + + // Initialiser le comptage pour chaque propriété + for (const feature of data.features) { + if (feature.properties) { + Object.entries(feature.properties).forEach(([key, value]) => { + // Convertir la valeur en chaîne pour pouvoir la compter + const stringValue = String(value); + + // Initialiser le compteur pour cette propriété si nécessaire + if (!valuesCounts[key]) { + valuesCounts[key] = {}; + } + + // Incrémenter le compteur pour cette valeur + valuesCounts[key][stringValue] = (valuesCounts[key][stringValue] || 0) + 1; + }); + } + } + + return valuesCounts; +} + +/** + * Crée les mappings suggérés pour les propriétés + */ +function createMappingSuggestions(properties: Set): Record { + const defaults: Record = {}; + + properties.forEach(prop => { + // Cherche les propriétés liées au web ou au téléphone + if (prop.toLowerCase().includes('web') || prop.toLowerCase().includes('site') || prop.toLowerCase().includes('url')) { + defaults[prop] = 'contact:website'; + } + else if (prop.toLowerCase().includes('telephone') || prop.toLowerCase().includes('téléphone') || prop.toLowerCase().includes('phone')) { + defaults[prop] = 'contact:phone'; + } + else if (prop.toLowerCase().includes('nom') || prop.toLowerCase().includes('name')) { + defaults[prop] = 'name'; + } + else if (prop.toLowerCase().includes('adresse') || prop.toLowerCase().includes('address')) { + defaults[prop] = 'addr:full'; + } + else if (prop.toLowerCase().includes('id') || prop.toLowerCase().includes('ref')) { + defaults[prop] = 'ref'; + } + else { + defaults[prop] = ''; + } + }); + + return defaults; +} + +/** + * Affiche les statistiques des valeurs pour chaque propriété + */ +function displayPropertyValueStats(valuesCounts: Record>): void { + console.log('\n=== Statistiques des valeurs par propriété ==='); + + Object.entries(valuesCounts).forEach(([prop, values]) => { + const totalValues = Object.values(values).reduce((sum, count) => sum + count, 0); + const uniqueValues = Object.keys(values).length; + + console.log(`\nPropriété: ${prop}`); + console.log(`- Total d'occurrences: ${totalValues}`); + console.log(`- Valeurs uniques: ${uniqueValues}`); + + // Si trop de valeurs uniques, n'afficher que les plus fréquentes + if (uniqueValues > 10) { + console.log('- Top 5 des valeurs les plus fréquentes:'); + const sortedValues = Object.entries(values) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + + sortedValues.forEach(([value, count]) => { + // Tronquer les valeurs très longues + const displayValue = value.length > 50 ? value.substring(0, 47) + '...' : value; + console.log(` "${displayValue}": ${count} occurrences (${Math.round(count / totalValues * 100)}%)`); + }); + console.log(` ... et ${uniqueValues - 5} autres valeurs`); + } else { + console.log('- Toutes les valeurs:'); + Object.entries(values) + .sort((a, b) => b[1] - a[1]) + .forEach(([value, count]) => { + // Tronquer les valeurs très longues + const displayValue = value.length > 50 ? value.substring(0, 47) + '...' : value; + console.log(` "${displayValue}": ${count} occurrences (${Math.round(count / totalValues * 100)}%)`); + }); + } + }); + + console.log('\n=== Fin des statistiques ===\n'); +} + +/** + * Fonction principale + */ +function main(): void { + const filePath = checkArguments(); + const data = loadGeoJSONFile(filePath); + + console.log(`\nFichier GeoJSON chargé: ${filePath}`); + console.log(`Nombre de features: ${data.features.length}`); + + const properties = extractProperties(data); + + const valuesCounts = countPropertyValues(data); + displayPropertyValueStats(valuesCounts); + + const mappingSuggestions = createMappingSuggestions(properties); + + console.log('Suggestions de mapping OSM:'); + console.log(JSON.stringify(mappingSuggestions, null, 2)); + + console.log(`Nombre de propriétés uniques: ${properties.size}`); +} + +// Exécution du programme +main(); \ No newline at end of file