321 lines
12 KiB
Dart
321 lines
12 KiB
Dart
import 'package:terepi_seged/models/sensor_chanel.dart';
|
|||
|
|
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||
|
|
|
||
|
|
/// SPS (Shell Processing Support) fájlok beolvasása — a SEG 1993-as
|
||
|
|
/// "SPS Format for Land 3D Surveys" specifikációja szerint, fix
|
||
|
|
/// oszlop-pozíciókkal (1-alapú, záró oszlop is beleértve).
|
||
|
|
///
|
||
|
|
/// Csak azt olvassuk ki, ami a csatorna-geometria ellenőrzéséhez kell:
|
||
|
|
/// * R-fájl (Receiver "Point Record"): vonal, pontszám, EOV Y/X, magasság
|
||
|
|
/// * X-fájl (Relation Record): csatorna-tartomány → vonal + állomás-tartomány
|
||
|
|
///
|
||
|
|
/// A fix oszlopszélességű, évtizedes szabvány gyártónként kicsit eltérő
|
||
|
|
/// exportokat is szülhet — ezért import előtt MINDIG előnézet van
|
||
|
|
/// (lásd SpsImportPreview), soha nem mentünk vakon.
|
||
|
|
class SpsParser {
|
||
|
|
SpsParser._();
|
||
|
|
|
||
|
|
// ── Nyers sor-kivágás (1-alapú, záró oszlop is benne) ─────────────
|
||
|
|
static String _col(String line, int from, int to) {
|
||
|
|
if (line.length < from) return '';
|
||
|
|
final end = line.length < to ? line.length : to;
|
||
|
|
return line.substring(from - 1, end).trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool _isDataLine(String line, String expectedFirstChar) {
|
||
|
|
if (line.isEmpty) return false;
|
||
|
|
if (line.startsWith('EOF')) return false;
|
||
|
|
if (line[0] == 'H') return false; // fejléc/komment sor
|
||
|
|
return line[0].toUpperCase() == expectedFirstChar;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
// R-fájl (vevőpont) — "Point Record", cols 1-80
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
// 1 Rekord-azonosító 1-1 "R"
|
||
|
|
// 2 Vonalnév 2-17
|
||
|
|
// 3 Pontszám 18-25
|
||
|
|
// 4 Pont-index 26-26
|
||
|
|
// 11 EOV Y (easting) 47-55
|
||
|
|
// 12 EOV X (northing) 56-65
|
||
|
|
// 13 Magasság 66-71
|
||
|
|
|
||
|
|
static List<SpsPointRecord> parseReceiverFile(String content) =>
|
||
|
|
_parsePointFile(content, 'R');
|
||
|
|
|
||
|
|
/// Forráspontok (vibrátor-állomások) — az S-fájl UGYANAZT az
|
||
|
|
/// oszlop-elrendezést használja, mint az R-fájl, csak a rekord-jelölő
|
||
|
|
/// betű más.
|
||
|
|
static List<SpsPointRecord> parseSourceFile(String content) =>
|
||
|
|
_parsePointFile(content, 'S');
|
||
|
|
|
||
|
|
static List<SpsPointRecord> _parsePointFile(
|
||
|
|
String content, String recordType) {
|
||
|
|
final result = <SpsPointRecord>[];
|
||
|
|
for (final raw in content.split(RegExp(r'\r\n|\r|\n'))) {
|
||
|
|
if (!_isDataLine(raw, recordType)) continue;
|
||
|
|
final lineId = _col(raw, 2, 17);
|
||
|
|
final pointStr = _col(raw, 18, 25);
|
||
|
|
final indexStr = _col(raw, 26, 26);
|
||
|
|
final eastingStr = _col(raw, 47, 55);
|
||
|
|
final northingStr = _col(raw, 56, 65);
|
||
|
|
final elevStr = _col(raw, 66, 71);
|
||
|
|
|
||
|
|
// A pontszám ritkán tartalmazhat törtrészt — az egész részt vesszük
|
||
|
|
// állomásszámként.
|
||
|
|
final pointNum =
|
||
|
|
int.tryParse(pointStr.split('.').first.replaceAll(RegExp(r'\D'), ''));
|
||
|
|
if (pointNum == null) continue;
|
||
|
|
|
||
|
|
result.add(SpsPointRecord(
|
||
|
|
lineId: lineId,
|
||
|
|
station: pointNum,
|
||
|
|
pointIndex: int.tryParse(indexStr) ?? 1,
|
||
|
|
eovY: double.tryParse(eastingStr),
|
||
|
|
eovX: double.tryParse(northingStr),
|
||
|
|
elevation: double.tryParse(elevStr),
|
||
|
|
rawLine: raw,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
// X-fájl (kapcsolat) — "Relation Record", cols 1-80
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
// 9 From channel 39-42
|
||
|
|
// 10 To channel 43-46
|
||
|
|
// 11 Channel increment 47-47
|
||
|
|
// 12 Vevő-vonalnév 48-63
|
||
|
|
// 13 From receiver 64-71
|
||
|
|
// 14 To receiver 72-79
|
||
|
|
// 15 Receiver index 80-80
|
||
|
|
|
||
|
|
static List<SpsRelationRecord> parseRelationFile(String content) {
|
||
|
|
final result = <SpsRelationRecord>[];
|
||
|
|
for (final raw in content.split(RegExp(r'\r\n|\r|\n'))) {
|
||
|
|
if (!_isDataLine(raw, 'X')) continue;
|
||
|
|
|
||
|
|
final fromCh = int.tryParse(_col(raw, 39, 42));
|
||
|
|
final toCh = int.tryParse(_col(raw, 43, 46));
|
||
|
|
final chInc = int.tryParse(_col(raw, 47, 47)) ?? 1;
|
||
|
|
final recvLine = _col(raw, 48, 63);
|
||
|
|
final fromRecv = int.tryParse(_col(raw, 64, 71));
|
||
|
|
final toRecv = int.tryParse(_col(raw, 72, 79));
|
||
|
|
|
||
|
|
if (fromCh == null ||
|
||
|
|
toCh == null ||
|
||
|
|
fromRecv == null ||
|
||
|
|
toRecv == null) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
result.add(SpsRelationRecord(
|
||
|
|
fromChannel: fromCh,
|
||
|
|
toChannel: toCh,
|
||
|
|
channelIncrement: chInc <= 0 ? 1 : chInc,
|
||
|
|
recvLineId: recvLine,
|
||
|
|
fromReceiver: fromRecv,
|
||
|
|
toReceiver: toRecv,
|
||
|
|
rawLine: raw,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Egy X-rekord (csatorna-TARTOMÁNY) egyedi (csatorna, vonal, állomás)
|
||
|
|
/// hármasokra bontása. A normál (egykomponensű) esetben a csatorna- és
|
||
|
|
/// vevőszám párhuzamosan fut végig a tartományon. Többkomponensű
|
||
|
|
/// (channel increment > 1) esetet — ritka, pl. 3C geofonoknál — nem
|
||
|
|
/// bontunk szét channelenként külön állomásra, mert az UGYANAHHOZ az
|
||
|
|
/// egy fizikai ponthoz tartozna; ilyenkor a tartomány KEZDŐ csatornáját
|
||
|
|
/// társítjuk a ponthoz, a többit átugorjuk.
|
||
|
|
static List<({int channel, String lineId, int station})> expandRelation(
|
||
|
|
SpsRelationRecord r) {
|
||
|
|
final out = <({int channel, String lineId, int station})>[];
|
||
|
|
if (r.channelIncrement != 1) {
|
||
|
|
out.add((
|
||
|
|
channel: r.fromChannel,
|
||
|
|
lineId: r.recvLineId,
|
||
|
|
station: r.fromReceiver
|
||
|
|
));
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
final chCount = r.toChannel - r.fromChannel;
|
||
|
|
final recvCount = r.toReceiver - r.fromReceiver;
|
||
|
|
if (chCount < 0) return out;
|
||
|
|
for (var i = 0; i <= chCount; i++) {
|
||
|
|
final station = chCount == 0
|
||
|
|
? r.fromReceiver
|
||
|
|
: r.fromReceiver + (recvCount * i / chCount).round();
|
||
|
|
out.add(
|
||
|
|
(channel: r.fromChannel + i, lineId: r.recvLineId, station: station));
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
// Előnézet összeállítása — R + X összefésülve
|
||
|
|
// ═════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
/// [receiverPoints] és/vagy [relations] közül legalább az egyik legyen
|
||
|
|
/// nem üres. Ha csak relations van, a csatorna-hozzárendelés megvan,
|
||
|
|
/// de terv-koordináta nélkül (a GNSS-mért kitűzési pont adja majd a
|
||
|
|
/// pozíciót). Ha csak receiverPoints van, nincs csatornaszám — ekkor
|
||
|
|
/// channel = null marad minden sorban (a UI jelzi, hogy ez hiányos).
|
||
|
|
static SpsImportPreview buildPreview({
|
||
|
|
List<SpsPointRecord> receiverPoints = const [],
|
||
|
|
List<SpsRelationRecord> relations = const [],
|
||
|
|
}) {
|
||
|
|
final byLineStation = <String, SpsPointRecord>{};
|
||
|
|
for (final p in receiverPoints) {
|
||
|
|
byLineStation['${p.lineId}|${p.station}'] = p;
|
||
|
|
}
|
||
|
|
|
||
|
|
final rows = <SpsPreviewRow>[];
|
||
|
|
var unmatchedChannels = 0;
|
||
|
|
|
||
|
|
if (relations.isNotEmpty) {
|
||
|
|
for (final rel in relations) {
|
||
|
|
for (final e in expandRelation(rel)) {
|
||
|
|
final match = byLineStation['${e.lineId}|${e.station}'];
|
||
|
|
if (match == null) unmatchedChannels++;
|
||
|
|
rows.add(SpsPreviewRow(
|
||
|
|
channel: e.channel,
|
||
|
|
lineId: e.lineId,
|
||
|
|
station: e.station,
|
||
|
|
eovY: match?.eovY,
|
||
|
|
eovX: match?.eovX,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Csak R-fájl: nincs csatornaszám, csak a vevőpontok listája.
|
||
|
|
for (final p in receiverPoints) {
|
||
|
|
rows.add(SpsPreviewRow(
|
||
|
|
channel: null,
|
||
|
|
lineId: p.lineId,
|
||
|
|
station: p.station,
|
||
|
|
eovY: p.eovY,
|
||
|
|
eovX: p.eovX,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
rows.sort(
|
||
|
|
(a, b) => (a.channel ?? a.station).compareTo(b.channel ?? b.station));
|
||
|
|
|
||
|
|
return SpsImportPreview(
|
||
|
|
rows: rows,
|
||
|
|
totalReceiverPoints: receiverPoints.length,
|
||
|
|
totalRelations: relations.length,
|
||
|
|
unmatchedChannelCount: unmatchedChannels,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A jóváhagyott előnézetből SensorChannel lista építése (mentés előtt).
|
||
|
|
static List<SensorChannel> buildSensorChannels({
|
||
|
|
required SpsImportPreview preview,
|
||
|
|
required int projectId,
|
||
|
|
required String importBatch,
|
||
|
|
}) {
|
||
|
|
final conv = CoordConverterService.to;
|
||
|
|
final out = <SensorChannel>[];
|
||
|
|
for (final r in preview.rows) {
|
||
|
|
if (r.channel == null) continue; // csatornaszám nélkül nincs mit menteni
|
||
|
|
double? lat, lon;
|
||
|
|
if (r.eovY != null && r.eovX != null) {
|
||
|
|
final w = conv.eovToWgsPoint(r.eovY!, r.eovX!);
|
||
|
|
lon = w.x;
|
||
|
|
lat = w.y;
|
||
|
|
}
|
||
|
|
out.add(SensorChannel(
|
||
|
|
projectId: projectId,
|
||
|
|
channel: r.channel!,
|
||
|
|
lineId: r.lineId,
|
||
|
|
station: r.station,
|
||
|
|
planEovY: r.eovY,
|
||
|
|
planEovX: r.eovX,
|
||
|
|
planLat: lat,
|
||
|
|
planLon: lon,
|
||
|
|
source: 'sps',
|
||
|
|
importBatch: importBatch,
|
||
|
|
));
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═════════════════════════════════════════════════════════════════════
|
||
|
|
// Adatszerkezetek
|
||
|
|
// ═════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class SpsPointRecord {
|
||
|
|
final String lineId;
|
||
|
|
final int station;
|
||
|
|
final int pointIndex;
|
||
|
|
final double? eovY;
|
||
|
|
final double? eovX;
|
||
|
|
final double? elevation;
|
||
|
|
final String rawLine;
|
||
|
|
SpsPointRecord({
|
||
|
|
required this.lineId,
|
||
|
|
required this.station,
|
||
|
|
required this.pointIndex,
|
||
|
|
this.eovY,
|
||
|
|
this.eovX,
|
||
|
|
this.elevation,
|
||
|
|
required this.rawLine,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
class SpsRelationRecord {
|
||
|
|
final int fromChannel;
|
||
|
|
final int toChannel;
|
||
|
|
final int channelIncrement;
|
||
|
|
final String recvLineId;
|
||
|
|
final int fromReceiver;
|
||
|
|
final int toReceiver;
|
||
|
|
final String rawLine;
|
||
|
|
SpsRelationRecord({
|
||
|
|
required this.fromChannel,
|
||
|
|
required this.toChannel,
|
||
|
|
required this.channelIncrement,
|
||
|
|
required this.recvLineId,
|
||
|
|
required this.fromReceiver,
|
||
|
|
required this.toReceiver,
|
||
|
|
required this.rawLine,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Egy előnézeti sor — ez jelenik meg a felhasználónak import előtt.
|
||
|
|
class SpsPreviewRow {
|
||
|
|
final int? channel;
|
||
|
|
final String lineId;
|
||
|
|
final int station;
|
||
|
|
final double? eovY;
|
||
|
|
final double? eovX;
|
||
|
|
SpsPreviewRow({
|
||
|
|
required this.channel,
|
||
|
|
required this.lineId,
|
||
|
|
required this.station,
|
||
|
|
this.eovY,
|
||
|
|
this.eovX,
|
||
|
|
});
|
||
|
|
|
||
|
|
bool get hasPosition => eovY != null && eovX != null;
|
||
|
|
}
|
||
|
|
|
||
|
|
class SpsImportPreview {
|
||
|
|
final List<SpsPreviewRow> rows;
|
||
|
|
final int totalReceiverPoints;
|
||
|
|
final int totalRelations;
|
||
|
|
final int unmatchedChannelCount;
|
||
|
|
SpsImportPreview({
|
||
|
|
required this.rows,
|
||
|
|
required this.totalReceiverPoints,
|
||
|
|
required this.totalRelations,
|
||
|
|
required this.unmatchedChannelCount,
|
||
|
|
});
|
||
|
|
}
|