'B1 (2100 MHz)', 'B2' => 'B2 (1900 MHz)', 'B3' => 'B3 (1800 MHz)', 'B4' => 'B4 (AWS)', 'B5' => 'B5 (850 MHz)', 'B7' => 'B7 (2600 MHz)', 'B8' => 'B8 (900 MHz)', 'B20' => 'B20 (800 MHz)', 'B28' => 'B28 (700 MHz)', 'B38' => 'B38 (TDD 2600)', 'B40' => 'B40 (TDD 2300)', 'B41' => 'B41 (TDD 2500)', ]; /** * Recupere les mappings bande->EARFCN pour un operateur donne. * Retourne un tableau associatif ['B20' => ['start'=>6200,'end'=>6349], ...] */ function getOperatorBandMappings(PDO $db, int $operatorId): array { $stmt = $db->prepare('SELECT band, earfcn_start, earfcn_end FROM operator_band_mappings WHERE operator_id = ?'); $stmt->execute([$operatorId]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $mappings = []; foreach ($rows as $row) { $mappings[$row['band']] = [ 'start' => (int)$row['earfcn_start'], 'end' => (int)$row['earfcn_end'], ]; } return $mappings; } /** * Deduit l'EARFCN pour une bande donnee selon le mapping operateur. * Retourne l'EARFCN de debut de plage (valeur typique utilisee par l'operateur). */ function getEarfcnForBand(array $mappings, string $band): ?int { if (!isset($mappings[$band])) { return null; } return $mappings[$band]['start']; } /** * Convertit un EARFCN LTE en numero de bande 3GPP (fallback si pas de mapping operateur). */ function earfcnToBand(int $earfcn, string $tech): ?string { if ($tech !== '4G') { return null; } static $ranges = [ [0, 599, 'B1'], [600, 1199, 'B2'], [1200, 1949, 'B3'], [1950, 2399, 'B4'], [2400, 2649, 'B5'], [2650, 2749, 'B6'], [2750, 3449, 'B7'], [3450, 3799, 'B8'], [3800, 4149, 'B9'], [4150, 4749, 'B10'], [4750, 4949, 'B11'], [5010, 5179, 'B12'], [5180, 5279, 'B13'], [5280, 5379, 'B14'], [5730, 5849, 'B18'], [5850, 5999, 'B19'], [6000, 6149, 'B20'], [6150, 6449, 'B21'], [6450, 6599, 'B22'], [6600, 7399, 'B23'], [7500, 7699, 'B24'], [7700, 8039, 'B25'], [8040, 8689, 'B26'], [8690, 9039, 'B27'], [9040, 9209, 'B28'], [9210, 9659, 'B29'], [9660, 9769, 'B30'], [9770, 9869, 'B31'], [9870, 10359, 'B32'], [36000, 36199, 'B33'], [36200, 36349, 'B34'], [36350, 36949, 'B35'], [36950, 37549, 'B36'], [37550, 37749, 'B37'], [37750, 38249, 'B38'], [38250, 38649, 'B39'], [38650, 39649, 'B40'], [39650, 41589, 'B41'], [41590, 43589, 'B42'], [43590, 45589, 'B43'], [45590, 46589, 'B44'], [46590, 46789, 'B45'], [46790, 54539, 'B46'], [54540, 55239, 'B47'], [55240, 56739, 'B48'], [65536, 66435, 'B65'], [66436, 67335, 'B66'], [67336, 67535, 'B67'], [67536, 67835, 'B68'], [67836, 68335, 'B69'], [68336, 68585, 'B70'], [68586, 68935, 'B71'], [68936, 68985, 'B72'], [68986, 69035, 'B73'], [69036, 69465, 'B74'], [69466, 70315, 'B75'], [70316, 70365, 'B76'], [70366, 70545, 'B85'], ]; foreach ($ranges as [$lo, $hi, $band]) { if ($earfcn >= $lo && $earfcn <= $hi) { return $band; } } return null; } function ratToTech(string $rat): ?string { $rat = strtoupper(trim($rat)); return match (true) { str_contains($rat, 'NR') => '5G', str_contains($rat, 'LTE') => '4G', str_contains($rat, 'UMTS'), str_contains($rat, 'HSPA'), str_contains($rat, 'WCDMA') => '3G', str_contains($rat, 'GSM'), str_contains($rat, 'EDGE'), str_contains($rat, 'GPRS') => '2G', str_contains($rat, 'CDMA') => 'CDMA', default => null, }; } function parseCellMapperJson(string $json): array { $decoded = json_decode($json, true); if (json_last_error() !== JSON_ERROR_NONE) { return [null, [], 'JSON invalide : ' . json_last_error_msg()]; } $data = $decoded['responseData'] ?? $decoded; if (!is_array($data)) { return [null, [], 'Structure JSON inattendue : cle "responseData" absente ou invalide.']; } $rat = (string)($data['RAT'] ?? $data['RATSubType'] ?? ''); $tech = ratToTech($rat); if ($tech === null) { return [null, [], "Technologie radio non reconnue (RAT=\"$rat\")."]; } $siteId = $data['siteID'] ?? null; if ($siteId === null || !is_numeric($siteId)) { return [null, [], 'Champ "siteID" (eNB) absent ou invalide dans le JSON.']; } $enb = (int)$siteId; $regionId = $data['regionID'] ?? null; $areaCode = ($regionId !== null && is_numeric($regionId)) ? (int)$regionId : null; $provider = $data['Provider'] ?? []; $providerId = $provider['providerID'] ?? null; $cellsRaw = $data['cells'] ?? []; if (!is_array($cellsRaw) || empty($cellsRaw)) { return [null, [], 'Aucune cellule trouvee dans le champ "cells" du JSON.']; } $channels = $data['channels'] ?? []; $channels = is_array($channels) ? array_values($channels) : []; $entries = []; foreach ($cellsRaw as $internalId => $cell) { if (!is_array($cell) || !isset($cell['Sector'])) { continue; } $entries[] = $cell; } usort($entries, static fn($a, $b) => ((int)$a['Sector']) <=> ((int)$b['Sector'])); $meta = [ 'tech' => $tech, 'enb' => $enb, 'area_code' => $areaCode, 'provider_id' => $providerId !== null ? (int)$providerId : null, 'site_id_raw' => (string)$siteId, ]; $cells = []; foreach ($entries as $index => $cell) { $cellId = (int)$cell['Sector']; $pci = isset($cell['PCI']) && is_numeric($cell['PCI']) ? (int)$cell['PCI'] : null; $earfcn = null; if (isset($channels[$index]) && is_numeric($channels[$index])) { $earfcn = (int)$channels[$index]; } $bandwidth = null; if (isset($cell['Bandwidth']) && is_numeric($cell['Bandwidth'])) { $bandwidth = ((int)$cell['Bandwidth']) . 'MHz'; } $band = $earfcn !== null ? earfcnToBand($earfcn, $tech) : null; $cells[] = [ 'cell_id' => $cellId, 'pci' => $pci, 'arfcn' => $earfcn, 'band' => $band, 'bandwidth' => $bandwidth, ]; } return [$meta, $cells, null]; } function findStagingCellsForEnb(PDO $db, string $tech, string $mcc, string $mnc, int $enb): array { $stmt = $db->prepare( 'SELECT id, cell_id, sub_id, arfcn, band, bandwidth, area_code, status FROM staging_cells WHERE tech = ? AND mcc = ? AND mnc = ? AND sub_id2 = ?' ); $stmt->execute([$tech, $mcc, $mnc, $enb]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $byCellId = []; foreach ($rows as $row) { $byCellId[(int)$row['cell_id']] = $row; } return $byCellId; } // Traitement du formulaire if ($_SERVER['REQUEST_METHOD'] === 'POST') { require_csrf_token(); $mcc = trim((string)($_POST['mcc'] ?? '')); $mnc = trim((string)($_POST['mnc'] ?? '')); $operatorId = (int)($_POST['operator_id'] ?? 0); $selectedBand = trim((string)($_POST['band'] ?? '')); $jsonInput = trim((string)($_POST['json_input'] ?? '')); $uploadedFile = $_FILES['json_file'] ?? null; if ($uploadedFile && $uploadedFile['error'] === UPLOAD_ERR_OK) { if (!is_uploaded_file($uploadedFile['tmp_name'])) { $errorMessage = 'Fichier invalide (upload non reconnu comme legitime).'; } elseif ($uploadedFile['size'] > MAX_JSON_SIZE_BYTES) { $errorMessage = 'Le fichier depasse la limite de ' . (MAX_JSON_SIZE_BYTES / 1024 / 1024) . ' Mo.'; } else { $jsonInput = (string)file_get_contents($uploadedFile['tmp_name']); } } if ($errorMessage === null && $jsonInput === '') { $errorMessage = 'Aucune donnee JSON fournie (ni fichier, ni texte colle).'; } elseif ($errorMessage === null && !preg_match('/^\d{3}$/', $mcc)) { $errorMessage = 'MCC invalide (3 chiffres attendus).'; } elseif ($errorMessage === null && !preg_match('/^\d{2,3}$/', $mnc)) { $errorMessage = 'MNC invalide (2 ou 3 chiffres attendus).'; } elseif ($errorMessage === null && strlen($jsonInput) > MAX_JSON_SIZE_BYTES) { $errorMessage = 'Le contenu JSON colle depasse la limite de ' . (MAX_JSON_SIZE_BYTES / 1024 / 1024) . ' Mo.'; } else { $mcc = str_pad($mcc, 3, '0', STR_PAD_LEFT); $mnc = str_pad($mnc, 2, '0', STR_PAD_LEFT); [$meta, $parsedCells, $parseError] = parseCellMapperJson($jsonInput); if ($parseError !== null) { $errorMessage = $parseError; } else { $tech = $meta['tech']; $enb = $meta['enb']; $areaCode = $meta['area_code']; try { // Si operator_id est fourni, recuperer ses mappings bande/EARFCN $bandMappings = $operatorId > 0 ? getOperatorBandMappings($db, $operatorId) : []; // Si une bande est selectionnee, deduire l'EARFCN du mapping operateur $forcedEarfcn = null; $forcedBand = null; if ($selectedBand !== '' && !empty($bandMappings[$selectedBand])) { $forcedBand = strtoupper($selectedBand); $forcedEarfcn = getEarfcnForBand($bandMappings, $forcedBand); } $stmt = $db->prepare('SELECT id FROM operators WHERE mcc = ? AND mnc = ?'); $stmt->execute([$mcc, $mnc]); $operatorIdFromDb = $stmt->fetchColumn(); $operatorIdFromDb = $operatorIdFromDb !== false ? (int)$operatorIdFromDb : null; $existingByCellId = findStagingCellsForEnb($db, $tech, $mcc, $mnc, $enb); $updateStmt = $db->prepare( 'UPDATE staging_cells SET sub_id = ?, arfcn = ?, band = ?, bandwidth = ?, area_code = COALESCE(area_code, ?) WHERE id = ?' ); $insertStmt = $db->prepare( 'INSERT INTO staging_cells (import_id, tech, mcc, mnc, operator_guess, cell_id, area_code, sub_id, sub_id2, band, frequency, azimuth, bandwidth, address_desc, lat, lon, location_desc, arfcn, raw_line, source, possible_dup_of, status) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, NULL, NULL, NULL, ?, ?, ?, "cellmapper_import", NULL, "pending")' ); $db->beginTransaction(); $updated = 0; $created = 0; $skippedNoChange = 0; $details = []; foreach ($parsedCells as $cell) { $cellId = $cell['cell_id']; // Appliquer la bande et l'EARFCN forces si selectionnes $bandToUse = $forcedBand ?? $cell['band']; $arfcnToUse = $forcedEarfcn ?? $cell['arfcn']; if (isset($existingByCellId[$cellId])) { $existing = $existingByCellId[$cellId]; $noChange = (int)($existing['sub_id'] ?? -1) === ($cell['pci'] ?? -1) && (int)($existing['arfcn'] ?? -1) === ($arfcnToUse ?? -1) && ($existing['band'] ?? null) === $bandToUse && ($existing['bandwidth'] ?? null) === $cell['bandwidth'] && ($existing['area_code'] !== null || $areaCode === null); if ($noChange) { $skippedNoChange++; $details[] = "Cellule $cellId (eNB $enb) : deja a jour, aucune modification."; continue; } $updateStmt->execute([ $cell['pci'], $arfcnToUse, $bandToUse, $cell['bandwidth'], $areaCode, $existing['id'], ]); $updated++; $tacNote = $existing['area_code'] === null && $areaCode !== null ? ", TAC=$areaCode (complete)" : ''; $details[] = "Cellule $cellId (eNB $enb) : mise a jour (PCI={$cell['pci']}, ARFCN=$arfcnToUse, bande=$bandToUse$tacNote)."; } else { $rawLine = sprintf( 'cellmapper:enb=%d;cell_id=%d;pci=%s;arfcn=%s;band=%s;bandwidth=%s;tac=%s', $enb, $cellId, $cell['pci'] ?? 'null', $arfcnToUse ?? 'null', $bandToUse ?? 'null', $cell['bandwidth'] ?? 'null', $areaCode ?? 'null' ); $insertStmt->execute([ $tech, $mcc, $mnc, $operatorIdFromDb, $cellId, $areaCode, $cell['pci'], $enb, $bandToUse, $cell['bandwidth'], 'Import CellMapper : coordonnees/azimut/adresse a completer manuellement', $arfcnToUse, $rawLine, ]); $created++; $isNewEnb = empty($existingByCellId); $details[] = "Cellule $cellId (eNB $enb) : creee" . ($isNewEnb ? ' (nouvel eNB, a localiser via cadastre)' : ' (nouvelle cellule sur eNB existant)') . ", TAC=" . ($areaCode ?? 'inconnu') . "."; } } $db->commit(); $importSummary = [ 'tech' => $tech, 'enb' => $enb, 'mcc' => $mcc, 'mnc' => $mnc, 'area_code' => $areaCode, 'total_parsed' => count($parsedCells), 'updated' => $updated, 'created' => $created, 'skipped_no_change' => $skippedNoChange, 'details' => $details, ]; $successMessage = sprintf( 'eNB %d (%s, MCC %s / MNC %s, TAC %s) : %d cellule(s) analysee(s) — %d creee(s), %d mise(s) a jour, %d deja a jour.', $enb, $tech, $mcc, $mnc, $areaCode ?? 'inconnu', count($parsedCells), $created, $updated, $skippedNoChange ); } catch (Throwable $e) { if ($db->inTransaction()) { $db->rollBack(); } error_log('[cellmapper_import] ' . $e->getMessage()); $errorMessage = 'Une erreur est survenue lors du traitement. Aucune donnee n\'a ete modifiee. Consultez les journaux du serveur.'; } } } } // Charger les operateurs pour le select $operators = $db->query( 'SELECT o.*, c.name AS country_name FROM operators o JOIN countries c ON c.id = o.country_id ORDER BY c.name, o.brand' )->fetchAll(PDO::FETCH_ASSOC); $operatorsByCountry = []; foreach ($operators as $op) { $operatorsByCountry[$op['country_id']][] = $op; } // Charger les mappings pour chaque operateur (pour le JS) $bandMappingsByOperator = []; $mappings = $db->query('SELECT * FROM operator_band_mappings ORDER BY operator_id, band')->fetchAll(PDO::FETCH_ASSOC); foreach ($mappings as $m) { $bandMappingsByOperator[$m['operator_id']][] = $m; } ?> NetMonster Admin - Import CellMapper

Import CellMapper (JSON)

Collez ou televersez l'export JSON d'un site CellMapper (un eNB). Selectionnez la bande 4G pour remplir automatiquement l'EARFCN selon le mapping de l'operateur. Seuls les parametres radio techniques sont extraits (Cell ID, PCI, EARFCN, bande, largeur de bande, TAC).

Nouvel import

MCC/MNC ne sont pas presents dans le JSON CellMapper : ils doivent etre saisis manuellement. Si vous selectionnez un operateur et une bande, l'EARFCN sera rempli automatiquement selon le mapping configure dans countries_settings.php.

Selectionnez un fichier .json (taille maximale : 2 Mo)

Resultat de l'import — eNB () — TAC

cellule(s) analysee(s)
creee(s)
mise(s) a jour
deja a jour (ignoree(s))

Detail

Aller a la page de validation →

Rappel des regles appliquees