diff --git a/app/src/main/java/com/grigowashere/aismap/controllers/NMEAParser.java b/app/src/main/java/com/grigowashere/aismap/controllers/NMEAParser.java index 8d6a2a2..80f5770 100644 --- a/app/src/main/java/com/grigowashere/aismap/controllers/NMEAParser.java +++ b/app/src/main/java/com/grigowashere/aismap/controllers/NMEAParser.java @@ -1081,8 +1081,8 @@ public class NMEAParser { double length = dimRefA + dimRefB; double width = dimRefC + dimRefD; - // Draft (8 бит) - осадка - бит 296 - String draftBits = decodeAISField(payload, 296, 8); + // Draft (8 бит) - осадка - бит 294 + String draftBits = decodeAISField(payload, 294, 8); double draft = Integer.parseInt(draftBits, 2) / 10.0; Log.d(TAG, "Static Data - используем Dimension Reference поля (9, 9, 6, 6 бит):"); @@ -1094,8 +1094,8 @@ public class NMEAParser { Log.d(TAG, " Total Width (C+D): " + width + " м"); Log.d(TAG, " Draft: " + draftBits + " = " + draft + " м"); - // ETA (20 бит) - бит 294 - String etaBits = decodeAISField(payload, 294, 20); + // ETA (20 бит) - бит 274 + String etaBits = decodeAISField(payload, 274, 20); int eta = Integer.parseInt(etaBits, 2); Log.d(TAG, "ETA bits: " + etaBits + " = " + eta); @@ -1109,7 +1109,7 @@ public class NMEAParser { // Вычисляем доступную длину для оставшихся полей int totalBits = payload.length() * 6; - int remainingBits = totalBits - 314; // Остается после ETA + int remainingBits = totalBits - 294; // Остается после ETA Log.d(TAG, "Remaining bits after ETA: " + remainingBits + " (total: " + totalBits + ")"); String destination = ""; @@ -1117,81 +1117,18 @@ public class NMEAParser { String epfdDescription = "Unknown"; boolean dteReady = false; - // Для коротких сообщений (426 бит) используем упрощенную структуру - if (totalBits <= 426) { - // В коротких сообщениях может не быть всех полей - // Пробуем разные позиции для Destination - int[] possibleDestStarts = {302, 314, 320, 328}; - for (int destStartBit : possibleDestStarts) { - if (destStartBit + 120 <= totalBits) { - String destBits = decodeAISField(payload, destStartBit, 120); - String testDest = decodeAISString(destBits); - Log.d(TAG, "Пробуем Destination с бита " + destStartBit + ": " + testDest); - if (testDest.contains("DEFAULT") || testDest.contains("FAULT")) { - destination = testDest; - Log.d(TAG, "Найден Destination с бита " + destStartBit + ": '" + destination + "'"); - break; - } - } - } - - // Если не нашли, используем стандартную позицию - if (destination.isEmpty() && remainingBits > 0) { - int destStartBit = 314; - int destLength = Math.min(remainingBits, 120); - String destBits = decodeAISField(payload, destStartBit, destLength); - destination = decodeAISString(destBits); - Log.d(TAG, "Destination bits (fallback): " + destBits + " = '" + destination + "' (length: " + destLength + ")"); - } - } else { - // Для полных сообщений используем стандартную структуру - if (remainingBits >= 8) { - // Maximum present static draught (8 бит) - бит 314 - String draughtBits = decodeAISField(payload, 314, 8); - int draughtValue = Integer.parseInt(draughtBits, 2); - maxDraught = (draughtValue == 0) ? 0.0 : (draughtValue == 255) ? 25.5 : draughtValue / 10.0; - Log.d(TAG, "Max Draught bits: " + draughtBits + " = " + maxDraught + "m"); - remainingBits -= 8; - } - - if (remainingBits >= 4) { - // Type of electronic position fixing device (4 бита) - int epfdStartBit = 314 + 8; - String epfdBits = decodeAISField(payload, epfdStartBit, 4); - int epfdType = Integer.parseInt(epfdBits, 2); - epfdDescription = getEPFDType(epfdType); - Log.d(TAG, "EPFD Type bits: " + epfdBits + " = " + epfdType + " (" + epfdDescription + ")"); - remainingBits -= 4; - } - - if (remainingBits >= 1) { - // DTE (1 бит) - int dteStartBit = 314 + 8 + 4; - String dteBits = decodeAISField(payload, dteStartBit, 1); - dteReady = Integer.parseInt(dteBits, 2) == 0; - Log.d(TAG, "DTE bits: " + dteBits + " = " + dteReady + " (ready: " + dteReady + ")"); - remainingBits -= 1; - } - - if (remainingBits >= 1) { - // Spare (1 бит) - int spareStartBit = 314 + 8 + 4 + 1; - String spareBits = decodeAISField(payload, spareStartBit, 1); - int spare = Integer.parseInt(spareBits, 2); - Log.d(TAG, "Spare bits: " + spareBits + " = " + spare); - remainingBits -= 1; - } - - if (remainingBits > 0) { - // Destination (оставшиеся биты) - int destStartBit = 314 + 8 + 4 + 1 + 1; - int destLength = Math.min(remainingBits, 120); // Максимум 120 бит - String destBits = decodeAISField(payload, destStartBit, destLength); - destination = decodeAISString(destBits); - Log.d(TAG, "Destination bits (full): " + destBits + " = '" + destination + "' (length: " + destLength + ")"); - } else { - Log.w(TAG, "Destination поле недоступно - недостаточно битов"); - } + // Destination (120 бит) - бит 302 + if (totalBits >= 302 + 120) { + String destBits = decodeAISField(payload, 302, 120); + destination = decodeAISString(destBits); + Log.d(TAG, "Destination bits: " + destBits + " = '" + destination + "'"); + } else if (remainingBits > 0) { + // Если сообщение короткое, читаем доступные биты + int destStartBit = 302; + int destLength = Math.min(remainingBits, 120); + String destBits = decodeAISField(payload, destStartBit, destLength); + destination = decodeAISString(destBits); + Log.d(TAG, "Destination bits (short): " + destBits + " = '" + destination + "' (length: " + destLength + ")"); } Log.d(TAG, String.format("AIS Static: MMSI=%d, IMO=%d, name='%s', callSign='%s', type=%d, L=%.1f, W=%.1f, D=%.1f, maxD=%.1f, ETA=%s, EPFD=%s, DTE=%s, dest='%s'", @@ -1300,52 +1237,29 @@ public class NMEAParser { /** * Декодирует AIS строку */ + //TODO: Исправить на нормальный декодер строк private String decodeAISString(String bits) { StringBuilder result = new StringBuilder(); - Log.d(TAG, "Декодируем AIS строку из битов: " + bits + " (длина: " + bits.length() + ")"); - - for (int i = 0; i < bits.length(); i += 6) { - if (i + 6 <= bits.length()) { - String charBits = bits.substring(i, i + 6); - int value = Integer.parseInt(charBits, 2); - - char decodedChar; - Log.d(TAG, "Обрабатываем значение: " + value + " (биты: " + charBits + ")"); - - // Приоритет специальных случаев (пробелы) - if (value == 32 || value == 63) { - decodedChar = ' '; // Пробел - Log.d(TAG, "Найден пробел (" + value + ")"); - } else if (value >= 1 && value <= 26) { - // Заглавные буквы A-Z - decodedChar = (char)('A' + value - 1); - Log.d(TAG, "Диапазон A-Z: " + value + " -> " + decodedChar); - } else if (value >= 49 && value <= 58) { - // Цифры 1-9 (кастомное сопоставление на основе AIS1) - decodedChar = (char)('1' + (value - 49)); - Log.d(TAG, "Диапазон 1-9: " + value + " -> " + decodedChar); - } else if (value == 59) { - // Цифра 0 (кастомное сопоставление) - decodedChar = '0'; - Log.d(TAG, "Декодирован символ (59) -> '0'"); - } else if (value == 0) { - // Нулевое значение - конец строки, но не останавливаемся сразу - decodedChar = ' '; // Заменяем на пробел для продолжения - Log.d(TAG, "Найден ноль, заменяем на пробел"); - } else { - // Неизвестный или зарезервированный символ - decodedChar = '?'; - Log.w(TAG, "Неизвестное или зарезервированное значение AIS символа: " + value + " (биты: " + charBits + ")"); - } - - result.append(decodedChar); - Log.d(TAG, "Декодирован символ: " + charBits + " (" + value + ") -> '" + decodedChar + "'"); + + for (int i = 0; i + 6 <= bits.length(); i += 6) { + String charBits = bits.substring(i, i + 6); + int value = Integer.parseInt(charBits, 2); + + char decodedChar; + if (value == 0) { + decodedChar = ' '; // 0 = пробел + } else if (value >= 1 && value <= 26) { + decodedChar = (char) ('A' + value - 1); // 1..26 = A..Z + } else if (value >= 27 && value <= 36) { + decodedChar = (char) ('0' + (value - 27)); // 27..36 = 0..9 + } else { + decodedChar = ' '; // всё остальное = пробел } + + result.append(decodedChar); } - - String resultStr = result.toString().trim(); - Log.d(TAG, "Результат декодирования строки: '" + resultStr + "'"); - return resultStr; + + return result.toString().trim(); } /** diff --git a/app/src/main/java/com/grigowashere/aismap/view/CompassView.java b/app/src/main/java/com/grigowashere/aismap/view/CompassView.java index 832261e..4a630b4 100644 --- a/app/src/main/java/com/grigowashere/aismap/view/CompassView.java +++ b/app/src/main/java/com/grigowashere/aismap/view/CompassView.java @@ -69,11 +69,27 @@ public class CompassView extends BaseDockWidget { } private float getShortestRotation(float start, float end) { + // Нормализуем углы к диапазону 0-360 + start = normalizeAngle(start); + end = normalizeAngle(end); + float diff = end - start; - while (diff > 180) diff -= 360; - while (diff < -180) diff += 360; + + // Если разность больше 180°, идем в обратную сторону + if (diff > 180) { + diff -= 360; + } else if (diff < -180) { + diff += 360; + } + return diff; } + + private float normalizeAngle(float angle) { + while (angle < 0) angle += 360; + while (angle >= 360) angle -= 360; + return angle; + } @@ -109,9 +125,11 @@ public class CompassView extends BaseDockWidget { // Плавное обновление азимута float diff = getShortestRotation(currentAzimuth, targetAzimuth); if (Math.abs(diff) > 0.1f) { - currentAzimuth += diff * SMOOTHING_FACTOR; - if (currentAzimuth > 360) currentAzimuth -= 360; - if (currentAzimuth < 0) currentAzimuth += 360; + // Ограничиваем максимальное изменение за один кадр + float maxChange = 3.0f; // максимальное изменение в градусах за кадр + float change = Math.signum(diff) * Math.min(Math.abs(diff * SMOOTHING_FACTOR), maxChange); + currentAzimuth += change; + currentAzimuth = normalizeAngle(currentAzimuth); postInvalidateOnAnimation(); } @@ -123,8 +141,7 @@ public class CompassView extends BaseDockWidget { // Рисуем деления шкалы for (int degree = 0; degree < 360; degree += 15) { // Вычисляем относительное положение деления - float relativeDegree = (degree - currentAzimuth + 360) % 360; - if (relativeDegree > 180) relativeDegree -= 360; + float relativeDegree = getShortestRotation(currentAzimuth, degree); // Рисуем только видимые деления if (Math.abs(relativeDegree) <= visibleDegrees / 2) { @@ -149,8 +166,7 @@ public class CompassView extends BaseDockWidget { // Рисуем суда for (AISVessel vessel : nearbyVessels) { - float relativeBearing = (float) ((vessel.getCourse() - currentAzimuth + 360) % 360); - if (relativeBearing > 180) relativeBearing -= 360; + float relativeBearing = getShortestRotation(currentAzimuth, (float) vessel.getCourse()); if (Math.abs(relativeBearing) <= visibleDegrees / 2) { float x = centerX + (relativeBearing / (visibleDegrees / 2)) * (w / 2); double distance = ourVessel != null ? GeoUtils.calculateDistance(ourVessel, vessel) : 0; @@ -218,9 +234,11 @@ public class CompassView extends BaseDockWidget { // Плавное обновление азимута float diff = getShortestRotation(currentAzimuth, targetAzimuth); if (Math.abs(diff) > 0.1f) { - currentAzimuth += diff * SMOOTHING_FACTOR; - if (currentAzimuth > 360) currentAzimuth -= 360; - if (currentAzimuth < 0) currentAzimuth += 360; + // Ограничиваем максимальное изменение за один кадр + float maxChange = 3.0f; // максимальное изменение в градусах за кадр + float change = Math.signum(diff) * Math.min(Math.abs(diff * SMOOTHING_FACTOR), maxChange); + currentAzimuth += change; + currentAzimuth = normalizeAngle(currentAzimuth); postInvalidateOnAnimation(); } @@ -246,7 +264,7 @@ public class CompassView extends BaseDockWidget { // Рисуем суда по кругу for (AISVessel vessel : nearbyVessels) { - float bearing = (float) ((vessel.getCourse() - currentAzimuth + 360) % 360); + float bearing = getShortestRotation(currentAzimuth, (float) vessel.getCourse()); float angle = (float) Math.toRadians(bearing); float vesselRadius = radius * 0.6f; float vx = cx + (float) Math.sin(angle) * vesselRadius; @@ -313,7 +331,25 @@ public class CompassView extends BaseDockWidget { } public void setAzimuth(float azimuth) { - this.targetAzimuth = azimuth; + // Проверяем на валидность азимута + if (Float.isNaN(azimuth) || Float.isInfinite(azimuth)) { + return; // Игнорируем невалидные значения + } + + // Нормализуем входящий азимут + this.targetAzimuth = normalizeAngle(azimuth); + + // Если текущий азимут еще не инициализирован, устанавливаем его сразу + if (currentAzimuth == 0 && targetAzimuth != 0) { + currentAzimuth = targetAzimuth; + } + + // Специальная обработка для 0° - если текущий азимут близок к 360°, + // то 0° должен интерпретироваться как 360° + if (targetAzimuth == 0 && currentAzimuth > 350) { + this.targetAzimuth = 360; + } + invalidate(); }