diff --git a/README.md b/README.md deleted file mode 100644 index c4a9f02d..00000000 --- a/README.md +++ /dev/null @@ -1,45 +0,0 @@ -**PositionMe** is an indoor positioning data collection application initially developed for the University of Edinburgh's Embedded Wireless course. The application now includes enhanced features, including **trajectory playback**, improved UI design, and comprehensive location tracking. - -## Features - -- **Real-time Sensor Data Collection**: Captures sensor, location, and GNSS data. -- **Trajectory Playback**: Simulates recorded movement from previously saved trajectory files (Trajectory proto files). -- **Interactive Map Display**: - - Visualizes the user's **PDR trajectory/path**. - - Displays **received GNSS locations**. - - Supports **floor changes and indoor maps** for a seamless experience. -- **Playback Controls**: - - **Play/Pause, Exit, Restart, Jump to End**. - - **Progress bar for tracking playback status**. -- **Redesigned UI**: Modern and user-friendly interface for enhanced usability. - -## Requirements - -- **Android Studio 4.2** or later -- **Android SDK 30** or later - -## Installation - -1. **Clone the repository.** -2. **Open the project in Android Studio**. -3. Add your own API key for Google Maps in AndroidManifest.xml -4. Set the website where you want to send your data. The application was built for use with [openpositioning.org](http://openpositioning.org/). -5. **Build and run the project on your Android device**. - -## Usage - -1. **Install the application** using Android Studio. -2. **Launch the application** on your Android device. -3. **Grant necessary permissions** when prompted: - - Sensor access - - Location services - - Internet connectivity -4. **Collect real-time positioning data**: - - Follow on-screen instructions to record sensor data. -5. **Replay previously recorded trajectories**: - - Navigate to the **Files** section. - - Select a saved trajectory and press **Play**. - - The recorded trajectory will be simulated and displayed on the map. -6. **Control playback**: - - Pause, restart, or jump to the end using playback controls. - diff --git a/app/build.gradle b/app/build.gradle index 3e29b13f..f27cba0a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ plugins { id 'com.google.gms.google-services' id 'androidx.navigation.safeargs' id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' + id 'com.google.protobuf' } // (Optional) load local secrets file: @@ -32,6 +33,9 @@ android { "\"${localProperties['OPENPOSITIONING_API_KEY'] ?: ''}\"" buildConfigField "String", "OPENPOSITIONING_MASTER_KEY", "\"${localProperties['OPENPOSITIONING_MASTER_KEY'] ?: ''}\"" + + //I ADD + resValue "string", "google_maps_key", localProperties.getProperty("MAPS_API_KEY", "") } buildFeatures { @@ -57,6 +61,19 @@ android { } } +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.21.12' + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { } + } + } + } +} + dependencies { // Core AndroidX implementation 'androidx.appcompat:appcompat:1.7.0-alpha03' // or stable: 1.6.1 @@ -73,11 +90,12 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' implementation 'com.google.android.material:material:1.12.0' - implementation 'com.google.protobuf:protobuf-java:3.0.0' + implementation 'com.google.protobuf:protobuf-java:3.21.12' implementation 'com.squareup.okhttp3:okhttp:4.10.0' - implementation "com.google.protobuf:protobuf-java-util:3.0.0" + implementation "com.google.protobuf:protobuf-java-util:3.21.12" implementation "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" implementation 'com.google.android.gms:play-services-maps:19.0.0' + implementation 'org.ejml:ejml-simple:0.43.1' // Navigation components def nav_version = "2.8.6" @@ -92,4 +110,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} + implementation 'com.google.android.gms:play-services-location:21.0.1' +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 678711fd..e56319eb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,10 +7,15 @@ If you truly require these sensors, keep `required="true"`. Otherwise, consider marking them as `required="false"`. --> - - - - + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - + android:theme="@style/Theme.Material3.DayNight.NoActionBar"> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/Traj.java b/app/src/main/java/com/openpositioning/PositionMe/Traj.java deleted file mode 100644 index 7925fa55..00000000 --- a/app/src/main/java/com/openpositioning/PositionMe/Traj.java +++ /dev/null @@ -1,12664 +0,0 @@ -package com.openpositioning.PositionMe;// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: Cloud/app/src/main/proto/traj.proto - -public final class Traj { - private Traj() {} - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistryLite registry) { - } - - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistry registry) { - registerAllExtensions( - (com.google.protobuf.ExtensionRegistryLite) registry); - } - public interface TrajectoryOrBuilder extends - // @@protoc_insertion_point(interface_extends:Trajectory) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string android_version = 1; - */ - String getAndroidVersion(); - /** - * optional string android_version = 1; - */ - com.google.protobuf.ByteString - getAndroidVersionBytes(); - - /** - * repeated .Motion_Sample imu_data = 2; - */ - java.util.List - getImuDataList(); - /** - * repeated .Motion_Sample imu_data = 2; - */ - Motion_Sample getImuData(int index); - /** - * repeated .Motion_Sample imu_data = 2; - */ - int getImuDataCount(); - /** - * repeated .Motion_Sample imu_data = 2; - */ - java.util.List - getImuDataOrBuilderList(); - /** - * repeated .Motion_Sample imu_data = 2; - */ - Motion_SampleOrBuilder getImuDataOrBuilder( - int index); - - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - java.util.List - getPdrDataList(); - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - Pdr_Sample getPdrData(int index); - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - int getPdrDataCount(); - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - java.util.List - getPdrDataOrBuilderList(); - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - Pdr_SampleOrBuilder getPdrDataOrBuilder( - int index); - - /** - * repeated .Position_Sample position_data = 4; - */ - java.util.List - getPositionDataList(); - /** - * repeated .Position_Sample position_data = 4; - */ - Position_Sample getPositionData(int index); - /** - * repeated .Position_Sample position_data = 4; - */ - int getPositionDataCount(); - /** - * repeated .Position_Sample position_data = 4; - */ - java.util.List - getPositionDataOrBuilderList(); - /** - * repeated .Position_Sample position_data = 4; - */ - Position_SampleOrBuilder getPositionDataOrBuilder( - int index); - - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - java.util.List - getPressureDataList(); - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - Pressure_Sample getPressureData(int index); - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - int getPressureDataCount(); - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - java.util.List - getPressureDataOrBuilderList(); - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - Pressure_SampleOrBuilder getPressureDataOrBuilder( - int index); - - /** - * repeated .Light_Sample light_data = 6; - */ - java.util.List - getLightDataList(); - /** - * repeated .Light_Sample light_data = 6; - */ - Light_Sample getLightData(int index); - /** - * repeated .Light_Sample light_data = 6; - */ - int getLightDataCount(); - /** - * repeated .Light_Sample light_data = 6; - */ - java.util.List - getLightDataOrBuilderList(); - /** - * repeated .Light_Sample light_data = 6; - */ - Light_SampleOrBuilder getLightDataOrBuilder( - int index); - - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - java.util.List - getGnssDataList(); - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - GNSS_Sample getGnssData(int index); - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - int getGnssDataCount(); - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - java.util.List - getGnssDataOrBuilderList(); - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - GNSS_SampleOrBuilder getGnssDataOrBuilder( - int index); - - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - java.util.List - getWifiDataList(); - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - WiFi_Sample getWifiData(int index); - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - int getWifiDataCount(); - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - java.util.List - getWifiDataOrBuilderList(); - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - WiFi_SampleOrBuilder getWifiDataOrBuilder( - int index); - - /** - * repeated .AP_Data aps_data = 9; - */ - java.util.List - getApsDataList(); - /** - * repeated .AP_Data aps_data = 9; - */ - AP_Data getApsData(int index); - /** - * repeated .AP_Data aps_data = 9; - */ - int getApsDataCount(); - /** - * repeated .AP_Data aps_data = 9; - */ - java.util.List - getApsDataOrBuilderList(); - /** - * repeated .AP_Data aps_data = 9; - */ - AP_DataOrBuilder getApsDataOrBuilder( - int index); - - /** - *
-     * UNIX timestamp (in milliseconds) recorded from the start of this
-     * trajectory data collection event. All future
-     * timestamps in sub classes are to be RELATIVE timestamps
-     * (in milliseconds) to this start time.
-     * E.g.
-     * start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning)
-     * relative_timestamp = 3000 (3s)
-     * 
- * - * optional int64 start_timestamp = 10; - */ - long getStartTimestamp(); - - /** - * optional string data_identifier = 11; - */ - String getDataIdentifier(); - /** - * optional string data_identifier = 11; - */ - com.google.protobuf.ByteString - getDataIdentifierBytes(); - - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - boolean hasAccelerometerInfo(); - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - Sensor_Info getAccelerometerInfo(); - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - Sensor_InfoOrBuilder getAccelerometerInfoOrBuilder(); - - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - boolean hasGyroscopeInfo(); - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - Sensor_Info getGyroscopeInfo(); - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - Sensor_InfoOrBuilder getGyroscopeInfoOrBuilder(); - - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - boolean hasRotationVectorInfo(); - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - Sensor_Info getRotationVectorInfo(); - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - Sensor_InfoOrBuilder getRotationVectorInfoOrBuilder(); - - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - boolean hasMagnetometerInfo(); - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - Sensor_Info getMagnetometerInfo(); - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - Sensor_InfoOrBuilder getMagnetometerInfoOrBuilder(); - - /** - * optional .Sensor_Info barometer_info = 16; - */ - boolean hasBarometerInfo(); - /** - * optional .Sensor_Info barometer_info = 16; - */ - Sensor_Info getBarometerInfo(); - /** - * optional .Sensor_Info barometer_info = 16; - */ - Sensor_InfoOrBuilder getBarometerInfoOrBuilder(); - - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - boolean hasLightSensorInfo(); - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - Sensor_Info getLightSensorInfo(); - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - Sensor_InfoOrBuilder getLightSensorInfoOrBuilder(); - } - /** - * Protobuf type {@code Trajectory} - */ - public static final class Trajectory extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Trajectory) - TrajectoryOrBuilder { - // Use Trajectory.newBuilder() to construct. - private Trajectory(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Trajectory() { - androidVersion_ = ""; - imuData_ = java.util.Collections.emptyList(); - pdrData_ = java.util.Collections.emptyList(); - positionData_ = java.util.Collections.emptyList(); - pressureData_ = java.util.Collections.emptyList(); - lightData_ = java.util.Collections.emptyList(); - gnssData_ = java.util.Collections.emptyList(); - wifiData_ = java.util.Collections.emptyList(); - apsData_ = java.util.Collections.emptyList(); - startTimestamp_ = 0L; - dataIdentifier_ = ""; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Trajectory( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 10: { - String s = input.readStringRequireUtf8(); - - androidVersion_ = s; - break; - } - case 18: { - if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - imuData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000002; - } - imuData_.add( - input.readMessage(Motion_Sample.parser(), extensionRegistry)); - break; - } - case 26: { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - pdrData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - pdrData_.add( - input.readMessage(Pdr_Sample.parser(), extensionRegistry)); - break; - } - case 34: { - if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - positionData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000008; - } - positionData_.add( - input.readMessage(Position_Sample.parser(), extensionRegistry)); - break; - } - case 42: { - if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) { - pressureData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000010; - } - pressureData_.add( - input.readMessage(Pressure_Sample.parser(), extensionRegistry)); - break; - } - case 50: { - if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - lightData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000020; - } - lightData_.add( - input.readMessage(Light_Sample.parser(), extensionRegistry)); - break; - } - case 58: { - if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) { - gnssData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000040; - } - gnssData_.add( - input.readMessage(GNSS_Sample.parser(), extensionRegistry)); - break; - } - case 66: { - if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) { - wifiData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000080; - } - wifiData_.add( - input.readMessage(WiFi_Sample.parser(), extensionRegistry)); - break; - } - case 74: { - if (!((mutable_bitField0_ & 0x00000100) == 0x00000100)) { - apsData_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000100; - } - apsData_.add( - input.readMessage(AP_Data.parser(), extensionRegistry)); - break; - } - case 80: { - - startTimestamp_ = input.readInt64(); - break; - } - case 90: { - String s = input.readStringRequireUtf8(); - - dataIdentifier_ = s; - break; - } - case 98: { - Sensor_Info.Builder subBuilder = null; - if (accelerometerInfo_ != null) { - subBuilder = accelerometerInfo_.toBuilder(); - } - accelerometerInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(accelerometerInfo_); - accelerometerInfo_ = subBuilder.buildPartial(); - } - - break; - } - case 106: { - Sensor_Info.Builder subBuilder = null; - if (gyroscopeInfo_ != null) { - subBuilder = gyroscopeInfo_.toBuilder(); - } - gyroscopeInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(gyroscopeInfo_); - gyroscopeInfo_ = subBuilder.buildPartial(); - } - - break; - } - case 114: { - Sensor_Info.Builder subBuilder = null; - if (rotationVectorInfo_ != null) { - subBuilder = rotationVectorInfo_.toBuilder(); - } - rotationVectorInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(rotationVectorInfo_); - rotationVectorInfo_ = subBuilder.buildPartial(); - } - - break; - } - case 122: { - Sensor_Info.Builder subBuilder = null; - if (magnetometerInfo_ != null) { - subBuilder = magnetometerInfo_.toBuilder(); - } - magnetometerInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(magnetometerInfo_); - magnetometerInfo_ = subBuilder.buildPartial(); - } - - break; - } - case 130: { - Sensor_Info.Builder subBuilder = null; - if (barometerInfo_ != null) { - subBuilder = barometerInfo_.toBuilder(); - } - barometerInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(barometerInfo_); - barometerInfo_ = subBuilder.buildPartial(); - } - - break; - } - case 138: { - Sensor_Info.Builder subBuilder = null; - if (lightSensorInfo_ != null) { - subBuilder = lightSensorInfo_.toBuilder(); - } - lightSensorInfo_ = input.readMessage(Sensor_Info.parser(), extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(lightSensorInfo_); - lightSensorInfo_ = subBuilder.buildPartial(); - } - - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - imuData_ = java.util.Collections.unmodifiableList(imuData_); - } - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - pdrData_ = java.util.Collections.unmodifiableList(pdrData_); - } - if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - positionData_ = java.util.Collections.unmodifiableList(positionData_); - } - if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) { - pressureData_ = java.util.Collections.unmodifiableList(pressureData_); - } - if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - lightData_ = java.util.Collections.unmodifiableList(lightData_); - } - if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) { - gnssData_ = java.util.Collections.unmodifiableList(gnssData_); - } - if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) { - wifiData_ = java.util.Collections.unmodifiableList(wifiData_); - } - if (((mutable_bitField0_ & 0x00000100) == 0x00000100)) { - apsData_ = java.util.Collections.unmodifiableList(apsData_); - } - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Trajectory_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Trajectory_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Trajectory.class, Builder.class); - } - - private int bitField0_; - public static final int ANDROID_VERSION_FIELD_NUMBER = 1; - private volatile Object androidVersion_; - /** - * optional string android_version = 1; - */ - public String getAndroidVersion() { - Object ref = androidVersion_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - androidVersion_ = s; - return s; - } - } - /** - * optional string android_version = 1; - */ - public com.google.protobuf.ByteString - getAndroidVersionBytes() { - Object ref = androidVersion_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - androidVersion_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int IMU_DATA_FIELD_NUMBER = 2; - private java.util.List imuData_; - /** - * repeated .Motion_Sample imu_data = 2; - */ - public java.util.List getImuDataList() { - return imuData_; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public java.util.List - getImuDataOrBuilderList() { - return imuData_; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public int getImuDataCount() { - return imuData_.size(); - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_Sample getImuData(int index) { - return imuData_.get(index); - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_SampleOrBuilder getImuDataOrBuilder( - int index) { - return imuData_.get(index); - } - - public static final int PDR_DATA_FIELD_NUMBER = 3; - private java.util.List pdrData_; - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public java.util.List getPdrDataList() { - return pdrData_; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public java.util.List - getPdrDataOrBuilderList() { - return pdrData_; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public int getPdrDataCount() { - return pdrData_.size(); - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_Sample getPdrData(int index) { - return pdrData_.get(index); - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_SampleOrBuilder getPdrDataOrBuilder( - int index) { - return pdrData_.get(index); - } - - public static final int POSITION_DATA_FIELD_NUMBER = 4; - private java.util.List positionData_; - /** - * repeated .Position_Sample position_data = 4; - */ - public java.util.List getPositionDataList() { - return positionData_; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public java.util.List - getPositionDataOrBuilderList() { - return positionData_; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public int getPositionDataCount() { - return positionData_.size(); - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_Sample getPositionData(int index) { - return positionData_.get(index); - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_SampleOrBuilder getPositionDataOrBuilder( - int index) { - return positionData_.get(index); - } - - public static final int PRESSURE_DATA_FIELD_NUMBER = 5; - private java.util.List pressureData_; - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public java.util.List getPressureDataList() { - return pressureData_; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public java.util.List - getPressureDataOrBuilderList() { - return pressureData_; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public int getPressureDataCount() { - return pressureData_.size(); - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_Sample getPressureData(int index) { - return pressureData_.get(index); - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_SampleOrBuilder getPressureDataOrBuilder( - int index) { - return pressureData_.get(index); - } - - public static final int LIGHT_DATA_FIELD_NUMBER = 6; - private java.util.List lightData_; - /** - * repeated .Light_Sample light_data = 6; - */ - public java.util.List getLightDataList() { - return lightData_; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public java.util.List - getLightDataOrBuilderList() { - return lightData_; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public int getLightDataCount() { - return lightData_.size(); - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_Sample getLightData(int index) { - return lightData_.get(index); - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_SampleOrBuilder getLightDataOrBuilder( - int index) { - return lightData_.get(index); - } - - public static final int GNSS_DATA_FIELD_NUMBER = 7; - private java.util.List gnssData_; - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public java.util.List getGnssDataList() { - return gnssData_; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public java.util.List - getGnssDataOrBuilderList() { - return gnssData_; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public int getGnssDataCount() { - return gnssData_.size(); - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_Sample getGnssData(int index) { - return gnssData_.get(index); - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_SampleOrBuilder getGnssDataOrBuilder( - int index) { - return gnssData_.get(index); - } - - public static final int WIFI_DATA_FIELD_NUMBER = 8; - private java.util.List wifiData_; - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public java.util.List getWifiDataList() { - return wifiData_; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public java.util.List - getWifiDataOrBuilderList() { - return wifiData_; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public int getWifiDataCount() { - return wifiData_.size(); - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_Sample getWifiData(int index) { - return wifiData_.get(index); - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_SampleOrBuilder getWifiDataOrBuilder( - int index) { - return wifiData_.get(index); - } - - public static final int APS_DATA_FIELD_NUMBER = 9; - private java.util.List apsData_; - /** - * repeated .AP_Data aps_data = 9; - */ - public java.util.List getApsDataList() { - return apsData_; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public java.util.List - getApsDataOrBuilderList() { - return apsData_; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public int getApsDataCount() { - return apsData_.size(); - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_Data getApsData(int index) { - return apsData_.get(index); - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_DataOrBuilder getApsDataOrBuilder( - int index) { - return apsData_.get(index); - } - - public static final int START_TIMESTAMP_FIELD_NUMBER = 10; - private long startTimestamp_; - /** - *
-     * UNIX timestamp (in milliseconds) recorded from the start of this
-     * trajectory data collection event. All future
-     * timestamps in sub classes are to be RELATIVE timestamps
-     * (in milliseconds) to this start time.
-     * E.g.
-     * start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning)
-     * relative_timestamp = 3000 (3s)
-     * 
- * - * optional int64 start_timestamp = 10; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - - public static final int DATA_IDENTIFIER_FIELD_NUMBER = 11; - private volatile Object dataIdentifier_; - /** - * optional string data_identifier = 11; - */ - public String getDataIdentifier() { - Object ref = dataIdentifier_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - dataIdentifier_ = s; - return s; - } - } - /** - * optional string data_identifier = 11; - */ - public com.google.protobuf.ByteString - getDataIdentifierBytes() { - Object ref = dataIdentifier_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - dataIdentifier_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ACCELEROMETER_INFO_FIELD_NUMBER = 12; - private Sensor_Info accelerometerInfo_; - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public boolean hasAccelerometerInfo() { - return accelerometerInfo_ != null; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Sensor_Info getAccelerometerInfo() { - return accelerometerInfo_ == null ? Sensor_Info.getDefaultInstance() : accelerometerInfo_; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Sensor_InfoOrBuilder getAccelerometerInfoOrBuilder() { - return getAccelerometerInfo(); - } - - public static final int GYROSCOPE_INFO_FIELD_NUMBER = 13; - private Sensor_Info gyroscopeInfo_; - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public boolean hasGyroscopeInfo() { - return gyroscopeInfo_ != null; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Sensor_Info getGyroscopeInfo() { - return gyroscopeInfo_ == null ? Sensor_Info.getDefaultInstance() : gyroscopeInfo_; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Sensor_InfoOrBuilder getGyroscopeInfoOrBuilder() { - return getGyroscopeInfo(); - } - - public static final int ROTATION_VECTOR_INFO_FIELD_NUMBER = 14; - private Sensor_Info rotationVectorInfo_; - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public boolean hasRotationVectorInfo() { - return rotationVectorInfo_ != null; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Sensor_Info getRotationVectorInfo() { - return rotationVectorInfo_ == null ? Sensor_Info.getDefaultInstance() : rotationVectorInfo_; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Sensor_InfoOrBuilder getRotationVectorInfoOrBuilder() { - return getRotationVectorInfo(); - } - - public static final int MAGNETOMETER_INFO_FIELD_NUMBER = 15; - private Sensor_Info magnetometerInfo_; - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public boolean hasMagnetometerInfo() { - return magnetometerInfo_ != null; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Sensor_Info getMagnetometerInfo() { - return magnetometerInfo_ == null ? Sensor_Info.getDefaultInstance() : magnetometerInfo_; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Sensor_InfoOrBuilder getMagnetometerInfoOrBuilder() { - return getMagnetometerInfo(); - } - - public static final int BAROMETER_INFO_FIELD_NUMBER = 16; - private Sensor_Info barometerInfo_; - /** - * optional .Sensor_Info barometer_info = 16; - */ - public boolean hasBarometerInfo() { - return barometerInfo_ != null; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Sensor_Info getBarometerInfo() { - return barometerInfo_ == null ? Sensor_Info.getDefaultInstance() : barometerInfo_; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Sensor_InfoOrBuilder getBarometerInfoOrBuilder() { - return getBarometerInfo(); - } - - public static final int LIGHT_SENSOR_INFO_FIELD_NUMBER = 17; - private Sensor_Info lightSensorInfo_; - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public boolean hasLightSensorInfo() { - return lightSensorInfo_ != null; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Sensor_Info getLightSensorInfo() { - return lightSensorInfo_ == null ? Sensor_Info.getDefaultInstance() : lightSensorInfo_; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Sensor_InfoOrBuilder getLightSensorInfoOrBuilder() { - return getLightSensorInfo(); - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (!getAndroidVersionBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 1, androidVersion_); - } - for (int i = 0; i < imuData_.size(); i++) { - output.writeMessage(2, imuData_.get(i)); - } - for (int i = 0; i < pdrData_.size(); i++) { - output.writeMessage(3, pdrData_.get(i)); - } - for (int i = 0; i < positionData_.size(); i++) { - output.writeMessage(4, positionData_.get(i)); - } - for (int i = 0; i < pressureData_.size(); i++) { - output.writeMessage(5, pressureData_.get(i)); - } - for (int i = 0; i < lightData_.size(); i++) { - output.writeMessage(6, lightData_.get(i)); - } - for (int i = 0; i < gnssData_.size(); i++) { - output.writeMessage(7, gnssData_.get(i)); - } - for (int i = 0; i < wifiData_.size(); i++) { - output.writeMessage(8, wifiData_.get(i)); - } - for (int i = 0; i < apsData_.size(); i++) { - output.writeMessage(9, apsData_.get(i)); - } - if (startTimestamp_ != 0L) { - output.writeInt64(10, startTimestamp_); - } - if (!getDataIdentifierBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 11, dataIdentifier_); - } - if (accelerometerInfo_ != null) { - output.writeMessage(12, getAccelerometerInfo()); - } - if (gyroscopeInfo_ != null) { - output.writeMessage(13, getGyroscopeInfo()); - } - if (rotationVectorInfo_ != null) { - output.writeMessage(14, getRotationVectorInfo()); - } - if (magnetometerInfo_ != null) { - output.writeMessage(15, getMagnetometerInfo()); - } - if (barometerInfo_ != null) { - output.writeMessage(16, getBarometerInfo()); - } - if (lightSensorInfo_ != null) { - output.writeMessage(17, getLightSensorInfo()); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (!getAndroidVersionBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, androidVersion_); - } - for (int i = 0; i < imuData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, imuData_.get(i)); - } - for (int i = 0; i < pdrData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, pdrData_.get(i)); - } - for (int i = 0; i < positionData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(4, positionData_.get(i)); - } - for (int i = 0; i < pressureData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(5, pressureData_.get(i)); - } - for (int i = 0; i < lightData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(6, lightData_.get(i)); - } - for (int i = 0; i < gnssData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(7, gnssData_.get(i)); - } - for (int i = 0; i < wifiData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(8, wifiData_.get(i)); - } - for (int i = 0; i < apsData_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(9, apsData_.get(i)); - } - if (startTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(10, startTimestamp_); - } - if (!getDataIdentifierBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(11, dataIdentifier_); - } - if (accelerometerInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(12, getAccelerometerInfo()); - } - if (gyroscopeInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(13, getGyroscopeInfo()); - } - if (rotationVectorInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(14, getRotationVectorInfo()); - } - if (magnetometerInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(15, getMagnetometerInfo()); - } - if (barometerInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(16, getBarometerInfo()); - } - if (lightSensorInfo_ != null) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(17, getLightSensorInfo()); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Trajectory)) { - return super.equals(obj); - } - Trajectory other = (Trajectory) obj; - - boolean result = true; - result = result && getAndroidVersion() - .equals(other.getAndroidVersion()); - result = result && getImuDataList() - .equals(other.getImuDataList()); - result = result && getPdrDataList() - .equals(other.getPdrDataList()); - result = result && getPositionDataList() - .equals(other.getPositionDataList()); - result = result && getPressureDataList() - .equals(other.getPressureDataList()); - result = result && getLightDataList() - .equals(other.getLightDataList()); - result = result && getGnssDataList() - .equals(other.getGnssDataList()); - result = result && getWifiDataList() - .equals(other.getWifiDataList()); - result = result && getApsDataList() - .equals(other.getApsDataList()); - result = result && (getStartTimestamp() - == other.getStartTimestamp()); - result = result && getDataIdentifier() - .equals(other.getDataIdentifier()); - result = result && (hasAccelerometerInfo() == other.hasAccelerometerInfo()); - if (hasAccelerometerInfo()) { - result = result && getAccelerometerInfo() - .equals(other.getAccelerometerInfo()); - } - result = result && (hasGyroscopeInfo() == other.hasGyroscopeInfo()); - if (hasGyroscopeInfo()) { - result = result && getGyroscopeInfo() - .equals(other.getGyroscopeInfo()); - } - result = result && (hasRotationVectorInfo() == other.hasRotationVectorInfo()); - if (hasRotationVectorInfo()) { - result = result && getRotationVectorInfo() - .equals(other.getRotationVectorInfo()); - } - result = result && (hasMagnetometerInfo() == other.hasMagnetometerInfo()); - if (hasMagnetometerInfo()) { - result = result && getMagnetometerInfo() - .equals(other.getMagnetometerInfo()); - } - result = result && (hasBarometerInfo() == other.hasBarometerInfo()); - if (hasBarometerInfo()) { - result = result && getBarometerInfo() - .equals(other.getBarometerInfo()); - } - result = result && (hasLightSensorInfo() == other.hasLightSensorInfo()); - if (hasLightSensorInfo()) { - result = result && getLightSensorInfo() - .equals(other.getLightSensorInfo()); - } - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + ANDROID_VERSION_FIELD_NUMBER; - hash = (53 * hash) + getAndroidVersion().hashCode(); - if (getImuDataCount() > 0) { - hash = (37 * hash) + IMU_DATA_FIELD_NUMBER; - hash = (53 * hash) + getImuDataList().hashCode(); - } - if (getPdrDataCount() > 0) { - hash = (37 * hash) + PDR_DATA_FIELD_NUMBER; - hash = (53 * hash) + getPdrDataList().hashCode(); - } - if (getPositionDataCount() > 0) { - hash = (37 * hash) + POSITION_DATA_FIELD_NUMBER; - hash = (53 * hash) + getPositionDataList().hashCode(); - } - if (getPressureDataCount() > 0) { - hash = (37 * hash) + PRESSURE_DATA_FIELD_NUMBER; - hash = (53 * hash) + getPressureDataList().hashCode(); - } - if (getLightDataCount() > 0) { - hash = (37 * hash) + LIGHT_DATA_FIELD_NUMBER; - hash = (53 * hash) + getLightDataList().hashCode(); - } - if (getGnssDataCount() > 0) { - hash = (37 * hash) + GNSS_DATA_FIELD_NUMBER; - hash = (53 * hash) + getGnssDataList().hashCode(); - } - if (getWifiDataCount() > 0) { - hash = (37 * hash) + WIFI_DATA_FIELD_NUMBER; - hash = (53 * hash) + getWifiDataList().hashCode(); - } - if (getApsDataCount() > 0) { - hash = (37 * hash) + APS_DATA_FIELD_NUMBER; - hash = (53 * hash) + getApsDataList().hashCode(); - } - hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getStartTimestamp()); - hash = (37 * hash) + DATA_IDENTIFIER_FIELD_NUMBER; - hash = (53 * hash) + getDataIdentifier().hashCode(); - if (hasAccelerometerInfo()) { - hash = (37 * hash) + ACCELEROMETER_INFO_FIELD_NUMBER; - hash = (53 * hash) + getAccelerometerInfo().hashCode(); - } - if (hasGyroscopeInfo()) { - hash = (37 * hash) + GYROSCOPE_INFO_FIELD_NUMBER; - hash = (53 * hash) + getGyroscopeInfo().hashCode(); - } - if (hasRotationVectorInfo()) { - hash = (37 * hash) + ROTATION_VECTOR_INFO_FIELD_NUMBER; - hash = (53 * hash) + getRotationVectorInfo().hashCode(); - } - if (hasMagnetometerInfo()) { - hash = (37 * hash) + MAGNETOMETER_INFO_FIELD_NUMBER; - hash = (53 * hash) + getMagnetometerInfo().hashCode(); - } - if (hasBarometerInfo()) { - hash = (37 * hash) + BAROMETER_INFO_FIELD_NUMBER; - hash = (53 * hash) + getBarometerInfo().hashCode(); - } - if (hasLightSensorInfo()) { - hash = (37 * hash) + LIGHT_SENSOR_INFO_FIELD_NUMBER; - hash = (53 * hash) + getLightSensorInfo().hashCode(); - } - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Trajectory parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Trajectory parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Trajectory parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Trajectory parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Trajectory parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Trajectory parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Trajectory parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Trajectory parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Trajectory parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Trajectory parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Trajectory prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Trajectory} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Trajectory) - TrajectoryOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Trajectory_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Trajectory_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Trajectory.class, Builder.class); - } - - // Construct using Traj.Trajectory.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - getImuDataFieldBuilder(); - getPdrDataFieldBuilder(); - getPositionDataFieldBuilder(); - getPressureDataFieldBuilder(); - getLightDataFieldBuilder(); - getGnssDataFieldBuilder(); - getWifiDataFieldBuilder(); - getApsDataFieldBuilder(); - } - } - public Builder clear() { - super.clear(); - androidVersion_ = ""; - - if (imuDataBuilder_ == null) { - imuData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - } else { - imuDataBuilder_.clear(); - } - if (pdrDataBuilder_ == null) { - pdrData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - } else { - pdrDataBuilder_.clear(); - } - if (positionDataBuilder_ == null) { - positionData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); - } else { - positionDataBuilder_.clear(); - } - if (pressureDataBuilder_ == null) { - pressureData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); - } else { - pressureDataBuilder_.clear(); - } - if (lightDataBuilder_ == null) { - lightData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); - } else { - lightDataBuilder_.clear(); - } - if (gnssDataBuilder_ == null) { - gnssData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000040); - } else { - gnssDataBuilder_.clear(); - } - if (wifiDataBuilder_ == null) { - wifiData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000080); - } else { - wifiDataBuilder_.clear(); - } - if (apsDataBuilder_ == null) { - apsData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000100); - } else { - apsDataBuilder_.clear(); - } - startTimestamp_ = 0L; - - dataIdentifier_ = ""; - - if (accelerometerInfoBuilder_ == null) { - accelerometerInfo_ = null; - } else { - accelerometerInfo_ = null; - accelerometerInfoBuilder_ = null; - } - if (gyroscopeInfoBuilder_ == null) { - gyroscopeInfo_ = null; - } else { - gyroscopeInfo_ = null; - gyroscopeInfoBuilder_ = null; - } - if (rotationVectorInfoBuilder_ == null) { - rotationVectorInfo_ = null; - } else { - rotationVectorInfo_ = null; - rotationVectorInfoBuilder_ = null; - } - if (magnetometerInfoBuilder_ == null) { - magnetometerInfo_ = null; - } else { - magnetometerInfo_ = null; - magnetometerInfoBuilder_ = null; - } - if (barometerInfoBuilder_ == null) { - barometerInfo_ = null; - } else { - barometerInfo_ = null; - barometerInfoBuilder_ = null; - } - if (lightSensorInfoBuilder_ == null) { - lightSensorInfo_ = null; - } else { - lightSensorInfo_ = null; - lightSensorInfoBuilder_ = null; - } - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Trajectory_descriptor; - } - - public Trajectory getDefaultInstanceForType() { - return Trajectory.getDefaultInstance(); - } - - public Trajectory build() { - Trajectory result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Trajectory buildPartial() { - Trajectory result = new Trajectory(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - result.androidVersion_ = androidVersion_; - if (imuDataBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002)) { - imuData_ = java.util.Collections.unmodifiableList(imuData_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.imuData_ = imuData_; - } else { - result.imuData_ = imuDataBuilder_.build(); - } - if (pdrDataBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004)) { - pdrData_ = java.util.Collections.unmodifiableList(pdrData_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.pdrData_ = pdrData_; - } else { - result.pdrData_ = pdrDataBuilder_.build(); - } - if (positionDataBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008)) { - positionData_ = java.util.Collections.unmodifiableList(positionData_); - bitField0_ = (bitField0_ & ~0x00000008); - } - result.positionData_ = positionData_; - } else { - result.positionData_ = positionDataBuilder_.build(); - } - if (pressureDataBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010)) { - pressureData_ = java.util.Collections.unmodifiableList(pressureData_); - bitField0_ = (bitField0_ & ~0x00000010); - } - result.pressureData_ = pressureData_; - } else { - result.pressureData_ = pressureDataBuilder_.build(); - } - if (lightDataBuilder_ == null) { - if (((bitField0_ & 0x00000020) == 0x00000020)) { - lightData_ = java.util.Collections.unmodifiableList(lightData_); - bitField0_ = (bitField0_ & ~0x00000020); - } - result.lightData_ = lightData_; - } else { - result.lightData_ = lightDataBuilder_.build(); - } - if (gnssDataBuilder_ == null) { - if (((bitField0_ & 0x00000040) == 0x00000040)) { - gnssData_ = java.util.Collections.unmodifiableList(gnssData_); - bitField0_ = (bitField0_ & ~0x00000040); - } - result.gnssData_ = gnssData_; - } else { - result.gnssData_ = gnssDataBuilder_.build(); - } - if (wifiDataBuilder_ == null) { - if (((bitField0_ & 0x00000080) == 0x00000080)) { - wifiData_ = java.util.Collections.unmodifiableList(wifiData_); - bitField0_ = (bitField0_ & ~0x00000080); - } - result.wifiData_ = wifiData_; - } else { - result.wifiData_ = wifiDataBuilder_.build(); - } - if (apsDataBuilder_ == null) { - if (((bitField0_ & 0x00000100) == 0x00000100)) { - apsData_ = java.util.Collections.unmodifiableList(apsData_); - bitField0_ = (bitField0_ & ~0x00000100); - } - result.apsData_ = apsData_; - } else { - result.apsData_ = apsDataBuilder_.build(); - } - result.startTimestamp_ = startTimestamp_; - result.dataIdentifier_ = dataIdentifier_; - if (accelerometerInfoBuilder_ == null) { - result.accelerometerInfo_ = accelerometerInfo_; - } else { - result.accelerometerInfo_ = accelerometerInfoBuilder_.build(); - } - if (gyroscopeInfoBuilder_ == null) { - result.gyroscopeInfo_ = gyroscopeInfo_; - } else { - result.gyroscopeInfo_ = gyroscopeInfoBuilder_.build(); - } - if (rotationVectorInfoBuilder_ == null) { - result.rotationVectorInfo_ = rotationVectorInfo_; - } else { - result.rotationVectorInfo_ = rotationVectorInfoBuilder_.build(); - } - if (magnetometerInfoBuilder_ == null) { - result.magnetometerInfo_ = magnetometerInfo_; - } else { - result.magnetometerInfo_ = magnetometerInfoBuilder_.build(); - } - if (barometerInfoBuilder_ == null) { - result.barometerInfo_ = barometerInfo_; - } else { - result.barometerInfo_ = barometerInfoBuilder_.build(); - } - if (lightSensorInfoBuilder_ == null) { - result.lightSensorInfo_ = lightSensorInfo_; - } else { - result.lightSensorInfo_ = lightSensorInfoBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Trajectory) { - return mergeFrom((Trajectory)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Trajectory other) { - if (other == Trajectory.getDefaultInstance()) return this; - if (!other.getAndroidVersion().isEmpty()) { - androidVersion_ = other.androidVersion_; - onChanged(); - } - if (imuDataBuilder_ == null) { - if (!other.imuData_.isEmpty()) { - if (imuData_.isEmpty()) { - imuData_ = other.imuData_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureImuDataIsMutable(); - imuData_.addAll(other.imuData_); - } - onChanged(); - } - } else { - if (!other.imuData_.isEmpty()) { - if (imuDataBuilder_.isEmpty()) { - imuDataBuilder_.dispose(); - imuDataBuilder_ = null; - imuData_ = other.imuData_; - bitField0_ = (bitField0_ & ~0x00000002); - imuDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getImuDataFieldBuilder() : null; - } else { - imuDataBuilder_.addAllMessages(other.imuData_); - } - } - } - if (pdrDataBuilder_ == null) { - if (!other.pdrData_.isEmpty()) { - if (pdrData_.isEmpty()) { - pdrData_ = other.pdrData_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensurePdrDataIsMutable(); - pdrData_.addAll(other.pdrData_); - } - onChanged(); - } - } else { - if (!other.pdrData_.isEmpty()) { - if (pdrDataBuilder_.isEmpty()) { - pdrDataBuilder_.dispose(); - pdrDataBuilder_ = null; - pdrData_ = other.pdrData_; - bitField0_ = (bitField0_ & ~0x00000004); - pdrDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getPdrDataFieldBuilder() : null; - } else { - pdrDataBuilder_.addAllMessages(other.pdrData_); - } - } - } - if (positionDataBuilder_ == null) { - if (!other.positionData_.isEmpty()) { - if (positionData_.isEmpty()) { - positionData_ = other.positionData_; - bitField0_ = (bitField0_ & ~0x00000008); - } else { - ensurePositionDataIsMutable(); - positionData_.addAll(other.positionData_); - } - onChanged(); - } - } else { - if (!other.positionData_.isEmpty()) { - if (positionDataBuilder_.isEmpty()) { - positionDataBuilder_.dispose(); - positionDataBuilder_ = null; - positionData_ = other.positionData_; - bitField0_ = (bitField0_ & ~0x00000008); - positionDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getPositionDataFieldBuilder() : null; - } else { - positionDataBuilder_.addAllMessages(other.positionData_); - } - } - } - if (pressureDataBuilder_ == null) { - if (!other.pressureData_.isEmpty()) { - if (pressureData_.isEmpty()) { - pressureData_ = other.pressureData_; - bitField0_ = (bitField0_ & ~0x00000010); - } else { - ensurePressureDataIsMutable(); - pressureData_.addAll(other.pressureData_); - } - onChanged(); - } - } else { - if (!other.pressureData_.isEmpty()) { - if (pressureDataBuilder_.isEmpty()) { - pressureDataBuilder_.dispose(); - pressureDataBuilder_ = null; - pressureData_ = other.pressureData_; - bitField0_ = (bitField0_ & ~0x00000010); - pressureDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getPressureDataFieldBuilder() : null; - } else { - pressureDataBuilder_.addAllMessages(other.pressureData_); - } - } - } - if (lightDataBuilder_ == null) { - if (!other.lightData_.isEmpty()) { - if (lightData_.isEmpty()) { - lightData_ = other.lightData_; - bitField0_ = (bitField0_ & ~0x00000020); - } else { - ensureLightDataIsMutable(); - lightData_.addAll(other.lightData_); - } - onChanged(); - } - } else { - if (!other.lightData_.isEmpty()) { - if (lightDataBuilder_.isEmpty()) { - lightDataBuilder_.dispose(); - lightDataBuilder_ = null; - lightData_ = other.lightData_; - bitField0_ = (bitField0_ & ~0x00000020); - lightDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getLightDataFieldBuilder() : null; - } else { - lightDataBuilder_.addAllMessages(other.lightData_); - } - } - } - if (gnssDataBuilder_ == null) { - if (!other.gnssData_.isEmpty()) { - if (gnssData_.isEmpty()) { - gnssData_ = other.gnssData_; - bitField0_ = (bitField0_ & ~0x00000040); - } else { - ensureGnssDataIsMutable(); - gnssData_.addAll(other.gnssData_); - } - onChanged(); - } - } else { - if (!other.gnssData_.isEmpty()) { - if (gnssDataBuilder_.isEmpty()) { - gnssDataBuilder_.dispose(); - gnssDataBuilder_ = null; - gnssData_ = other.gnssData_; - bitField0_ = (bitField0_ & ~0x00000040); - gnssDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getGnssDataFieldBuilder() : null; - } else { - gnssDataBuilder_.addAllMessages(other.gnssData_); - } - } - } - if (wifiDataBuilder_ == null) { - if (!other.wifiData_.isEmpty()) { - if (wifiData_.isEmpty()) { - wifiData_ = other.wifiData_; - bitField0_ = (bitField0_ & ~0x00000080); - } else { - ensureWifiDataIsMutable(); - wifiData_.addAll(other.wifiData_); - } - onChanged(); - } - } else { - if (!other.wifiData_.isEmpty()) { - if (wifiDataBuilder_.isEmpty()) { - wifiDataBuilder_.dispose(); - wifiDataBuilder_ = null; - wifiData_ = other.wifiData_; - bitField0_ = (bitField0_ & ~0x00000080); - wifiDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getWifiDataFieldBuilder() : null; - } else { - wifiDataBuilder_.addAllMessages(other.wifiData_); - } - } - } - if (apsDataBuilder_ == null) { - if (!other.apsData_.isEmpty()) { - if (apsData_.isEmpty()) { - apsData_ = other.apsData_; - bitField0_ = (bitField0_ & ~0x00000100); - } else { - ensureApsDataIsMutable(); - apsData_.addAll(other.apsData_); - } - onChanged(); - } - } else { - if (!other.apsData_.isEmpty()) { - if (apsDataBuilder_.isEmpty()) { - apsDataBuilder_.dispose(); - apsDataBuilder_ = null; - apsData_ = other.apsData_; - bitField0_ = (bitField0_ & ~0x00000100); - apsDataBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getApsDataFieldBuilder() : null; - } else { - apsDataBuilder_.addAllMessages(other.apsData_); - } - } - } - if (other.getStartTimestamp() != 0L) { - setStartTimestamp(other.getStartTimestamp()); - } - if (!other.getDataIdentifier().isEmpty()) { - dataIdentifier_ = other.dataIdentifier_; - onChanged(); - } - if (other.hasAccelerometerInfo()) { - mergeAccelerometerInfo(other.getAccelerometerInfo()); - } - if (other.hasGyroscopeInfo()) { - mergeGyroscopeInfo(other.getGyroscopeInfo()); - } - if (other.hasRotationVectorInfo()) { - mergeRotationVectorInfo(other.getRotationVectorInfo()); - } - if (other.hasMagnetometerInfo()) { - mergeMagnetometerInfo(other.getMagnetometerInfo()); - } - if (other.hasBarometerInfo()) { - mergeBarometerInfo(other.getBarometerInfo()); - } - if (other.hasLightSensorInfo()) { - mergeLightSensorInfo(other.getLightSensorInfo()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Trajectory parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Trajectory) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - private Object androidVersion_ = ""; - /** - * optional string android_version = 1; - */ - public String getAndroidVersion() { - Object ref = androidVersion_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - androidVersion_ = s; - return s; - } else { - return (String) ref; - } - } - /** - * optional string android_version = 1; - */ - public com.google.protobuf.ByteString - getAndroidVersionBytes() { - Object ref = androidVersion_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - androidVersion_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string android_version = 1; - */ - public Builder setAndroidVersion( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - androidVersion_ = value; - onChanged(); - return this; - } - /** - * optional string android_version = 1; - */ - public Builder clearAndroidVersion() { - - androidVersion_ = getDefaultInstance().getAndroidVersion(); - onChanged(); - return this; - } - /** - * optional string android_version = 1; - */ - public Builder setAndroidVersionBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - androidVersion_ = value; - onChanged(); - return this; - } - - private java.util.List imuData_ = - java.util.Collections.emptyList(); - private void ensureImuDataIsMutable() { - if (!((bitField0_ & 0x00000002) == 0x00000002)) { - imuData_ = new java.util.ArrayList(imuData_); - bitField0_ |= 0x00000002; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Motion_Sample, Motion_Sample.Builder, Motion_SampleOrBuilder> imuDataBuilder_; - - /** - * repeated .Motion_Sample imu_data = 2; - */ - public java.util.List getImuDataList() { - if (imuDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(imuData_); - } else { - return imuDataBuilder_.getMessageList(); - } - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public int getImuDataCount() { - if (imuDataBuilder_ == null) { - return imuData_.size(); - } else { - return imuDataBuilder_.getCount(); - } - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_Sample getImuData(int index) { - if (imuDataBuilder_ == null) { - return imuData_.get(index); - } else { - return imuDataBuilder_.getMessage(index); - } - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder setImuData( - int index, Motion_Sample value) { - if (imuDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureImuDataIsMutable(); - imuData_.set(index, value); - onChanged(); - } else { - imuDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder setImuData( - int index, Motion_Sample.Builder builderForValue) { - if (imuDataBuilder_ == null) { - ensureImuDataIsMutable(); - imuData_.set(index, builderForValue.build()); - onChanged(); - } else { - imuDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder addImuData(Motion_Sample value) { - if (imuDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureImuDataIsMutable(); - imuData_.add(value); - onChanged(); - } else { - imuDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder addImuData( - int index, Motion_Sample value) { - if (imuDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureImuDataIsMutable(); - imuData_.add(index, value); - onChanged(); - } else { - imuDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder addImuData( - Motion_Sample.Builder builderForValue) { - if (imuDataBuilder_ == null) { - ensureImuDataIsMutable(); - imuData_.add(builderForValue.build()); - onChanged(); - } else { - imuDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder addImuData( - int index, Motion_Sample.Builder builderForValue) { - if (imuDataBuilder_ == null) { - ensureImuDataIsMutable(); - imuData_.add(index, builderForValue.build()); - onChanged(); - } else { - imuDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder addAllImuData( - Iterable values) { - if (imuDataBuilder_ == null) { - ensureImuDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, imuData_); - onChanged(); - } else { - imuDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder clearImuData() { - if (imuDataBuilder_ == null) { - imuData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - } else { - imuDataBuilder_.clear(); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Builder removeImuData(int index) { - if (imuDataBuilder_ == null) { - ensureImuDataIsMutable(); - imuData_.remove(index); - onChanged(); - } else { - imuDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_Sample.Builder getImuDataBuilder( - int index) { - return getImuDataFieldBuilder().getBuilder(index); - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_SampleOrBuilder getImuDataOrBuilder( - int index) { - if (imuDataBuilder_ == null) { - return imuData_.get(index); } else { - return imuDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public java.util.List - getImuDataOrBuilderList() { - if (imuDataBuilder_ != null) { - return imuDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(imuData_); - } - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_Sample.Builder addImuDataBuilder() { - return getImuDataFieldBuilder().addBuilder( - Motion_Sample.getDefaultInstance()); - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public Motion_Sample.Builder addImuDataBuilder( - int index) { - return getImuDataFieldBuilder().addBuilder( - index, Motion_Sample.getDefaultInstance()); - } - /** - * repeated .Motion_Sample imu_data = 2; - */ - public java.util.List - getImuDataBuilderList() { - return getImuDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Motion_Sample, Motion_Sample.Builder, Motion_SampleOrBuilder> - getImuDataFieldBuilder() { - if (imuDataBuilder_ == null) { - imuDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Motion_Sample, Motion_Sample.Builder, Motion_SampleOrBuilder>( - imuData_, - ((bitField0_ & 0x00000002) == 0x00000002), - getParentForChildren(), - isClean()); - imuData_ = null; - } - return imuDataBuilder_; - } - - private java.util.List pdrData_ = - java.util.Collections.emptyList(); - private void ensurePdrDataIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - pdrData_ = new java.util.ArrayList(pdrData_); - bitField0_ |= 0x00000004; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Pdr_Sample, Pdr_Sample.Builder, Pdr_SampleOrBuilder> pdrDataBuilder_; - - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public java.util.List getPdrDataList() { - if (pdrDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(pdrData_); - } else { - return pdrDataBuilder_.getMessageList(); - } - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public int getPdrDataCount() { - if (pdrDataBuilder_ == null) { - return pdrData_.size(); - } else { - return pdrDataBuilder_.getCount(); - } - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_Sample getPdrData(int index) { - if (pdrDataBuilder_ == null) { - return pdrData_.get(index); - } else { - return pdrDataBuilder_.getMessage(index); - } - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder setPdrData( - int index, Pdr_Sample value) { - if (pdrDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePdrDataIsMutable(); - pdrData_.set(index, value); - onChanged(); - } else { - pdrDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder setPdrData( - int index, Pdr_Sample.Builder builderForValue) { - if (pdrDataBuilder_ == null) { - ensurePdrDataIsMutable(); - pdrData_.set(index, builderForValue.build()); - onChanged(); - } else { - pdrDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder addPdrData(Pdr_Sample value) { - if (pdrDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePdrDataIsMutable(); - pdrData_.add(value); - onChanged(); - } else { - pdrDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder addPdrData( - int index, Pdr_Sample value) { - if (pdrDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePdrDataIsMutable(); - pdrData_.add(index, value); - onChanged(); - } else { - pdrDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder addPdrData( - Pdr_Sample.Builder builderForValue) { - if (pdrDataBuilder_ == null) { - ensurePdrDataIsMutable(); - pdrData_.add(builderForValue.build()); - onChanged(); - } else { - pdrDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder addPdrData( - int index, Pdr_Sample.Builder builderForValue) { - if (pdrDataBuilder_ == null) { - ensurePdrDataIsMutable(); - pdrData_.add(index, builderForValue.build()); - onChanged(); - } else { - pdrDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder addAllPdrData( - Iterable values) { - if (pdrDataBuilder_ == null) { - ensurePdrDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, pdrData_); - onChanged(); - } else { - pdrDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder clearPdrData() { - if (pdrDataBuilder_ == null) { - pdrData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - } else { - pdrDataBuilder_.clear(); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Builder removePdrData(int index) { - if (pdrDataBuilder_ == null) { - ensurePdrDataIsMutable(); - pdrData_.remove(index); - onChanged(); - } else { - pdrDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_Sample.Builder getPdrDataBuilder( - int index) { - return getPdrDataFieldBuilder().getBuilder(index); - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_SampleOrBuilder getPdrDataOrBuilder( - int index) { - if (pdrDataBuilder_ == null) { - return pdrData_.get(index); } else { - return pdrDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public java.util.List - getPdrDataOrBuilderList() { - if (pdrDataBuilder_ != null) { - return pdrDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(pdrData_); - } - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_Sample.Builder addPdrDataBuilder() { - return getPdrDataFieldBuilder().addBuilder( - Pdr_Sample.getDefaultInstance()); - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public Pdr_Sample.Builder addPdrDataBuilder( - int index) { - return getPdrDataFieldBuilder().addBuilder( - index, Pdr_Sample.getDefaultInstance()); - } - /** - * repeated .Pdr_Sample pdr_data = 3; - */ - public java.util.List - getPdrDataBuilderList() { - return getPdrDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Pdr_Sample, Pdr_Sample.Builder, Pdr_SampleOrBuilder> - getPdrDataFieldBuilder() { - if (pdrDataBuilder_ == null) { - pdrDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Pdr_Sample, Pdr_Sample.Builder, Pdr_SampleOrBuilder>( - pdrData_, - ((bitField0_ & 0x00000004) == 0x00000004), - getParentForChildren(), - isClean()); - pdrData_ = null; - } - return pdrDataBuilder_; - } - - private java.util.List positionData_ = - java.util.Collections.emptyList(); - private void ensurePositionDataIsMutable() { - if (!((bitField0_ & 0x00000008) == 0x00000008)) { - positionData_ = new java.util.ArrayList(positionData_); - bitField0_ |= 0x00000008; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Position_Sample, Position_Sample.Builder, Position_SampleOrBuilder> positionDataBuilder_; - - /** - * repeated .Position_Sample position_data = 4; - */ - public java.util.List getPositionDataList() { - if (positionDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(positionData_); - } else { - return positionDataBuilder_.getMessageList(); - } - } - /** - * repeated .Position_Sample position_data = 4; - */ - public int getPositionDataCount() { - if (positionDataBuilder_ == null) { - return positionData_.size(); - } else { - return positionDataBuilder_.getCount(); - } - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_Sample getPositionData(int index) { - if (positionDataBuilder_ == null) { - return positionData_.get(index); - } else { - return positionDataBuilder_.getMessage(index); - } - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder setPositionData( - int index, Position_Sample value) { - if (positionDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePositionDataIsMutable(); - positionData_.set(index, value); - onChanged(); - } else { - positionDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder setPositionData( - int index, Position_Sample.Builder builderForValue) { - if (positionDataBuilder_ == null) { - ensurePositionDataIsMutable(); - positionData_.set(index, builderForValue.build()); - onChanged(); - } else { - positionDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder addPositionData(Position_Sample value) { - if (positionDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePositionDataIsMutable(); - positionData_.add(value); - onChanged(); - } else { - positionDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder addPositionData( - int index, Position_Sample value) { - if (positionDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePositionDataIsMutable(); - positionData_.add(index, value); - onChanged(); - } else { - positionDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder addPositionData( - Position_Sample.Builder builderForValue) { - if (positionDataBuilder_ == null) { - ensurePositionDataIsMutable(); - positionData_.add(builderForValue.build()); - onChanged(); - } else { - positionDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder addPositionData( - int index, Position_Sample.Builder builderForValue) { - if (positionDataBuilder_ == null) { - ensurePositionDataIsMutable(); - positionData_.add(index, builderForValue.build()); - onChanged(); - } else { - positionDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder addAllPositionData( - Iterable values) { - if (positionDataBuilder_ == null) { - ensurePositionDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, positionData_); - onChanged(); - } else { - positionDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder clearPositionData() { - if (positionDataBuilder_ == null) { - positionData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); - } else { - positionDataBuilder_.clear(); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Builder removePositionData(int index) { - if (positionDataBuilder_ == null) { - ensurePositionDataIsMutable(); - positionData_.remove(index); - onChanged(); - } else { - positionDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_Sample.Builder getPositionDataBuilder( - int index) { - return getPositionDataFieldBuilder().getBuilder(index); - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_SampleOrBuilder getPositionDataOrBuilder( - int index) { - if (positionDataBuilder_ == null) { - return positionData_.get(index); } else { - return positionDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Position_Sample position_data = 4; - */ - public java.util.List - getPositionDataOrBuilderList() { - if (positionDataBuilder_ != null) { - return positionDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(positionData_); - } - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_Sample.Builder addPositionDataBuilder() { - return getPositionDataFieldBuilder().addBuilder( - Position_Sample.getDefaultInstance()); - } - /** - * repeated .Position_Sample position_data = 4; - */ - public Position_Sample.Builder addPositionDataBuilder( - int index) { - return getPositionDataFieldBuilder().addBuilder( - index, Position_Sample.getDefaultInstance()); - } - /** - * repeated .Position_Sample position_data = 4; - */ - public java.util.List - getPositionDataBuilderList() { - return getPositionDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Position_Sample, Position_Sample.Builder, Position_SampleOrBuilder> - getPositionDataFieldBuilder() { - if (positionDataBuilder_ == null) { - positionDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Position_Sample, Position_Sample.Builder, Position_SampleOrBuilder>( - positionData_, - ((bitField0_ & 0x00000008) == 0x00000008), - getParentForChildren(), - isClean()); - positionData_ = null; - } - return positionDataBuilder_; - } - - private java.util.List pressureData_ = - java.util.Collections.emptyList(); - private void ensurePressureDataIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { - pressureData_ = new java.util.ArrayList(pressureData_); - bitField0_ |= 0x00000010; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Pressure_Sample, Pressure_Sample.Builder, Pressure_SampleOrBuilder> pressureDataBuilder_; - - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public java.util.List getPressureDataList() { - if (pressureDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(pressureData_); - } else { - return pressureDataBuilder_.getMessageList(); - } - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public int getPressureDataCount() { - if (pressureDataBuilder_ == null) { - return pressureData_.size(); - } else { - return pressureDataBuilder_.getCount(); - } - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_Sample getPressureData(int index) { - if (pressureDataBuilder_ == null) { - return pressureData_.get(index); - } else { - return pressureDataBuilder_.getMessage(index); - } - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder setPressureData( - int index, Pressure_Sample value) { - if (pressureDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePressureDataIsMutable(); - pressureData_.set(index, value); - onChanged(); - } else { - pressureDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder setPressureData( - int index, Pressure_Sample.Builder builderForValue) { - if (pressureDataBuilder_ == null) { - ensurePressureDataIsMutable(); - pressureData_.set(index, builderForValue.build()); - onChanged(); - } else { - pressureDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder addPressureData(Pressure_Sample value) { - if (pressureDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePressureDataIsMutable(); - pressureData_.add(value); - onChanged(); - } else { - pressureDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder addPressureData( - int index, Pressure_Sample value) { - if (pressureDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePressureDataIsMutable(); - pressureData_.add(index, value); - onChanged(); - } else { - pressureDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder addPressureData( - Pressure_Sample.Builder builderForValue) { - if (pressureDataBuilder_ == null) { - ensurePressureDataIsMutable(); - pressureData_.add(builderForValue.build()); - onChanged(); - } else { - pressureDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder addPressureData( - int index, Pressure_Sample.Builder builderForValue) { - if (pressureDataBuilder_ == null) { - ensurePressureDataIsMutable(); - pressureData_.add(index, builderForValue.build()); - onChanged(); - } else { - pressureDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder addAllPressureData( - Iterable values) { - if (pressureDataBuilder_ == null) { - ensurePressureDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, pressureData_); - onChanged(); - } else { - pressureDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder clearPressureData() { - if (pressureDataBuilder_ == null) { - pressureData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); - onChanged(); - } else { - pressureDataBuilder_.clear(); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Builder removePressureData(int index) { - if (pressureDataBuilder_ == null) { - ensurePressureDataIsMutable(); - pressureData_.remove(index); - onChanged(); - } else { - pressureDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_Sample.Builder getPressureDataBuilder( - int index) { - return getPressureDataFieldBuilder().getBuilder(index); - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_SampleOrBuilder getPressureDataOrBuilder( - int index) { - if (pressureDataBuilder_ == null) { - return pressureData_.get(index); } else { - return pressureDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public java.util.List - getPressureDataOrBuilderList() { - if (pressureDataBuilder_ != null) { - return pressureDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(pressureData_); - } - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_Sample.Builder addPressureDataBuilder() { - return getPressureDataFieldBuilder().addBuilder( - Pressure_Sample.getDefaultInstance()); - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public Pressure_Sample.Builder addPressureDataBuilder( - int index) { - return getPressureDataFieldBuilder().addBuilder( - index, Pressure_Sample.getDefaultInstance()); - } - /** - * repeated .Pressure_Sample pressure_data = 5; - */ - public java.util.List - getPressureDataBuilderList() { - return getPressureDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Pressure_Sample, Pressure_Sample.Builder, Pressure_SampleOrBuilder> - getPressureDataFieldBuilder() { - if (pressureDataBuilder_ == null) { - pressureDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Pressure_Sample, Pressure_Sample.Builder, Pressure_SampleOrBuilder>( - pressureData_, - ((bitField0_ & 0x00000010) == 0x00000010), - getParentForChildren(), - isClean()); - pressureData_ = null; - } - return pressureDataBuilder_; - } - - private java.util.List lightData_ = - java.util.Collections.emptyList(); - private void ensureLightDataIsMutable() { - if (!((bitField0_ & 0x00000020) == 0x00000020)) { - lightData_ = new java.util.ArrayList(lightData_); - bitField0_ |= 0x00000020; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Light_Sample, Light_Sample.Builder, Light_SampleOrBuilder> lightDataBuilder_; - - /** - * repeated .Light_Sample light_data = 6; - */ - public java.util.List getLightDataList() { - if (lightDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(lightData_); - } else { - return lightDataBuilder_.getMessageList(); - } - } - /** - * repeated .Light_Sample light_data = 6; - */ - public int getLightDataCount() { - if (lightDataBuilder_ == null) { - return lightData_.size(); - } else { - return lightDataBuilder_.getCount(); - } - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_Sample getLightData(int index) { - if (lightDataBuilder_ == null) { - return lightData_.get(index); - } else { - return lightDataBuilder_.getMessage(index); - } - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder setLightData( - int index, Light_Sample value) { - if (lightDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureLightDataIsMutable(); - lightData_.set(index, value); - onChanged(); - } else { - lightDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder setLightData( - int index, Light_Sample.Builder builderForValue) { - if (lightDataBuilder_ == null) { - ensureLightDataIsMutable(); - lightData_.set(index, builderForValue.build()); - onChanged(); - } else { - lightDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder addLightData(Light_Sample value) { - if (lightDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureLightDataIsMutable(); - lightData_.add(value); - onChanged(); - } else { - lightDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder addLightData( - int index, Light_Sample value) { - if (lightDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureLightDataIsMutable(); - lightData_.add(index, value); - onChanged(); - } else { - lightDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder addLightData( - Light_Sample.Builder builderForValue) { - if (lightDataBuilder_ == null) { - ensureLightDataIsMutable(); - lightData_.add(builderForValue.build()); - onChanged(); - } else { - lightDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder addLightData( - int index, Light_Sample.Builder builderForValue) { - if (lightDataBuilder_ == null) { - ensureLightDataIsMutable(); - lightData_.add(index, builderForValue.build()); - onChanged(); - } else { - lightDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder addAllLightData( - Iterable values) { - if (lightDataBuilder_ == null) { - ensureLightDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, lightData_); - onChanged(); - } else { - lightDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder clearLightData() { - if (lightDataBuilder_ == null) { - lightData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); - onChanged(); - } else { - lightDataBuilder_.clear(); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Builder removeLightData(int index) { - if (lightDataBuilder_ == null) { - ensureLightDataIsMutable(); - lightData_.remove(index); - onChanged(); - } else { - lightDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_Sample.Builder getLightDataBuilder( - int index) { - return getLightDataFieldBuilder().getBuilder(index); - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_SampleOrBuilder getLightDataOrBuilder( - int index) { - if (lightDataBuilder_ == null) { - return lightData_.get(index); } else { - return lightDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Light_Sample light_data = 6; - */ - public java.util.List - getLightDataOrBuilderList() { - if (lightDataBuilder_ != null) { - return lightDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(lightData_); - } - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_Sample.Builder addLightDataBuilder() { - return getLightDataFieldBuilder().addBuilder( - Light_Sample.getDefaultInstance()); - } - /** - * repeated .Light_Sample light_data = 6; - */ - public Light_Sample.Builder addLightDataBuilder( - int index) { - return getLightDataFieldBuilder().addBuilder( - index, Light_Sample.getDefaultInstance()); - } - /** - * repeated .Light_Sample light_data = 6; - */ - public java.util.List - getLightDataBuilderList() { - return getLightDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Light_Sample, Light_Sample.Builder, Light_SampleOrBuilder> - getLightDataFieldBuilder() { - if (lightDataBuilder_ == null) { - lightDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Light_Sample, Light_Sample.Builder, Light_SampleOrBuilder>( - lightData_, - ((bitField0_ & 0x00000020) == 0x00000020), - getParentForChildren(), - isClean()); - lightData_ = null; - } - return lightDataBuilder_; - } - - private java.util.List gnssData_ = - java.util.Collections.emptyList(); - private void ensureGnssDataIsMutable() { - if (!((bitField0_ & 0x00000040) == 0x00000040)) { - gnssData_ = new java.util.ArrayList(gnssData_); - bitField0_ |= 0x00000040; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - GNSS_Sample, GNSS_Sample.Builder, GNSS_SampleOrBuilder> gnssDataBuilder_; - - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public java.util.List getGnssDataList() { - if (gnssDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(gnssData_); - } else { - return gnssDataBuilder_.getMessageList(); - } - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public int getGnssDataCount() { - if (gnssDataBuilder_ == null) { - return gnssData_.size(); - } else { - return gnssDataBuilder_.getCount(); - } - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_Sample getGnssData(int index) { - if (gnssDataBuilder_ == null) { - return gnssData_.get(index); - } else { - return gnssDataBuilder_.getMessage(index); - } - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder setGnssData( - int index, GNSS_Sample value) { - if (gnssDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureGnssDataIsMutable(); - gnssData_.set(index, value); - onChanged(); - } else { - gnssDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder setGnssData( - int index, GNSS_Sample.Builder builderForValue) { - if (gnssDataBuilder_ == null) { - ensureGnssDataIsMutable(); - gnssData_.set(index, builderForValue.build()); - onChanged(); - } else { - gnssDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder addGnssData(GNSS_Sample value) { - if (gnssDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureGnssDataIsMutable(); - gnssData_.add(value); - onChanged(); - } else { - gnssDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder addGnssData( - int index, GNSS_Sample value) { - if (gnssDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureGnssDataIsMutable(); - gnssData_.add(index, value); - onChanged(); - } else { - gnssDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder addGnssData( - GNSS_Sample.Builder builderForValue) { - if (gnssDataBuilder_ == null) { - ensureGnssDataIsMutable(); - gnssData_.add(builderForValue.build()); - onChanged(); - } else { - gnssDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder addGnssData( - int index, GNSS_Sample.Builder builderForValue) { - if (gnssDataBuilder_ == null) { - ensureGnssDataIsMutable(); - gnssData_.add(index, builderForValue.build()); - onChanged(); - } else { - gnssDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder addAllGnssData( - Iterable values) { - if (gnssDataBuilder_ == null) { - ensureGnssDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, gnssData_); - onChanged(); - } else { - gnssDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder clearGnssData() { - if (gnssDataBuilder_ == null) { - gnssData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000040); - onChanged(); - } else { - gnssDataBuilder_.clear(); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public Builder removeGnssData(int index) { - if (gnssDataBuilder_ == null) { - ensureGnssDataIsMutable(); - gnssData_.remove(index); - onChanged(); - } else { - gnssDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_Sample.Builder getGnssDataBuilder( - int index) { - return getGnssDataFieldBuilder().getBuilder(index); - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_SampleOrBuilder getGnssDataOrBuilder( - int index) { - if (gnssDataBuilder_ == null) { - return gnssData_.get(index); } else { - return gnssDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public java.util.List - getGnssDataOrBuilderList() { - if (gnssDataBuilder_ != null) { - return gnssDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(gnssData_); - } - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_Sample.Builder addGnssDataBuilder() { - return getGnssDataFieldBuilder().addBuilder( - GNSS_Sample.getDefaultInstance()); - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public GNSS_Sample.Builder addGnssDataBuilder( - int index) { - return getGnssDataFieldBuilder().addBuilder( - index, GNSS_Sample.getDefaultInstance()); - } - /** - * repeated .GNSS_Sample gnss_data = 7; - */ - public java.util.List - getGnssDataBuilderList() { - return getGnssDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - GNSS_Sample, GNSS_Sample.Builder, GNSS_SampleOrBuilder> - getGnssDataFieldBuilder() { - if (gnssDataBuilder_ == null) { - gnssDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - GNSS_Sample, GNSS_Sample.Builder, GNSS_SampleOrBuilder>( - gnssData_, - ((bitField0_ & 0x00000040) == 0x00000040), - getParentForChildren(), - isClean()); - gnssData_ = null; - } - return gnssDataBuilder_; - } - - private java.util.List wifiData_ = - java.util.Collections.emptyList(); - private void ensureWifiDataIsMutable() { - if (!((bitField0_ & 0x00000080) == 0x00000080)) { - wifiData_ = new java.util.ArrayList(wifiData_); - bitField0_ |= 0x00000080; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - WiFi_Sample, WiFi_Sample.Builder, WiFi_SampleOrBuilder> wifiDataBuilder_; - - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public java.util.List getWifiDataList() { - if (wifiDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(wifiData_); - } else { - return wifiDataBuilder_.getMessageList(); - } - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public int getWifiDataCount() { - if (wifiDataBuilder_ == null) { - return wifiData_.size(); - } else { - return wifiDataBuilder_.getCount(); - } - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_Sample getWifiData(int index) { - if (wifiDataBuilder_ == null) { - return wifiData_.get(index); - } else { - return wifiDataBuilder_.getMessage(index); - } - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder setWifiData( - int index, WiFi_Sample value) { - if (wifiDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWifiDataIsMutable(); - wifiData_.set(index, value); - onChanged(); - } else { - wifiDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder setWifiData( - int index, WiFi_Sample.Builder builderForValue) { - if (wifiDataBuilder_ == null) { - ensureWifiDataIsMutable(); - wifiData_.set(index, builderForValue.build()); - onChanged(); - } else { - wifiDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder addWifiData(WiFi_Sample value) { - if (wifiDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWifiDataIsMutable(); - wifiData_.add(value); - onChanged(); - } else { - wifiDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder addWifiData( - int index, WiFi_Sample value) { - if (wifiDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureWifiDataIsMutable(); - wifiData_.add(index, value); - onChanged(); - } else { - wifiDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder addWifiData( - WiFi_Sample.Builder builderForValue) { - if (wifiDataBuilder_ == null) { - ensureWifiDataIsMutable(); - wifiData_.add(builderForValue.build()); - onChanged(); - } else { - wifiDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder addWifiData( - int index, WiFi_Sample.Builder builderForValue) { - if (wifiDataBuilder_ == null) { - ensureWifiDataIsMutable(); - wifiData_.add(index, builderForValue.build()); - onChanged(); - } else { - wifiDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder addAllWifiData( - Iterable values) { - if (wifiDataBuilder_ == null) { - ensureWifiDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, wifiData_); - onChanged(); - } else { - wifiDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder clearWifiData() { - if (wifiDataBuilder_ == null) { - wifiData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000080); - onChanged(); - } else { - wifiDataBuilder_.clear(); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public Builder removeWifiData(int index) { - if (wifiDataBuilder_ == null) { - ensureWifiDataIsMutable(); - wifiData_.remove(index); - onChanged(); - } else { - wifiDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_Sample.Builder getWifiDataBuilder( - int index) { - return getWifiDataFieldBuilder().getBuilder(index); - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_SampleOrBuilder getWifiDataOrBuilder( - int index) { - if (wifiDataBuilder_ == null) { - return wifiData_.get(index); } else { - return wifiDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public java.util.List - getWifiDataOrBuilderList() { - if (wifiDataBuilder_ != null) { - return wifiDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(wifiData_); - } - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_Sample.Builder addWifiDataBuilder() { - return getWifiDataFieldBuilder().addBuilder( - WiFi_Sample.getDefaultInstance()); - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public WiFi_Sample.Builder addWifiDataBuilder( - int index) { - return getWifiDataFieldBuilder().addBuilder( - index, WiFi_Sample.getDefaultInstance()); - } - /** - * repeated .WiFi_Sample wifi_data = 8; - */ - public java.util.List - getWifiDataBuilderList() { - return getWifiDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - WiFi_Sample, WiFi_Sample.Builder, WiFi_SampleOrBuilder> - getWifiDataFieldBuilder() { - if (wifiDataBuilder_ == null) { - wifiDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - WiFi_Sample, WiFi_Sample.Builder, WiFi_SampleOrBuilder>( - wifiData_, - ((bitField0_ & 0x00000080) == 0x00000080), - getParentForChildren(), - isClean()); - wifiData_ = null; - } - return wifiDataBuilder_; - } - - private java.util.List apsData_ = - java.util.Collections.emptyList(); - private void ensureApsDataIsMutable() { - if (!((bitField0_ & 0x00000100) == 0x00000100)) { - apsData_ = new java.util.ArrayList(apsData_); - bitField0_ |= 0x00000100; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - AP_Data, AP_Data.Builder, AP_DataOrBuilder> apsDataBuilder_; - - /** - * repeated .AP_Data aps_data = 9; - */ - public java.util.List getApsDataList() { - if (apsDataBuilder_ == null) { - return java.util.Collections.unmodifiableList(apsData_); - } else { - return apsDataBuilder_.getMessageList(); - } - } - /** - * repeated .AP_Data aps_data = 9; - */ - public int getApsDataCount() { - if (apsDataBuilder_ == null) { - return apsData_.size(); - } else { - return apsDataBuilder_.getCount(); - } - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_Data getApsData(int index) { - if (apsDataBuilder_ == null) { - return apsData_.get(index); - } else { - return apsDataBuilder_.getMessage(index); - } - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder setApsData( - int index, AP_Data value) { - if (apsDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureApsDataIsMutable(); - apsData_.set(index, value); - onChanged(); - } else { - apsDataBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder setApsData( - int index, AP_Data.Builder builderForValue) { - if (apsDataBuilder_ == null) { - ensureApsDataIsMutable(); - apsData_.set(index, builderForValue.build()); - onChanged(); - } else { - apsDataBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder addApsData(AP_Data value) { - if (apsDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureApsDataIsMutable(); - apsData_.add(value); - onChanged(); - } else { - apsDataBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder addApsData( - int index, AP_Data value) { - if (apsDataBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureApsDataIsMutable(); - apsData_.add(index, value); - onChanged(); - } else { - apsDataBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder addApsData( - AP_Data.Builder builderForValue) { - if (apsDataBuilder_ == null) { - ensureApsDataIsMutable(); - apsData_.add(builderForValue.build()); - onChanged(); - } else { - apsDataBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder addApsData( - int index, AP_Data.Builder builderForValue) { - if (apsDataBuilder_ == null) { - ensureApsDataIsMutable(); - apsData_.add(index, builderForValue.build()); - onChanged(); - } else { - apsDataBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder addAllApsData( - Iterable values) { - if (apsDataBuilder_ == null) { - ensureApsDataIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, apsData_); - onChanged(); - } else { - apsDataBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder clearApsData() { - if (apsDataBuilder_ == null) { - apsData_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000100); - onChanged(); - } else { - apsDataBuilder_.clear(); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public Builder removeApsData(int index) { - if (apsDataBuilder_ == null) { - ensureApsDataIsMutable(); - apsData_.remove(index); - onChanged(); - } else { - apsDataBuilder_.remove(index); - } - return this; - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_Data.Builder getApsDataBuilder( - int index) { - return getApsDataFieldBuilder().getBuilder(index); - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_DataOrBuilder getApsDataOrBuilder( - int index) { - if (apsDataBuilder_ == null) { - return apsData_.get(index); } else { - return apsDataBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .AP_Data aps_data = 9; - */ - public java.util.List - getApsDataOrBuilderList() { - if (apsDataBuilder_ != null) { - return apsDataBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(apsData_); - } - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_Data.Builder addApsDataBuilder() { - return getApsDataFieldBuilder().addBuilder( - AP_Data.getDefaultInstance()); - } - /** - * repeated .AP_Data aps_data = 9; - */ - public AP_Data.Builder addApsDataBuilder( - int index) { - return getApsDataFieldBuilder().addBuilder( - index, AP_Data.getDefaultInstance()); - } - /** - * repeated .AP_Data aps_data = 9; - */ - public java.util.List - getApsDataBuilderList() { - return getApsDataFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - AP_Data, AP_Data.Builder, AP_DataOrBuilder> - getApsDataFieldBuilder() { - if (apsDataBuilder_ == null) { - apsDataBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - AP_Data, AP_Data.Builder, AP_DataOrBuilder>( - apsData_, - ((bitField0_ & 0x00000100) == 0x00000100), - getParentForChildren(), - isClean()); - apsData_ = null; - } - return apsDataBuilder_; - } - - private long startTimestamp_ ; - /** - *
-       * UNIX timestamp (in milliseconds) recorded from the start of this
-       * trajectory data collection event. All future
-       * timestamps in sub classes are to be RELATIVE timestamps
-       * (in milliseconds) to this start time.
-       * E.g.
-       * start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning)
-       * relative_timestamp = 3000 (3s)
-       * 
- * - * optional int64 start_timestamp = 10; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - /** - *
-       * UNIX timestamp (in milliseconds) recorded from the start of this
-       * trajectory data collection event. All future
-       * timestamps in sub classes are to be RELATIVE timestamps
-       * (in milliseconds) to this start time.
-       * E.g.
-       * start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning)
-       * relative_timestamp = 3000 (3s)
-       * 
- * - * optional int64 start_timestamp = 10; - */ - public Builder setStartTimestamp(long value) { - - startTimestamp_ = value; - onChanged(); - return this; - } - /** - *
-       * UNIX timestamp (in milliseconds) recorded from the start of this
-       * trajectory data collection event. All future
-       * timestamps in sub classes are to be RELATIVE timestamps
-       * (in milliseconds) to this start time.
-       * E.g.
-       * start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning)
-       * relative_timestamp = 3000 (3s)
-       * 
- * - * optional int64 start_timestamp = 10; - */ - public Builder clearStartTimestamp() { - - startTimestamp_ = 0L; - onChanged(); - return this; - } - - private Object dataIdentifier_ = ""; - /** - * optional string data_identifier = 11; - */ - public String getDataIdentifier() { - Object ref = dataIdentifier_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - dataIdentifier_ = s; - return s; - } else { - return (String) ref; - } - } - /** - * optional string data_identifier = 11; - */ - public com.google.protobuf.ByteString - getDataIdentifierBytes() { - Object ref = dataIdentifier_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - dataIdentifier_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string data_identifier = 11; - */ - public Builder setDataIdentifier( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - dataIdentifier_ = value; - onChanged(); - return this; - } - /** - * optional string data_identifier = 11; - */ - public Builder clearDataIdentifier() { - - dataIdentifier_ = getDefaultInstance().getDataIdentifier(); - onChanged(); - return this; - } - /** - * optional string data_identifier = 11; - */ - public Builder setDataIdentifierBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - dataIdentifier_ = value; - onChanged(); - return this; - } - - private Sensor_Info accelerometerInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> accelerometerInfoBuilder_; - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public boolean hasAccelerometerInfo() { - return accelerometerInfoBuilder_ != null || accelerometerInfo_ != null; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Sensor_Info getAccelerometerInfo() { - if (accelerometerInfoBuilder_ == null) { - return accelerometerInfo_ == null ? Sensor_Info.getDefaultInstance() : accelerometerInfo_; - } else { - return accelerometerInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Builder setAccelerometerInfo(Sensor_Info value) { - if (accelerometerInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - accelerometerInfo_ = value; - onChanged(); - } else { - accelerometerInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Builder setAccelerometerInfo( - Sensor_Info.Builder builderForValue) { - if (accelerometerInfoBuilder_ == null) { - accelerometerInfo_ = builderForValue.build(); - onChanged(); - } else { - accelerometerInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Builder mergeAccelerometerInfo(Sensor_Info value) { - if (accelerometerInfoBuilder_ == null) { - if (accelerometerInfo_ != null) { - accelerometerInfo_ = - Sensor_Info.newBuilder(accelerometerInfo_).mergeFrom(value).buildPartial(); - } else { - accelerometerInfo_ = value; - } - onChanged(); - } else { - accelerometerInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Builder clearAccelerometerInfo() { - if (accelerometerInfoBuilder_ == null) { - accelerometerInfo_ = null; - onChanged(); - } else { - accelerometerInfo_ = null; - accelerometerInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Sensor_Info.Builder getAccelerometerInfoBuilder() { - - onChanged(); - return getAccelerometerInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - public Sensor_InfoOrBuilder getAccelerometerInfoOrBuilder() { - if (accelerometerInfoBuilder_ != null) { - return accelerometerInfoBuilder_.getMessageOrBuilder(); - } else { - return accelerometerInfo_ == null ? - Sensor_Info.getDefaultInstance() : accelerometerInfo_; - } - } - /** - * optional .Sensor_Info accelerometer_info = 12; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getAccelerometerInfoFieldBuilder() { - if (accelerometerInfoBuilder_ == null) { - accelerometerInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getAccelerometerInfo(), - getParentForChildren(), - isClean()); - accelerometerInfo_ = null; - } - return accelerometerInfoBuilder_; - } - - private Sensor_Info gyroscopeInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> gyroscopeInfoBuilder_; - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public boolean hasGyroscopeInfo() { - return gyroscopeInfoBuilder_ != null || gyroscopeInfo_ != null; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Sensor_Info getGyroscopeInfo() { - if (gyroscopeInfoBuilder_ == null) { - return gyroscopeInfo_ == null ? Sensor_Info.getDefaultInstance() : gyroscopeInfo_; - } else { - return gyroscopeInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Builder setGyroscopeInfo(Sensor_Info value) { - if (gyroscopeInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - gyroscopeInfo_ = value; - onChanged(); - } else { - gyroscopeInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Builder setGyroscopeInfo( - Sensor_Info.Builder builderForValue) { - if (gyroscopeInfoBuilder_ == null) { - gyroscopeInfo_ = builderForValue.build(); - onChanged(); - } else { - gyroscopeInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Builder mergeGyroscopeInfo(Sensor_Info value) { - if (gyroscopeInfoBuilder_ == null) { - if (gyroscopeInfo_ != null) { - gyroscopeInfo_ = - Sensor_Info.newBuilder(gyroscopeInfo_).mergeFrom(value).buildPartial(); - } else { - gyroscopeInfo_ = value; - } - onChanged(); - } else { - gyroscopeInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Builder clearGyroscopeInfo() { - if (gyroscopeInfoBuilder_ == null) { - gyroscopeInfo_ = null; - onChanged(); - } else { - gyroscopeInfo_ = null; - gyroscopeInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Sensor_Info.Builder getGyroscopeInfoBuilder() { - - onChanged(); - return getGyroscopeInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - public Sensor_InfoOrBuilder getGyroscopeInfoOrBuilder() { - if (gyroscopeInfoBuilder_ != null) { - return gyroscopeInfoBuilder_.getMessageOrBuilder(); - } else { - return gyroscopeInfo_ == null ? - Sensor_Info.getDefaultInstance() : gyroscopeInfo_; - } - } - /** - * optional .Sensor_Info gyroscope_info = 13; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getGyroscopeInfoFieldBuilder() { - if (gyroscopeInfoBuilder_ == null) { - gyroscopeInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getGyroscopeInfo(), - getParentForChildren(), - isClean()); - gyroscopeInfo_ = null; - } - return gyroscopeInfoBuilder_; - } - - private Sensor_Info rotationVectorInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> rotationVectorInfoBuilder_; - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public boolean hasRotationVectorInfo() { - return rotationVectorInfoBuilder_ != null || rotationVectorInfo_ != null; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Sensor_Info getRotationVectorInfo() { - if (rotationVectorInfoBuilder_ == null) { - return rotationVectorInfo_ == null ? Sensor_Info.getDefaultInstance() : rotationVectorInfo_; - } else { - return rotationVectorInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Builder setRotationVectorInfo(Sensor_Info value) { - if (rotationVectorInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - rotationVectorInfo_ = value; - onChanged(); - } else { - rotationVectorInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Builder setRotationVectorInfo( - Sensor_Info.Builder builderForValue) { - if (rotationVectorInfoBuilder_ == null) { - rotationVectorInfo_ = builderForValue.build(); - onChanged(); - } else { - rotationVectorInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Builder mergeRotationVectorInfo(Sensor_Info value) { - if (rotationVectorInfoBuilder_ == null) { - if (rotationVectorInfo_ != null) { - rotationVectorInfo_ = - Sensor_Info.newBuilder(rotationVectorInfo_).mergeFrom(value).buildPartial(); - } else { - rotationVectorInfo_ = value; - } - onChanged(); - } else { - rotationVectorInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Builder clearRotationVectorInfo() { - if (rotationVectorInfoBuilder_ == null) { - rotationVectorInfo_ = null; - onChanged(); - } else { - rotationVectorInfo_ = null; - rotationVectorInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Sensor_Info.Builder getRotationVectorInfoBuilder() { - - onChanged(); - return getRotationVectorInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - public Sensor_InfoOrBuilder getRotationVectorInfoOrBuilder() { - if (rotationVectorInfoBuilder_ != null) { - return rotationVectorInfoBuilder_.getMessageOrBuilder(); - } else { - return rotationVectorInfo_ == null ? - Sensor_Info.getDefaultInstance() : rotationVectorInfo_; - } - } - /** - * optional .Sensor_Info rotation_vector_info = 14; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getRotationVectorInfoFieldBuilder() { - if (rotationVectorInfoBuilder_ == null) { - rotationVectorInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getRotationVectorInfo(), - getParentForChildren(), - isClean()); - rotationVectorInfo_ = null; - } - return rotationVectorInfoBuilder_; - } - - private Sensor_Info magnetometerInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> magnetometerInfoBuilder_; - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public boolean hasMagnetometerInfo() { - return magnetometerInfoBuilder_ != null || magnetometerInfo_ != null; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Sensor_Info getMagnetometerInfo() { - if (magnetometerInfoBuilder_ == null) { - return magnetometerInfo_ == null ? Sensor_Info.getDefaultInstance() : magnetometerInfo_; - } else { - return magnetometerInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Builder setMagnetometerInfo(Sensor_Info value) { - if (magnetometerInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - magnetometerInfo_ = value; - onChanged(); - } else { - magnetometerInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Builder setMagnetometerInfo( - Sensor_Info.Builder builderForValue) { - if (magnetometerInfoBuilder_ == null) { - magnetometerInfo_ = builderForValue.build(); - onChanged(); - } else { - magnetometerInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Builder mergeMagnetometerInfo(Sensor_Info value) { - if (magnetometerInfoBuilder_ == null) { - if (magnetometerInfo_ != null) { - magnetometerInfo_ = - Sensor_Info.newBuilder(magnetometerInfo_).mergeFrom(value).buildPartial(); - } else { - magnetometerInfo_ = value; - } - onChanged(); - } else { - magnetometerInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Builder clearMagnetometerInfo() { - if (magnetometerInfoBuilder_ == null) { - magnetometerInfo_ = null; - onChanged(); - } else { - magnetometerInfo_ = null; - magnetometerInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Sensor_Info.Builder getMagnetometerInfoBuilder() { - - onChanged(); - return getMagnetometerInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - public Sensor_InfoOrBuilder getMagnetometerInfoOrBuilder() { - if (magnetometerInfoBuilder_ != null) { - return magnetometerInfoBuilder_.getMessageOrBuilder(); - } else { - return magnetometerInfo_ == null ? - Sensor_Info.getDefaultInstance() : magnetometerInfo_; - } - } - /** - * optional .Sensor_Info magnetometer_info = 15; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getMagnetometerInfoFieldBuilder() { - if (magnetometerInfoBuilder_ == null) { - magnetometerInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getMagnetometerInfo(), - getParentForChildren(), - isClean()); - magnetometerInfo_ = null; - } - return magnetometerInfoBuilder_; - } - - private Sensor_Info barometerInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> barometerInfoBuilder_; - /** - * optional .Sensor_Info barometer_info = 16; - */ - public boolean hasBarometerInfo() { - return barometerInfoBuilder_ != null || barometerInfo_ != null; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Sensor_Info getBarometerInfo() { - if (barometerInfoBuilder_ == null) { - return barometerInfo_ == null ? Sensor_Info.getDefaultInstance() : barometerInfo_; - } else { - return barometerInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Builder setBarometerInfo(Sensor_Info value) { - if (barometerInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - barometerInfo_ = value; - onChanged(); - } else { - barometerInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Builder setBarometerInfo( - Sensor_Info.Builder builderForValue) { - if (barometerInfoBuilder_ == null) { - barometerInfo_ = builderForValue.build(); - onChanged(); - } else { - barometerInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Builder mergeBarometerInfo(Sensor_Info value) { - if (barometerInfoBuilder_ == null) { - if (barometerInfo_ != null) { - barometerInfo_ = - Sensor_Info.newBuilder(barometerInfo_).mergeFrom(value).buildPartial(); - } else { - barometerInfo_ = value; - } - onChanged(); - } else { - barometerInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Builder clearBarometerInfo() { - if (barometerInfoBuilder_ == null) { - barometerInfo_ = null; - onChanged(); - } else { - barometerInfo_ = null; - barometerInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Sensor_Info.Builder getBarometerInfoBuilder() { - - onChanged(); - return getBarometerInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - public Sensor_InfoOrBuilder getBarometerInfoOrBuilder() { - if (barometerInfoBuilder_ != null) { - return barometerInfoBuilder_.getMessageOrBuilder(); - } else { - return barometerInfo_ == null ? - Sensor_Info.getDefaultInstance() : barometerInfo_; - } - } - /** - * optional .Sensor_Info barometer_info = 16; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getBarometerInfoFieldBuilder() { - if (barometerInfoBuilder_ == null) { - barometerInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getBarometerInfo(), - getParentForChildren(), - isClean()); - barometerInfo_ = null; - } - return barometerInfoBuilder_; - } - - private Sensor_Info lightSensorInfo_ = null; - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> lightSensorInfoBuilder_; - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public boolean hasLightSensorInfo() { - return lightSensorInfoBuilder_ != null || lightSensorInfo_ != null; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Sensor_Info getLightSensorInfo() { - if (lightSensorInfoBuilder_ == null) { - return lightSensorInfo_ == null ? Sensor_Info.getDefaultInstance() : lightSensorInfo_; - } else { - return lightSensorInfoBuilder_.getMessage(); - } - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Builder setLightSensorInfo(Sensor_Info value) { - if (lightSensorInfoBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - lightSensorInfo_ = value; - onChanged(); - } else { - lightSensorInfoBuilder_.setMessage(value); - } - - return this; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Builder setLightSensorInfo( - Sensor_Info.Builder builderForValue) { - if (lightSensorInfoBuilder_ == null) { - lightSensorInfo_ = builderForValue.build(); - onChanged(); - } else { - lightSensorInfoBuilder_.setMessage(builderForValue.build()); - } - - return this; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Builder mergeLightSensorInfo(Sensor_Info value) { - if (lightSensorInfoBuilder_ == null) { - if (lightSensorInfo_ != null) { - lightSensorInfo_ = - Sensor_Info.newBuilder(lightSensorInfo_).mergeFrom(value).buildPartial(); - } else { - lightSensorInfo_ = value; - } - onChanged(); - } else { - lightSensorInfoBuilder_.mergeFrom(value); - } - - return this; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Builder clearLightSensorInfo() { - if (lightSensorInfoBuilder_ == null) { - lightSensorInfo_ = null; - onChanged(); - } else { - lightSensorInfo_ = null; - lightSensorInfoBuilder_ = null; - } - - return this; - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Sensor_Info.Builder getLightSensorInfoBuilder() { - - onChanged(); - return getLightSensorInfoFieldBuilder().getBuilder(); - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - public Sensor_InfoOrBuilder getLightSensorInfoOrBuilder() { - if (lightSensorInfoBuilder_ != null) { - return lightSensorInfoBuilder_.getMessageOrBuilder(); - } else { - return lightSensorInfo_ == null ? - Sensor_Info.getDefaultInstance() : lightSensorInfo_; - } - } - /** - * optional .Sensor_Info light_sensor_info = 17; - */ - private com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder> - getLightSensorInfoFieldBuilder() { - if (lightSensorInfoBuilder_ == null) { - lightSensorInfoBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< - Sensor_Info, Sensor_Info.Builder, Sensor_InfoOrBuilder>( - getLightSensorInfo(), - getParentForChildren(), - isClean()); - lightSensorInfo_ = null; - } - return lightSensorInfoBuilder_; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Trajectory) - } - - // @@protoc_insertion_point(class_scope:Trajectory) - private static final Trajectory DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Trajectory(); - } - - public static Trajectory getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Trajectory parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Trajectory(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Trajectory getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Pdr_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:Pdr_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * milliseconds from the start_timestamp
-     * 
- * - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * Both in metres. You should implement an algorithm to estimate
-     * these values. The values are always relative to your start point
-     * so the first entry should always be x = 0.0, y = 0.0
-     * 
- * - * optional float x = 2; - */ - float getX(); - - /** - * optional float y = 3; - */ - float getY(); - } - /** - * Protobuf type {@code Pdr_Sample} - */ - public static final class Pdr_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Pdr_Sample) - Pdr_SampleOrBuilder { - // Use Pdr_Sample.newBuilder() to construct. - private Pdr_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Pdr_Sample() { - relativeTimestamp_ = 0L; - x_ = 0F; - y_ = 0F; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Pdr_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - x_ = input.readFloat(); - break; - } - case 29: { - - y_ = input.readFloat(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Pdr_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Pdr_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Pdr_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - *
-     * milliseconds from the start_timestamp
-     * 
- * - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int X_FIELD_NUMBER = 2; - private float x_; - /** - *
-     * Both in metres. You should implement an algorithm to estimate
-     * these values. The values are always relative to your start point
-     * so the first entry should always be x = 0.0, y = 0.0
-     * 
- * - * optional float x = 2; - */ - public float getX() { - return x_; - } - - public static final int Y_FIELD_NUMBER = 3; - private float y_; - /** - * optional float y = 3; - */ - public float getY() { - return y_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (x_ != 0F) { - output.writeFloat(2, x_); - } - if (y_ != 0F) { - output.writeFloat(3, y_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (x_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, x_); - } - if (y_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(3, y_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Pdr_Sample)) { - return super.equals(obj); - } - Pdr_Sample other = (Pdr_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getX()) - == Float.floatToIntBits( - other.getX())); - result = result && ( - Float.floatToIntBits(getY()) - == Float.floatToIntBits( - other.getY())); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + X_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getX()); - hash = (37 * hash) + Y_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getY()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Pdr_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Pdr_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Pdr_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Pdr_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Pdr_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Pdr_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Pdr_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Pdr_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Pdr_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Pdr_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Pdr_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Pdr_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Pdr_Sample) - Pdr_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Pdr_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Pdr_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Pdr_Sample.class, Builder.class); - } - - // Construct using Traj.Pdr_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - x_ = 0F; - - y_ = 0F; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Pdr_Sample_descriptor; - } - - public Pdr_Sample getDefaultInstanceForType() { - return Pdr_Sample.getDefaultInstance(); - } - - public Pdr_Sample build() { - Pdr_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Pdr_Sample buildPartial() { - Pdr_Sample result = new Pdr_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.x_ = x_; - result.y_ = y_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Pdr_Sample) { - return mergeFrom((Pdr_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Pdr_Sample other) { - if (other == Pdr_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getX() != 0F) { - setX(other.getX()); - } - if (other.getY() != 0F) { - setY(other.getY()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Pdr_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Pdr_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - *
-       * milliseconds from the start_timestamp
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - *
-       * milliseconds from the start_timestamp
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - *
-       * milliseconds from the start_timestamp
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float x_ ; - /** - *
-       * Both in metres. You should implement an algorithm to estimate
-       * these values. The values are always relative to your start point
-       * so the first entry should always be x = 0.0, y = 0.0
-       * 
- * - * optional float x = 2; - */ - public float getX() { - return x_; - } - /** - *
-       * Both in metres. You should implement an algorithm to estimate
-       * these values. The values are always relative to your start point
-       * so the first entry should always be x = 0.0, y = 0.0
-       * 
- * - * optional float x = 2; - */ - public Builder setX(float value) { - - x_ = value; - onChanged(); - return this; - } - /** - *
-       * Both in metres. You should implement an algorithm to estimate
-       * these values. The values are always relative to your start point
-       * so the first entry should always be x = 0.0, y = 0.0
-       * 
- * - * optional float x = 2; - */ - public Builder clearX() { - - x_ = 0F; - onChanged(); - return this; - } - - private float y_ ; - /** - * optional float y = 3; - */ - public float getY() { - return y_; - } - /** - * optional float y = 3; - */ - public Builder setY(float value) { - - y_ = value; - onChanged(); - return this; - } - /** - * optional float y = 3; - */ - public Builder clearY() { - - y_ = 0F; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Pdr_Sample) - } - - // @@protoc_insertion_point(class_scope:Pdr_Sample) - private static final Pdr_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Pdr_Sample(); - } - - public static Pdr_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Pdr_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Pdr_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Pdr_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Motion_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:Motion_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * milliseconds
-     * 
- * - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * m/s^2
-     * 
- * - * optional float acc_x = 2; - */ - float getAccX(); - - /** - * optional float acc_y = 3; - */ - float getAccY(); - - /** - * optional float acc_z = 4; - */ - float getAccZ(); - - /** - *
-     * radians/s
-     * 
- * - * optional float gyr_x = 5; - */ - float getGyrX(); - - /** - * optional float gyr_y = 6; - */ - float getGyrY(); - - /** - * optional float gyr_z = 7; - */ - float getGyrZ(); - - /** - *
-     * unitless, 4 components should sum to ~1
-     * 
- * - * optional float rotation_vector_x = 8; - */ - float getRotationVectorX(); - - /** - * optional float rotation_vector_y = 9; - */ - float getRotationVectorY(); - - /** - * optional float rotation_vector_z = 10; - */ - float getRotationVectorZ(); - - /** - * optional float rotation_vector_w = 11; - */ - float getRotationVectorW(); - - /** - *
-     * Integer
-     * 
- * - * optional int32 step_count = 12; - */ - int getStepCount(); - } - /** - * Protobuf type {@code Motion_Sample} - */ - public static final class Motion_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Motion_Sample) - Motion_SampleOrBuilder { - // Use Motion_Sample.newBuilder() to construct. - private Motion_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Motion_Sample() { - relativeTimestamp_ = 0L; - accX_ = 0F; - accY_ = 0F; - accZ_ = 0F; - gyrX_ = 0F; - gyrY_ = 0F; - gyrZ_ = 0F; - rotationVectorX_ = 0F; - rotationVectorY_ = 0F; - rotationVectorZ_ = 0F; - rotationVectorW_ = 0F; - stepCount_ = 0; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Motion_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - accX_ = input.readFloat(); - break; - } - case 29: { - - accY_ = input.readFloat(); - break; - } - case 37: { - - accZ_ = input.readFloat(); - break; - } - case 45: { - - gyrX_ = input.readFloat(); - break; - } - case 53: { - - gyrY_ = input.readFloat(); - break; - } - case 61: { - - gyrZ_ = input.readFloat(); - break; - } - case 69: { - - rotationVectorX_ = input.readFloat(); - break; - } - case 77: { - - rotationVectorY_ = input.readFloat(); - break; - } - case 85: { - - rotationVectorZ_ = input.readFloat(); - break; - } - case 93: { - - rotationVectorW_ = input.readFloat(); - break; - } - case 96: { - - stepCount_ = input.readInt32(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Motion_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Motion_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Motion_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - *
-     * milliseconds
-     * 
- * - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int ACC_X_FIELD_NUMBER = 2; - private float accX_; - /** - *
-     * m/s^2
-     * 
- * - * optional float acc_x = 2; - */ - public float getAccX() { - return accX_; - } - - public static final int ACC_Y_FIELD_NUMBER = 3; - private float accY_; - /** - * optional float acc_y = 3; - */ - public float getAccY() { - return accY_; - } - - public static final int ACC_Z_FIELD_NUMBER = 4; - private float accZ_; - /** - * optional float acc_z = 4; - */ - public float getAccZ() { - return accZ_; - } - - public static final int GYR_X_FIELD_NUMBER = 5; - private float gyrX_; - /** - *
-     * radians/s
-     * 
- * - * optional float gyr_x = 5; - */ - public float getGyrX() { - return gyrX_; - } - - public static final int GYR_Y_FIELD_NUMBER = 6; - private float gyrY_; - /** - * optional float gyr_y = 6; - */ - public float getGyrY() { - return gyrY_; - } - - public static final int GYR_Z_FIELD_NUMBER = 7; - private float gyrZ_; - /** - * optional float gyr_z = 7; - */ - public float getGyrZ() { - return gyrZ_; - } - - public static final int ROTATION_VECTOR_X_FIELD_NUMBER = 8; - private float rotationVectorX_; - /** - *
-     * unitless, 4 components should sum to ~1
-     * 
- * - * optional float rotation_vector_x = 8; - */ - public float getRotationVectorX() { - return rotationVectorX_; - } - - public static final int ROTATION_VECTOR_Y_FIELD_NUMBER = 9; - private float rotationVectorY_; - /** - * optional float rotation_vector_y = 9; - */ - public float getRotationVectorY() { - return rotationVectorY_; - } - - public static final int ROTATION_VECTOR_Z_FIELD_NUMBER = 10; - private float rotationVectorZ_; - /** - * optional float rotation_vector_z = 10; - */ - public float getRotationVectorZ() { - return rotationVectorZ_; - } - - public static final int ROTATION_VECTOR_W_FIELD_NUMBER = 11; - private float rotationVectorW_; - /** - * optional float rotation_vector_w = 11; - */ - public float getRotationVectorW() { - return rotationVectorW_; - } - - public static final int STEP_COUNT_FIELD_NUMBER = 12; - private int stepCount_; - /** - *
-     * Integer
-     * 
- * - * optional int32 step_count = 12; - */ - public int getStepCount() { - return stepCount_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (accX_ != 0F) { - output.writeFloat(2, accX_); - } - if (accY_ != 0F) { - output.writeFloat(3, accY_); - } - if (accZ_ != 0F) { - output.writeFloat(4, accZ_); - } - if (gyrX_ != 0F) { - output.writeFloat(5, gyrX_); - } - if (gyrY_ != 0F) { - output.writeFloat(6, gyrY_); - } - if (gyrZ_ != 0F) { - output.writeFloat(7, gyrZ_); - } - if (rotationVectorX_ != 0F) { - output.writeFloat(8, rotationVectorX_); - } - if (rotationVectorY_ != 0F) { - output.writeFloat(9, rotationVectorY_); - } - if (rotationVectorZ_ != 0F) { - output.writeFloat(10, rotationVectorZ_); - } - if (rotationVectorW_ != 0F) { - output.writeFloat(11, rotationVectorW_); - } - if (stepCount_ != 0) { - output.writeInt32(12, stepCount_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (accX_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, accX_); - } - if (accY_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(3, accY_); - } - if (accZ_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(4, accZ_); - } - if (gyrX_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(5, gyrX_); - } - if (gyrY_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(6, gyrY_); - } - if (gyrZ_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(7, gyrZ_); - } - if (rotationVectorX_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(8, rotationVectorX_); - } - if (rotationVectorY_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(9, rotationVectorY_); - } - if (rotationVectorZ_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(10, rotationVectorZ_); - } - if (rotationVectorW_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(11, rotationVectorW_); - } - if (stepCount_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeInt32Size(12, stepCount_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Motion_Sample)) { - return super.equals(obj); - } - Motion_Sample other = (Motion_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getAccX()) - == Float.floatToIntBits( - other.getAccX())); - result = result && ( - Float.floatToIntBits(getAccY()) - == Float.floatToIntBits( - other.getAccY())); - result = result && ( - Float.floatToIntBits(getAccZ()) - == Float.floatToIntBits( - other.getAccZ())); - result = result && ( - Float.floatToIntBits(getGyrX()) - == Float.floatToIntBits( - other.getGyrX())); - result = result && ( - Float.floatToIntBits(getGyrY()) - == Float.floatToIntBits( - other.getGyrY())); - result = result && ( - Float.floatToIntBits(getGyrZ()) - == Float.floatToIntBits( - other.getGyrZ())); - result = result && ( - Float.floatToIntBits(getRotationVectorX()) - == Float.floatToIntBits( - other.getRotationVectorX())); - result = result && ( - Float.floatToIntBits(getRotationVectorY()) - == Float.floatToIntBits( - other.getRotationVectorY())); - result = result && ( - Float.floatToIntBits(getRotationVectorZ()) - == Float.floatToIntBits( - other.getRotationVectorZ())); - result = result && ( - Float.floatToIntBits(getRotationVectorW()) - == Float.floatToIntBits( - other.getRotationVectorW())); - result = result && (getStepCount() - == other.getStepCount()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + ACC_X_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getAccX()); - hash = (37 * hash) + ACC_Y_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getAccY()); - hash = (37 * hash) + ACC_Z_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getAccZ()); - hash = (37 * hash) + GYR_X_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getGyrX()); - hash = (37 * hash) + GYR_Y_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getGyrY()); - hash = (37 * hash) + GYR_Z_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getGyrZ()); - hash = (37 * hash) + ROTATION_VECTOR_X_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getRotationVectorX()); - hash = (37 * hash) + ROTATION_VECTOR_Y_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getRotationVectorY()); - hash = (37 * hash) + ROTATION_VECTOR_Z_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getRotationVectorZ()); - hash = (37 * hash) + ROTATION_VECTOR_W_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getRotationVectorW()); - hash = (37 * hash) + STEP_COUNT_FIELD_NUMBER; - hash = (53 * hash) + getStepCount(); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Motion_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Motion_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Motion_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Motion_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Motion_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Motion_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Motion_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Motion_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Motion_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Motion_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Motion_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Motion_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Motion_Sample) - Motion_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Motion_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Motion_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Motion_Sample.class, Builder.class); - } - - // Construct using Traj.Motion_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - accX_ = 0F; - - accY_ = 0F; - - accZ_ = 0F; - - gyrX_ = 0F; - - gyrY_ = 0F; - - gyrZ_ = 0F; - - rotationVectorX_ = 0F; - - rotationVectorY_ = 0F; - - rotationVectorZ_ = 0F; - - rotationVectorW_ = 0F; - - stepCount_ = 0; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Motion_Sample_descriptor; - } - - public Motion_Sample getDefaultInstanceForType() { - return Motion_Sample.getDefaultInstance(); - } - - public Motion_Sample build() { - Motion_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Motion_Sample buildPartial() { - Motion_Sample result = new Motion_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.accX_ = accX_; - result.accY_ = accY_; - result.accZ_ = accZ_; - result.gyrX_ = gyrX_; - result.gyrY_ = gyrY_; - result.gyrZ_ = gyrZ_; - result.rotationVectorX_ = rotationVectorX_; - result.rotationVectorY_ = rotationVectorY_; - result.rotationVectorZ_ = rotationVectorZ_; - result.rotationVectorW_ = rotationVectorW_; - result.stepCount_ = stepCount_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Motion_Sample) { - return mergeFrom((Motion_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Motion_Sample other) { - if (other == Motion_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getAccX() != 0F) { - setAccX(other.getAccX()); - } - if (other.getAccY() != 0F) { - setAccY(other.getAccY()); - } - if (other.getAccZ() != 0F) { - setAccZ(other.getAccZ()); - } - if (other.getGyrX() != 0F) { - setGyrX(other.getGyrX()); - } - if (other.getGyrY() != 0F) { - setGyrY(other.getGyrY()); - } - if (other.getGyrZ() != 0F) { - setGyrZ(other.getGyrZ()); - } - if (other.getRotationVectorX() != 0F) { - setRotationVectorX(other.getRotationVectorX()); - } - if (other.getRotationVectorY() != 0F) { - setRotationVectorY(other.getRotationVectorY()); - } - if (other.getRotationVectorZ() != 0F) { - setRotationVectorZ(other.getRotationVectorZ()); - } - if (other.getRotationVectorW() != 0F) { - setRotationVectorW(other.getRotationVectorW()); - } - if (other.getStepCount() != 0) { - setStepCount(other.getStepCount()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Motion_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Motion_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - *
-       * milliseconds
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - *
-       * milliseconds
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - *
-       * milliseconds
-       * 
- * - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float accX_ ; - /** - *
-       * m/s^2
-       * 
- * - * optional float acc_x = 2; - */ - public float getAccX() { - return accX_; - } - /** - *
-       * m/s^2
-       * 
- * - * optional float acc_x = 2; - */ - public Builder setAccX(float value) { - - accX_ = value; - onChanged(); - return this; - } - /** - *
-       * m/s^2
-       * 
- * - * optional float acc_x = 2; - */ - public Builder clearAccX() { - - accX_ = 0F; - onChanged(); - return this; - } - - private float accY_ ; - /** - * optional float acc_y = 3; - */ - public float getAccY() { - return accY_; - } - /** - * optional float acc_y = 3; - */ - public Builder setAccY(float value) { - - accY_ = value; - onChanged(); - return this; - } - /** - * optional float acc_y = 3; - */ - public Builder clearAccY() { - - accY_ = 0F; - onChanged(); - return this; - } - - private float accZ_ ; - /** - * optional float acc_z = 4; - */ - public float getAccZ() { - return accZ_; - } - /** - * optional float acc_z = 4; - */ - public Builder setAccZ(float value) { - - accZ_ = value; - onChanged(); - return this; - } - /** - * optional float acc_z = 4; - */ - public Builder clearAccZ() { - - accZ_ = 0F; - onChanged(); - return this; - } - - private float gyrX_ ; - /** - *
-       * radians/s
-       * 
- * - * optional float gyr_x = 5; - */ - public float getGyrX() { - return gyrX_; - } - /** - *
-       * radians/s
-       * 
- * - * optional float gyr_x = 5; - */ - public Builder setGyrX(float value) { - - gyrX_ = value; - onChanged(); - return this; - } - /** - *
-       * radians/s
-       * 
- * - * optional float gyr_x = 5; - */ - public Builder clearGyrX() { - - gyrX_ = 0F; - onChanged(); - return this; - } - - private float gyrY_ ; - /** - * optional float gyr_y = 6; - */ - public float getGyrY() { - return gyrY_; - } - /** - * optional float gyr_y = 6; - */ - public Builder setGyrY(float value) { - - gyrY_ = value; - onChanged(); - return this; - } - /** - * optional float gyr_y = 6; - */ - public Builder clearGyrY() { - - gyrY_ = 0F; - onChanged(); - return this; - } - - private float gyrZ_ ; - /** - * optional float gyr_z = 7; - */ - public float getGyrZ() { - return gyrZ_; - } - /** - * optional float gyr_z = 7; - */ - public Builder setGyrZ(float value) { - - gyrZ_ = value; - onChanged(); - return this; - } - /** - * optional float gyr_z = 7; - */ - public Builder clearGyrZ() { - - gyrZ_ = 0F; - onChanged(); - return this; - } - - private float rotationVectorX_ ; - /** - *
-       * unitless, 4 components should sum to ~1
-       * 
- * - * optional float rotation_vector_x = 8; - */ - public float getRotationVectorX() { - return rotationVectorX_; - } - /** - *
-       * unitless, 4 components should sum to ~1
-       * 
- * - * optional float rotation_vector_x = 8; - */ - public Builder setRotationVectorX(float value) { - - rotationVectorX_ = value; - onChanged(); - return this; - } - /** - *
-       * unitless, 4 components should sum to ~1
-       * 
- * - * optional float rotation_vector_x = 8; - */ - public Builder clearRotationVectorX() { - - rotationVectorX_ = 0F; - onChanged(); - return this; - } - - private float rotationVectorY_ ; - /** - * optional float rotation_vector_y = 9; - */ - public float getRotationVectorY() { - return rotationVectorY_; - } - /** - * optional float rotation_vector_y = 9; - */ - public Builder setRotationVectorY(float value) { - - rotationVectorY_ = value; - onChanged(); - return this; - } - /** - * optional float rotation_vector_y = 9; - */ - public Builder clearRotationVectorY() { - - rotationVectorY_ = 0F; - onChanged(); - return this; - } - - private float rotationVectorZ_ ; - /** - * optional float rotation_vector_z = 10; - */ - public float getRotationVectorZ() { - return rotationVectorZ_; - } - /** - * optional float rotation_vector_z = 10; - */ - public Builder setRotationVectorZ(float value) { - - rotationVectorZ_ = value; - onChanged(); - return this; - } - /** - * optional float rotation_vector_z = 10; - */ - public Builder clearRotationVectorZ() { - - rotationVectorZ_ = 0F; - onChanged(); - return this; - } - - private float rotationVectorW_ ; - /** - * optional float rotation_vector_w = 11; - */ - public float getRotationVectorW() { - return rotationVectorW_; - } - /** - * optional float rotation_vector_w = 11; - */ - public Builder setRotationVectorW(float value) { - - rotationVectorW_ = value; - onChanged(); - return this; - } - /** - * optional float rotation_vector_w = 11; - */ - public Builder clearRotationVectorW() { - - rotationVectorW_ = 0F; - onChanged(); - return this; - } - - private int stepCount_ ; - /** - *
-       * Integer
-       * 
- * - * optional int32 step_count = 12; - */ - public int getStepCount() { - return stepCount_; - } - /** - *
-       * Integer
-       * 
- * - * optional int32 step_count = 12; - */ - public Builder setStepCount(int value) { - - stepCount_ = value; - onChanged(); - return this; - } - /** - *
-       * Integer
-       * 
- * - * optional int32 step_count = 12; - */ - public Builder clearStepCount() { - - stepCount_ = 0; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Motion_Sample) - } - - // @@protoc_insertion_point(class_scope:Motion_Sample) - private static final Motion_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Motion_Sample(); - } - - public static Motion_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Motion_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Motion_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Motion_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Position_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:Position_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * uT
-     * 
- * - * optional float mag_x = 2; - */ - float getMagX(); - - /** - * optional float mag_y = 3; - */ - float getMagY(); - - /** - * optional float mag_z = 4; - */ - float getMagZ(); - } - /** - * Protobuf type {@code Position_Sample} - */ - public static final class Position_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Position_Sample) - Position_SampleOrBuilder { - // Use Position_Sample.newBuilder() to construct. - private Position_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Position_Sample() { - relativeTimestamp_ = 0L; - magX_ = 0F; - magY_ = 0F; - magZ_ = 0F; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Position_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - magX_ = input.readFloat(); - break; - } - case 29: { - - magY_ = input.readFloat(); - break; - } - case 37: { - - magZ_ = input.readFloat(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Position_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Position_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Position_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int MAG_X_FIELD_NUMBER = 2; - private float magX_; - /** - *
-     * uT
-     * 
- * - * optional float mag_x = 2; - */ - public float getMagX() { - return magX_; - } - - public static final int MAG_Y_FIELD_NUMBER = 3; - private float magY_; - /** - * optional float mag_y = 3; - */ - public float getMagY() { - return magY_; - } - - public static final int MAG_Z_FIELD_NUMBER = 4; - private float magZ_; - /** - * optional float mag_z = 4; - */ - public float getMagZ() { - return magZ_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (magX_ != 0F) { - output.writeFloat(2, magX_); - } - if (magY_ != 0F) { - output.writeFloat(3, magY_); - } - if (magZ_ != 0F) { - output.writeFloat(4, magZ_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (magX_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, magX_); - } - if (magY_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(3, magY_); - } - if (magZ_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(4, magZ_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Position_Sample)) { - return super.equals(obj); - } - Position_Sample other = (Position_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getMagX()) - == Float.floatToIntBits( - other.getMagX())); - result = result && ( - Float.floatToIntBits(getMagY()) - == Float.floatToIntBits( - other.getMagY())); - result = result && ( - Float.floatToIntBits(getMagZ()) - == Float.floatToIntBits( - other.getMagZ())); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + MAG_X_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getMagX()); - hash = (37 * hash) + MAG_Y_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getMagY()); - hash = (37 * hash) + MAG_Z_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getMagZ()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Position_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Position_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Position_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Position_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Position_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Position_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Position_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Position_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Position_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Position_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Position_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Position_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Position_Sample) - Position_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Position_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Position_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Position_Sample.class, Builder.class); - } - - // Construct using Traj.Position_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - magX_ = 0F; - - magY_ = 0F; - - magZ_ = 0F; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Position_Sample_descriptor; - } - - public Position_Sample getDefaultInstanceForType() { - return Position_Sample.getDefaultInstance(); - } - - public Position_Sample build() { - Position_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Position_Sample buildPartial() { - Position_Sample result = new Position_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.magX_ = magX_; - result.magY_ = magY_; - result.magZ_ = magZ_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Position_Sample) { - return mergeFrom((Position_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Position_Sample other) { - if (other == Position_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getMagX() != 0F) { - setMagX(other.getMagX()); - } - if (other.getMagY() != 0F) { - setMagY(other.getMagY()); - } - if (other.getMagZ() != 0F) { - setMagZ(other.getMagZ()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Position_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Position_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float magX_ ; - /** - *
-       * uT
-       * 
- * - * optional float mag_x = 2; - */ - public float getMagX() { - return magX_; - } - /** - *
-       * uT
-       * 
- * - * optional float mag_x = 2; - */ - public Builder setMagX(float value) { - - magX_ = value; - onChanged(); - return this; - } - /** - *
-       * uT
-       * 
- * - * optional float mag_x = 2; - */ - public Builder clearMagX() { - - magX_ = 0F; - onChanged(); - return this; - } - - private float magY_ ; - /** - * optional float mag_y = 3; - */ - public float getMagY() { - return magY_; - } - /** - * optional float mag_y = 3; - */ - public Builder setMagY(float value) { - - magY_ = value; - onChanged(); - return this; - } - /** - * optional float mag_y = 3; - */ - public Builder clearMagY() { - - magY_ = 0F; - onChanged(); - return this; - } - - private float magZ_ ; - /** - * optional float mag_z = 4; - */ - public float getMagZ() { - return magZ_; - } - /** - * optional float mag_z = 4; - */ - public Builder setMagZ(float value) { - - magZ_ = value; - onChanged(); - return this; - } - /** - * optional float mag_z = 4; - */ - public Builder clearMagZ() { - - magZ_ = 0F; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Position_Sample) - } - - // @@protoc_insertion_point(class_scope:Position_Sample) - private static final Position_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Position_Sample(); - } - - public static Position_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Position_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Position_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Position_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Pressure_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:Pressure_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * mbar
-     * 
- * - * optional float pressure = 2; - */ - float getPressure(); - } - /** - * Protobuf type {@code Pressure_Sample} - */ - public static final class Pressure_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Pressure_Sample) - Pressure_SampleOrBuilder { - // Use Pressure_Sample.newBuilder() to construct. - private Pressure_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Pressure_Sample() { - relativeTimestamp_ = 0L; - pressure_ = 0F; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Pressure_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - pressure_ = input.readFloat(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Pressure_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Pressure_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Pressure_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int PRESSURE_FIELD_NUMBER = 2; - private float pressure_; - /** - *
-     * mbar
-     * 
- * - * optional float pressure = 2; - */ - public float getPressure() { - return pressure_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (pressure_ != 0F) { - output.writeFloat(2, pressure_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (pressure_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, pressure_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Pressure_Sample)) { - return super.equals(obj); - } - Pressure_Sample other = (Pressure_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getPressure()) - == Float.floatToIntBits( - other.getPressure())); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + PRESSURE_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getPressure()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Pressure_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Pressure_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Pressure_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Pressure_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Pressure_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Pressure_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Pressure_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Pressure_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Pressure_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Pressure_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Pressure_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Pressure_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Pressure_Sample) - Pressure_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Pressure_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Pressure_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Pressure_Sample.class, Builder.class); - } - - // Construct using Traj.Pressure_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - pressure_ = 0F; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Pressure_Sample_descriptor; - } - - public Pressure_Sample getDefaultInstanceForType() { - return Pressure_Sample.getDefaultInstance(); - } - - public Pressure_Sample build() { - Pressure_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Pressure_Sample buildPartial() { - Pressure_Sample result = new Pressure_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.pressure_ = pressure_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Pressure_Sample) { - return mergeFrom((Pressure_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Pressure_Sample other) { - if (other == Pressure_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getPressure() != 0F) { - setPressure(other.getPressure()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Pressure_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Pressure_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float pressure_ ; - /** - *
-       * mbar
-       * 
- * - * optional float pressure = 2; - */ - public float getPressure() { - return pressure_; - } - /** - *
-       * mbar
-       * 
- * - * optional float pressure = 2; - */ - public Builder setPressure(float value) { - - pressure_ = value; - onChanged(); - return this; - } - /** - *
-       * mbar
-       * 
- * - * optional float pressure = 2; - */ - public Builder clearPressure() { - - pressure_ = 0F; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Pressure_Sample) - } - - // @@protoc_insertion_point(class_scope:Pressure_Sample) - private static final Pressure_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Pressure_Sample(); - } - - public static Pressure_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Pressure_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Pressure_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Pressure_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Light_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:Light_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * lux
-     * 
- * - * optional float light = 2; - */ - float getLight(); - } - /** - * Protobuf type {@code Light_Sample} - */ - public static final class Light_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Light_Sample) - Light_SampleOrBuilder { - // Use Light_Sample.newBuilder() to construct. - private Light_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Light_Sample() { - relativeTimestamp_ = 0L; - light_ = 0F; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Light_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - light_ = input.readFloat(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Light_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Light_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Light_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int LIGHT_FIELD_NUMBER = 2; - private float light_; - /** - *
-     * lux
-     * 
- * - * optional float light = 2; - */ - public float getLight() { - return light_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (light_ != 0F) { - output.writeFloat(2, light_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (light_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, light_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Light_Sample)) { - return super.equals(obj); - } - Light_Sample other = (Light_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getLight()) - == Float.floatToIntBits( - other.getLight())); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + LIGHT_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getLight()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Light_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Light_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Light_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Light_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Light_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Light_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Light_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Light_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Light_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Light_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Light_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Light_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Light_Sample) - Light_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Light_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Light_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Light_Sample.class, Builder.class); - } - - // Construct using Traj.Light_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - light_ = 0F; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Light_Sample_descriptor; - } - - public Light_Sample getDefaultInstanceForType() { - return Light_Sample.getDefaultInstance(); - } - - public Light_Sample build() { - Light_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Light_Sample buildPartial() { - Light_Sample result = new Light_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.light_ = light_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Light_Sample) { - return mergeFrom((Light_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Light_Sample other) { - if (other == Light_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getLight() != 0F) { - setLight(other.getLight()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Light_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Light_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float light_ ; - /** - *
-       * lux
-       * 
- * - * optional float light = 2; - */ - public float getLight() { - return light_; - } - /** - *
-       * lux
-       * 
- * - * optional float light = 2; - */ - public Builder setLight(float value) { - - light_ = value; - onChanged(); - return this; - } - /** - *
-       * lux
-       * 
- * - * optional float light = 2; - */ - public Builder clearLight() { - - light_ = 0F; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Light_Sample) - } - - // @@protoc_insertion_point(class_scope:Light_Sample) - private static final Light_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Light_Sample(); - } - - public static Light_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Light_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Light_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Light_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface GNSS_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:GNSS_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * degrees (minimum 6 significant figures)
-     * latitude between -90 and 90
-     * 
- * - * optional float latitude = 2; - */ - float getLatitude(); - - /** - *
-     * longitude between -180 and 180
-     * 
- * - * optional float longitude = 3; - */ - float getLongitude(); - - /** - *
-     *metres
-     * 
- * - * optional float altitude = 4; - */ - float getAltitude(); - - /** - *
-     * metres
-     * 
- * - * optional float accuracy = 5; - */ - float getAccuracy(); - - /** - *
-     * m/s
-     * 
- * - * optional float speed = 6; - */ - float getSpeed(); - - /** - *
-     * e.g 'gps' or 'network'
-     * 
- * - * optional string provider = 7; - */ - String getProvider(); - /** - *
-     * e.g 'gps' or 'network'
-     * 
- * - * optional string provider = 7; - */ - com.google.protobuf.ByteString - getProviderBytes(); - } - /** - * Protobuf type {@code GNSS_Sample} - */ - public static final class GNSS_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:GNSS_Sample) - GNSS_SampleOrBuilder { - // Use GNSS_Sample.newBuilder() to construct. - private GNSS_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private GNSS_Sample() { - relativeTimestamp_ = 0L; - latitude_ = 0F; - longitude_ = 0F; - altitude_ = 0F; - accuracy_ = 0F; - speed_ = 0F; - provider_ = ""; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private GNSS_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 21: { - - latitude_ = input.readFloat(); - break; - } - case 29: { - - longitude_ = input.readFloat(); - break; - } - case 37: { - - altitude_ = input.readFloat(); - break; - } - case 45: { - - accuracy_ = input.readFloat(); - break; - } - case 53: { - - speed_ = input.readFloat(); - break; - } - case 58: { - String s = input.readStringRequireUtf8(); - - provider_ = s; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_GNSS_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_GNSS_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - GNSS_Sample.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int LATITUDE_FIELD_NUMBER = 2; - private float latitude_; - /** - *
-     * degrees (minimum 6 significant figures)
-     * latitude between -90 and 90
-     * 
- * - * optional float latitude = 2; - */ - public float getLatitude() { - return latitude_; - } - - public static final int LONGITUDE_FIELD_NUMBER = 3; - private float longitude_; - /** - *
-     * longitude between -180 and 180
-     * 
- * - * optional float longitude = 3; - */ - public float getLongitude() { - return longitude_; - } - - public static final int ALTITUDE_FIELD_NUMBER = 4; - private float altitude_; - /** - *
-     *metres
-     * 
- * - * optional float altitude = 4; - */ - public float getAltitude() { - return altitude_; - } - - public static final int ACCURACY_FIELD_NUMBER = 5; - private float accuracy_; - /** - *
-     * metres
-     * 
- * - * optional float accuracy = 5; - */ - public float getAccuracy() { - return accuracy_; - } - - public static final int SPEED_FIELD_NUMBER = 6; - private float speed_; - /** - *
-     * m/s
-     * 
- * - * optional float speed = 6; - */ - public float getSpeed() { - return speed_; - } - - public static final int PROVIDER_FIELD_NUMBER = 7; - private volatile Object provider_; - /** - *
-     * e.g 'gps' or 'network'
-     * 
- * - * optional string provider = 7; - */ - public String getProvider() { - Object ref = provider_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - provider_ = s; - return s; - } - } - /** - *
-     * e.g 'gps' or 'network'
-     * 
- * - * optional string provider = 7; - */ - public com.google.protobuf.ByteString - getProviderBytes() { - Object ref = provider_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - provider_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (latitude_ != 0F) { - output.writeFloat(2, latitude_); - } - if (longitude_ != 0F) { - output.writeFloat(3, longitude_); - } - if (altitude_ != 0F) { - output.writeFloat(4, altitude_); - } - if (accuracy_ != 0F) { - output.writeFloat(5, accuracy_); - } - if (speed_ != 0F) { - output.writeFloat(6, speed_); - } - if (!getProviderBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 7, provider_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (latitude_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(2, latitude_); - } - if (longitude_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(3, longitude_); - } - if (altitude_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(4, altitude_); - } - if (accuracy_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(5, accuracy_); - } - if (speed_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(6, speed_); - } - if (!getProviderBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, provider_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof GNSS_Sample)) { - return super.equals(obj); - } - GNSS_Sample other = (GNSS_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && ( - Float.floatToIntBits(getLatitude()) - == Float.floatToIntBits( - other.getLatitude())); - result = result && ( - Float.floatToIntBits(getLongitude()) - == Float.floatToIntBits( - other.getLongitude())); - result = result && ( - Float.floatToIntBits(getAltitude()) - == Float.floatToIntBits( - other.getAltitude())); - result = result && ( - Float.floatToIntBits(getAccuracy()) - == Float.floatToIntBits( - other.getAccuracy())); - result = result && ( - Float.floatToIntBits(getSpeed()) - == Float.floatToIntBits( - other.getSpeed())); - result = result && getProvider() - .equals(other.getProvider()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + LATITUDE_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getLatitude()); - hash = (37 * hash) + LONGITUDE_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getLongitude()); - hash = (37 * hash) + ALTITUDE_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getAltitude()); - hash = (37 * hash) + ACCURACY_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getAccuracy()); - hash = (37 * hash) + SPEED_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getSpeed()); - hash = (37 * hash) + PROVIDER_FIELD_NUMBER; - hash = (53 * hash) + getProvider().hashCode(); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static GNSS_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static GNSS_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static GNSS_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static GNSS_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static GNSS_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static GNSS_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static GNSS_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static GNSS_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static GNSS_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static GNSS_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(GNSS_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code GNSS_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:GNSS_Sample) - GNSS_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_GNSS_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_GNSS_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - GNSS_Sample.class, Builder.class); - } - - // Construct using Traj.GNSS_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - latitude_ = 0F; - - longitude_ = 0F; - - altitude_ = 0F; - - accuracy_ = 0F; - - speed_ = 0F; - - provider_ = ""; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_GNSS_Sample_descriptor; - } - - public GNSS_Sample getDefaultInstanceForType() { - return GNSS_Sample.getDefaultInstance(); - } - - public GNSS_Sample build() { - GNSS_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public GNSS_Sample buildPartial() { - GNSS_Sample result = new GNSS_Sample(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.latitude_ = latitude_; - result.longitude_ = longitude_; - result.altitude_ = altitude_; - result.accuracy_ = accuracy_; - result.speed_ = speed_; - result.provider_ = provider_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof GNSS_Sample) { - return mergeFrom((GNSS_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(GNSS_Sample other) { - if (other == GNSS_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getLatitude() != 0F) { - setLatitude(other.getLatitude()); - } - if (other.getLongitude() != 0F) { - setLongitude(other.getLongitude()); - } - if (other.getAltitude() != 0F) { - setAltitude(other.getAltitude()); - } - if (other.getAccuracy() != 0F) { - setAccuracy(other.getAccuracy()); - } - if (other.getSpeed() != 0F) { - setSpeed(other.getSpeed()); - } - if (!other.getProvider().isEmpty()) { - provider_ = other.provider_; - onChanged(); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - GNSS_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (GNSS_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private float latitude_ ; - /** - *
-       * degrees (minimum 6 significant figures)
-       * latitude between -90 and 90
-       * 
- * - * optional float latitude = 2; - */ - public float getLatitude() { - return latitude_; - } - /** - *
-       * degrees (minimum 6 significant figures)
-       * latitude between -90 and 90
-       * 
- * - * optional float latitude = 2; - */ - public Builder setLatitude(float value) { - - latitude_ = value; - onChanged(); - return this; - } - /** - *
-       * degrees (minimum 6 significant figures)
-       * latitude between -90 and 90
-       * 
- * - * optional float latitude = 2; - */ - public Builder clearLatitude() { - - latitude_ = 0F; - onChanged(); - return this; - } - - private float longitude_ ; - /** - *
-       * longitude between -180 and 180
-       * 
- * - * optional float longitude = 3; - */ - public float getLongitude() { - return longitude_; - } - /** - *
-       * longitude between -180 and 180
-       * 
- * - * optional float longitude = 3; - */ - public Builder setLongitude(float value) { - - longitude_ = value; - onChanged(); - return this; - } - /** - *
-       * longitude between -180 and 180
-       * 
- * - * optional float longitude = 3; - */ - public Builder clearLongitude() { - - longitude_ = 0F; - onChanged(); - return this; - } - - private float altitude_ ; - /** - *
-       *metres
-       * 
- * - * optional float altitude = 4; - */ - public float getAltitude() { - return altitude_; - } - /** - *
-       *metres
-       * 
- * - * optional float altitude = 4; - */ - public Builder setAltitude(float value) { - - altitude_ = value; - onChanged(); - return this; - } - /** - *
-       *metres
-       * 
- * - * optional float altitude = 4; - */ - public Builder clearAltitude() { - - altitude_ = 0F; - onChanged(); - return this; - } - - private float accuracy_ ; - /** - *
-       * metres
-       * 
- * - * optional float accuracy = 5; - */ - public float getAccuracy() { - return accuracy_; - } - /** - *
-       * metres
-       * 
- * - * optional float accuracy = 5; - */ - public Builder setAccuracy(float value) { - - accuracy_ = value; - onChanged(); - return this; - } - /** - *
-       * metres
-       * 
- * - * optional float accuracy = 5; - */ - public Builder clearAccuracy() { - - accuracy_ = 0F; - onChanged(); - return this; - } - - private float speed_ ; - /** - *
-       * m/s
-       * 
- * - * optional float speed = 6; - */ - public float getSpeed() { - return speed_; - } - /** - *
-       * m/s
-       * 
- * - * optional float speed = 6; - */ - public Builder setSpeed(float value) { - - speed_ = value; - onChanged(); - return this; - } - /** - *
-       * m/s
-       * 
- * - * optional float speed = 6; - */ - public Builder clearSpeed() { - - speed_ = 0F; - onChanged(); - return this; - } - - private Object provider_ = ""; - /** - *
-       * e.g 'gps' or 'network'
-       * 
- * - * optional string provider = 7; - */ - public String getProvider() { - Object ref = provider_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - provider_ = s; - return s; - } else { - return (String) ref; - } - } - /** - *
-       * e.g 'gps' or 'network'
-       * 
- * - * optional string provider = 7; - */ - public com.google.protobuf.ByteString - getProviderBytes() { - Object ref = provider_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - provider_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - *
-       * e.g 'gps' or 'network'
-       * 
- * - * optional string provider = 7; - */ - public Builder setProvider( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - provider_ = value; - onChanged(); - return this; - } - /** - *
-       * e.g 'gps' or 'network'
-       * 
- * - * optional string provider = 7; - */ - public Builder clearProvider() { - - provider_ = getDefaultInstance().getProvider(); - onChanged(); - return this; - } - /** - *
-       * e.g 'gps' or 'network'
-       * 
- * - * optional string provider = 7; - */ - public Builder setProviderBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - provider_ = value; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:GNSS_Sample) - } - - // @@protoc_insertion_point(class_scope:GNSS_Sample) - private static final GNSS_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new GNSS_Sample(); - } - - public static GNSS_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public GNSS_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GNSS_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public GNSS_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface WiFi_SampleOrBuilder extends - // @@protoc_insertion_point(interface_extends:WiFi_Sample) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - * repeated .Mac_Scan mac_scans = 2; - */ - java.util.List - getMacScansList(); - /** - * repeated .Mac_Scan mac_scans = 2; - */ - Mac_Scan getMacScans(int index); - /** - * repeated .Mac_Scan mac_scans = 2; - */ - int getMacScansCount(); - /** - * repeated .Mac_Scan mac_scans = 2; - */ - java.util.List - getMacScansOrBuilderList(); - /** - * repeated .Mac_Scan mac_scans = 2; - */ - Mac_ScanOrBuilder getMacScansOrBuilder( - int index); - } - /** - * Protobuf type {@code WiFi_Sample} - */ - public static final class WiFi_Sample extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:WiFi_Sample) - WiFi_SampleOrBuilder { - // Use WiFi_Sample.newBuilder() to construct. - private WiFi_Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private WiFi_Sample() { - relativeTimestamp_ = 0L; - macScans_ = java.util.Collections.emptyList(); - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private WiFi_Sample( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 18: { - if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - macScans_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000002; - } - macScans_.add( - input.readMessage(Mac_Scan.parser(), extensionRegistry)); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - macScans_ = java.util.Collections.unmodifiableList(macScans_); - } - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_WiFi_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_WiFi_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - WiFi_Sample.class, Builder.class); - } - - private int bitField0_; - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int MAC_SCANS_FIELD_NUMBER = 2; - private java.util.List macScans_; - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public java.util.List getMacScansList() { - return macScans_; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public java.util.List - getMacScansOrBuilderList() { - return macScans_; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public int getMacScansCount() { - return macScans_.size(); - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_Scan getMacScans(int index) { - return macScans_.get(index); - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_ScanOrBuilder getMacScansOrBuilder( - int index) { - return macScans_.get(index); - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - for (int i = 0; i < macScans_.size(); i++) { - output.writeMessage(2, macScans_.get(i)); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - for (int i = 0; i < macScans_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, macScans_.get(i)); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof WiFi_Sample)) { - return super.equals(obj); - } - WiFi_Sample other = (WiFi_Sample) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && getMacScansList() - .equals(other.getMacScansList()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - if (getMacScansCount() > 0) { - hash = (37 * hash) + MAC_SCANS_FIELD_NUMBER; - hash = (53 * hash) + getMacScansList().hashCode(); - } - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static WiFi_Sample parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static WiFi_Sample parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static WiFi_Sample parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static WiFi_Sample parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static WiFi_Sample parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static WiFi_Sample parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static WiFi_Sample parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static WiFi_Sample parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static WiFi_Sample parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static WiFi_Sample parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(WiFi_Sample prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code WiFi_Sample} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:WiFi_Sample) - WiFi_SampleOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_WiFi_Sample_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_WiFi_Sample_fieldAccessorTable - .ensureFieldAccessorsInitialized( - WiFi_Sample.class, Builder.class); - } - - // Construct using Traj.WiFi_Sample.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - getMacScansFieldBuilder(); - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - if (macScansBuilder_ == null) { - macScans_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - } else { - macScansBuilder_.clear(); - } - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_WiFi_Sample_descriptor; - } - - public WiFi_Sample getDefaultInstanceForType() { - return WiFi_Sample.getDefaultInstance(); - } - - public WiFi_Sample build() { - WiFi_Sample result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public WiFi_Sample buildPartial() { - WiFi_Sample result = new WiFi_Sample(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - result.relativeTimestamp_ = relativeTimestamp_; - if (macScansBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002)) { - macScans_ = java.util.Collections.unmodifiableList(macScans_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.macScans_ = macScans_; - } else { - result.macScans_ = macScansBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof WiFi_Sample) { - return mergeFrom((WiFi_Sample)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(WiFi_Sample other) { - if (other == WiFi_Sample.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (macScansBuilder_ == null) { - if (!other.macScans_.isEmpty()) { - if (macScans_.isEmpty()) { - macScans_ = other.macScans_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureMacScansIsMutable(); - macScans_.addAll(other.macScans_); - } - onChanged(); - } - } else { - if (!other.macScans_.isEmpty()) { - if (macScansBuilder_.isEmpty()) { - macScansBuilder_.dispose(); - macScansBuilder_ = null; - macScans_ = other.macScans_; - bitField0_ = (bitField0_ & ~0x00000002); - macScansBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getMacScansFieldBuilder() : null; - } else { - macScansBuilder_.addAllMessages(other.macScans_); - } - } - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - WiFi_Sample parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (WiFi_Sample) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private java.util.List macScans_ = - java.util.Collections.emptyList(); - private void ensureMacScansIsMutable() { - if (!((bitField0_ & 0x00000002) == 0x00000002)) { - macScans_ = new java.util.ArrayList(macScans_); - bitField0_ |= 0x00000002; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - Mac_Scan, Mac_Scan.Builder, Mac_ScanOrBuilder> macScansBuilder_; - - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public java.util.List getMacScansList() { - if (macScansBuilder_ == null) { - return java.util.Collections.unmodifiableList(macScans_); - } else { - return macScansBuilder_.getMessageList(); - } - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public int getMacScansCount() { - if (macScansBuilder_ == null) { - return macScans_.size(); - } else { - return macScansBuilder_.getCount(); - } - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_Scan getMacScans(int index) { - if (macScansBuilder_ == null) { - return macScans_.get(index); - } else { - return macScansBuilder_.getMessage(index); - } - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder setMacScans( - int index, Mac_Scan value) { - if (macScansBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMacScansIsMutable(); - macScans_.set(index, value); - onChanged(); - } else { - macScansBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder setMacScans( - int index, Mac_Scan.Builder builderForValue) { - if (macScansBuilder_ == null) { - ensureMacScansIsMutable(); - macScans_.set(index, builderForValue.build()); - onChanged(); - } else { - macScansBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder addMacScans(Mac_Scan value) { - if (macScansBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMacScansIsMutable(); - macScans_.add(value); - onChanged(); - } else { - macScansBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder addMacScans( - int index, Mac_Scan value) { - if (macScansBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMacScansIsMutable(); - macScans_.add(index, value); - onChanged(); - } else { - macScansBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder addMacScans( - Mac_Scan.Builder builderForValue) { - if (macScansBuilder_ == null) { - ensureMacScansIsMutable(); - macScans_.add(builderForValue.build()); - onChanged(); - } else { - macScansBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder addMacScans( - int index, Mac_Scan.Builder builderForValue) { - if (macScansBuilder_ == null) { - ensureMacScansIsMutable(); - macScans_.add(index, builderForValue.build()); - onChanged(); - } else { - macScansBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder addAllMacScans( - Iterable values) { - if (macScansBuilder_ == null) { - ensureMacScansIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, macScans_); - onChanged(); - } else { - macScansBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder clearMacScans() { - if (macScansBuilder_ == null) { - macScans_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - } else { - macScansBuilder_.clear(); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Builder removeMacScans(int index) { - if (macScansBuilder_ == null) { - ensureMacScansIsMutable(); - macScans_.remove(index); - onChanged(); - } else { - macScansBuilder_.remove(index); - } - return this; - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_Scan.Builder getMacScansBuilder( - int index) { - return getMacScansFieldBuilder().getBuilder(index); - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_ScanOrBuilder getMacScansOrBuilder( - int index) { - if (macScansBuilder_ == null) { - return macScans_.get(index); } else { - return macScansBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public java.util.List - getMacScansOrBuilderList() { - if (macScansBuilder_ != null) { - return macScansBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(macScans_); - } - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_Scan.Builder addMacScansBuilder() { - return getMacScansFieldBuilder().addBuilder( - Mac_Scan.getDefaultInstance()); - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public Mac_Scan.Builder addMacScansBuilder( - int index) { - return getMacScansFieldBuilder().addBuilder( - index, Mac_Scan.getDefaultInstance()); - } - /** - * repeated .Mac_Scan mac_scans = 2; - */ - public java.util.List - getMacScansBuilderList() { - return getMacScansFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - Mac_Scan, Mac_Scan.Builder, Mac_ScanOrBuilder> - getMacScansFieldBuilder() { - if (macScansBuilder_ == null) { - macScansBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - Mac_Scan, Mac_Scan.Builder, Mac_ScanOrBuilder>( - macScans_, - ((bitField0_ & 0x00000002) == 0x00000002), - getParentForChildren(), - isClean()); - macScans_ = null; - } - return macScansBuilder_; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:WiFi_Sample) - } - - // @@protoc_insertion_point(class_scope:WiFi_Sample) - private static final WiFi_Sample DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new WiFi_Sample(); - } - - public static WiFi_Sample getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public WiFi_Sample parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new WiFi_Sample(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public WiFi_Sample getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Mac_ScanOrBuilder extends - // @@protoc_insertion_point(interface_extends:Mac_Scan) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 relative_timestamp = 1; - */ - long getRelativeTimestamp(); - - /** - *
-     * Integer encoding of the hex mac address
-     * e.g. 207394925843984
-     * 
- * - * optional int64 mac = 2; - */ - long getMac(); - - /** - *
-     * rssi integer in dBm.
-     * typically between -120 and -10
-     * 
- * - * optional int32 rssi = 3; - */ - int getRssi(); - } - /** - * Protobuf type {@code Mac_Scan} - */ - public static final class Mac_Scan extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Mac_Scan) - Mac_ScanOrBuilder { - // Use Mac_Scan.newBuilder() to construct. - private Mac_Scan(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Mac_Scan() { - relativeTimestamp_ = 0L; - mac_ = 0L; - rssi_ = 0; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Mac_Scan( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - relativeTimestamp_ = input.readInt64(); - break; - } - case 16: { - - mac_ = input.readInt64(); - break; - } - case 24: { - - rssi_ = input.readInt32(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Mac_Scan_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Mac_Scan_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Mac_Scan.class, Builder.class); - } - - public static final int RELATIVE_TIMESTAMP_FIELD_NUMBER = 1; - private long relativeTimestamp_; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - - public static final int MAC_FIELD_NUMBER = 2; - private long mac_; - /** - *
-     * Integer encoding of the hex mac address
-     * e.g. 207394925843984
-     * 
- * - * optional int64 mac = 2; - */ - public long getMac() { - return mac_; - } - - public static final int RSSI_FIELD_NUMBER = 3; - private int rssi_; - /** - *
-     * rssi integer in dBm.
-     * typically between -120 and -10
-     * 
- * - * optional int32 rssi = 3; - */ - public int getRssi() { - return rssi_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (relativeTimestamp_ != 0L) { - output.writeInt64(1, relativeTimestamp_); - } - if (mac_ != 0L) { - output.writeInt64(2, mac_); - } - if (rssi_ != 0) { - output.writeInt32(3, rssi_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (relativeTimestamp_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, relativeTimestamp_); - } - if (mac_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(2, mac_); - } - if (rssi_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeInt32Size(3, rssi_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Mac_Scan)) { - return super.equals(obj); - } - Mac_Scan other = (Mac_Scan) obj; - - boolean result = true; - result = result && (getRelativeTimestamp() - == other.getRelativeTimestamp()); - result = result && (getMac() - == other.getMac()); - result = result && (getRssi() - == other.getRssi()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + RELATIVE_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getRelativeTimestamp()); - hash = (37 * hash) + MAC_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getMac()); - hash = (37 * hash) + RSSI_FIELD_NUMBER; - hash = (53 * hash) + getRssi(); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Mac_Scan parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Mac_Scan parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Mac_Scan parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Mac_Scan parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Mac_Scan parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Mac_Scan parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Mac_Scan parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Mac_Scan parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Mac_Scan parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Mac_Scan parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Mac_Scan prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Mac_Scan} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Mac_Scan) - Mac_ScanOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Mac_Scan_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Mac_Scan_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Mac_Scan.class, Builder.class); - } - - // Construct using Traj.Mac_Scan.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - relativeTimestamp_ = 0L; - - mac_ = 0L; - - rssi_ = 0; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Mac_Scan_descriptor; - } - - public Mac_Scan getDefaultInstanceForType() { - return Mac_Scan.getDefaultInstance(); - } - - public Mac_Scan build() { - Mac_Scan result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Mac_Scan buildPartial() { - Mac_Scan result = new Mac_Scan(this); - result.relativeTimestamp_ = relativeTimestamp_; - result.mac_ = mac_; - result.rssi_ = rssi_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Mac_Scan) { - return mergeFrom((Mac_Scan)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Mac_Scan other) { - if (other == Mac_Scan.getDefaultInstance()) return this; - if (other.getRelativeTimestamp() != 0L) { - setRelativeTimestamp(other.getRelativeTimestamp()); - } - if (other.getMac() != 0L) { - setMac(other.getMac()); - } - if (other.getRssi() != 0) { - setRssi(other.getRssi()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Mac_Scan parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Mac_Scan) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long relativeTimestamp_ ; - /** - * optional int64 relative_timestamp = 1; - */ - public long getRelativeTimestamp() { - return relativeTimestamp_; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder setRelativeTimestamp(long value) { - - relativeTimestamp_ = value; - onChanged(); - return this; - } - /** - * optional int64 relative_timestamp = 1; - */ - public Builder clearRelativeTimestamp() { - - relativeTimestamp_ = 0L; - onChanged(); - return this; - } - - private long mac_ ; - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 2; - */ - public long getMac() { - return mac_; - } - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 2; - */ - public Builder setMac(long value) { - - mac_ = value; - onChanged(); - return this; - } - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 2; - */ - public Builder clearMac() { - - mac_ = 0L; - onChanged(); - return this; - } - - private int rssi_ ; - /** - *
-       * rssi integer in dBm.
-       * typically between -120 and -10
-       * 
- * - * optional int32 rssi = 3; - */ - public int getRssi() { - return rssi_; - } - /** - *
-       * rssi integer in dBm.
-       * typically between -120 and -10
-       * 
- * - * optional int32 rssi = 3; - */ - public Builder setRssi(int value) { - - rssi_ = value; - onChanged(); - return this; - } - /** - *
-       * rssi integer in dBm.
-       * typically between -120 and -10
-       * 
- * - * optional int32 rssi = 3; - */ - public Builder clearRssi() { - - rssi_ = 0; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Mac_Scan) - } - - // @@protoc_insertion_point(class_scope:Mac_Scan) - private static final Mac_Scan DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Mac_Scan(); - } - - public static Mac_Scan getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Mac_Scan parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Mac_Scan(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Mac_Scan getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface AP_DataOrBuilder extends - // @@protoc_insertion_point(interface_extends:AP_Data) - com.google.protobuf.MessageOrBuilder { - - /** - *
-     * Integer encoding of the hex mac address
-     * e.g. 207394925843984
-     * 
- * - * optional int64 mac = 1; - */ - long getMac(); - - /** - *
-     * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-     * 
- * - * optional string ssid = 2; - */ - String getSsid(); - /** - *
-     * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-     * 
- * - * optional string ssid = 2; - */ - com.google.protobuf.ByteString - getSsidBytes(); - - /** - *
-     * Typically 2.4GHz or 5GHz
-     * 
- * - * optional int64 frequency = 3; - */ - long getFrequency(); - } - /** - * Protobuf type {@code AP_Data} - */ - public static final class AP_Data extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:AP_Data) - AP_DataOrBuilder { - // Use AP_Data.newBuilder() to construct. - private AP_Data(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private AP_Data() { - mac_ = 0L; - ssid_ = ""; - frequency_ = 0L; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private AP_Data( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 8: { - - mac_ = input.readInt64(); - break; - } - case 18: { - String s = input.readStringRequireUtf8(); - - ssid_ = s; - break; - } - case 24: { - - frequency_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_AP_Data_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_AP_Data_fieldAccessorTable - .ensureFieldAccessorsInitialized( - AP_Data.class, Builder.class); - } - - public static final int MAC_FIELD_NUMBER = 1; - private long mac_; - /** - *
-     * Integer encoding of the hex mac address
-     * e.g. 207394925843984
-     * 
- * - * optional int64 mac = 1; - */ - public long getMac() { - return mac_; - } - - public static final int SSID_FIELD_NUMBER = 2; - private volatile Object ssid_; - /** - *
-     * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-     * 
- * - * optional string ssid = 2; - */ - public String getSsid() { - Object ref = ssid_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - ssid_ = s; - return s; - } - } - /** - *
-     * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-     * 
- * - * optional string ssid = 2; - */ - public com.google.protobuf.ByteString - getSsidBytes() { - Object ref = ssid_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - ssid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int FREQUENCY_FIELD_NUMBER = 3; - private long frequency_; - /** - *
-     * Typically 2.4GHz or 5GHz
-     * 
- * - * optional int64 frequency = 3; - */ - public long getFrequency() { - return frequency_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (mac_ != 0L) { - output.writeInt64(1, mac_); - } - if (!getSsidBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 2, ssid_); - } - if (frequency_ != 0L) { - output.writeInt64(3, frequency_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (mac_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(1, mac_); - } - if (!getSsidBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, ssid_); - } - if (frequency_ != 0L) { - size += com.google.protobuf.CodedOutputStream - .computeInt64Size(3, frequency_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof AP_Data)) { - return super.equals(obj); - } - AP_Data other = (AP_Data) obj; - - boolean result = true; - result = result && (getMac() - == other.getMac()); - result = result && getSsid() - .equals(other.getSsid()); - result = result && (getFrequency() - == other.getFrequency()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + MAC_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getMac()); - hash = (37 * hash) + SSID_FIELD_NUMBER; - hash = (53 * hash) + getSsid().hashCode(); - hash = (37 * hash) + FREQUENCY_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getFrequency()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static AP_Data parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static AP_Data parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static AP_Data parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static AP_Data parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static AP_Data parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static AP_Data parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static AP_Data parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static AP_Data parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static AP_Data parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static AP_Data parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(AP_Data prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code AP_Data} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:AP_Data) - AP_DataOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_AP_Data_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_AP_Data_fieldAccessorTable - .ensureFieldAccessorsInitialized( - AP_Data.class, Builder.class); - } - - // Construct using Traj.AP_Data.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - mac_ = 0L; - - ssid_ = ""; - - frequency_ = 0L; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_AP_Data_descriptor; - } - - public AP_Data getDefaultInstanceForType() { - return AP_Data.getDefaultInstance(); - } - - public AP_Data build() { - AP_Data result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public AP_Data buildPartial() { - AP_Data result = new AP_Data(this); - result.mac_ = mac_; - result.ssid_ = ssid_; - result.frequency_ = frequency_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof AP_Data) { - return mergeFrom((AP_Data)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(AP_Data other) { - if (other == AP_Data.getDefaultInstance()) return this; - if (other.getMac() != 0L) { - setMac(other.getMac()); - } - if (!other.getSsid().isEmpty()) { - ssid_ = other.ssid_; - onChanged(); - } - if (other.getFrequency() != 0L) { - setFrequency(other.getFrequency()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - AP_Data parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (AP_Data) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private long mac_ ; - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 1; - */ - public long getMac() { - return mac_; - } - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 1; - */ - public Builder setMac(long value) { - - mac_ = value; - onChanged(); - return this; - } - /** - *
-       * Integer encoding of the hex mac address
-       * e.g. 207394925843984
-       * 
- * - * optional int64 mac = 1; - */ - public Builder clearMac() { - - mac_ = 0L; - onChanged(); - return this; - } - - private Object ssid_ = ""; - /** - *
-       * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-       * 
- * - * optional string ssid = 2; - */ - public String getSsid() { - Object ref = ssid_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - ssid_ = s; - return s; - } else { - return (String) ref; - } - } - /** - *
-       * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-       * 
- * - * optional string ssid = 2; - */ - public com.google.protobuf.ByteString - getSsidBytes() { - Object ref = ssid_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - ssid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - *
-       * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-       * 
- * - * optional string ssid = 2; - */ - public Builder setSsid( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - ssid_ = value; - onChanged(); - return this; - } - /** - *
-       * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-       * 
- * - * optional string ssid = 2; - */ - public Builder clearSsid() { - - ssid_ = getDefaultInstance().getSsid(); - onChanged(); - return this; - } - /** - *
-       * E.g. 'Eduroam' or 'Starbucks_free_wifi'
-       * 
- * - * optional string ssid = 2; - */ - public Builder setSsidBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - ssid_ = value; - onChanged(); - return this; - } - - private long frequency_ ; - /** - *
-       * Typically 2.4GHz or 5GHz
-       * 
- * - * optional int64 frequency = 3; - */ - public long getFrequency() { - return frequency_; - } - /** - *
-       * Typically 2.4GHz or 5GHz
-       * 
- * - * optional int64 frequency = 3; - */ - public Builder setFrequency(long value) { - - frequency_ = value; - onChanged(); - return this; - } - /** - *
-       * Typically 2.4GHz or 5GHz
-       * 
- * - * optional int64 frequency = 3; - */ - public Builder clearFrequency() { - - frequency_ = 0L; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:AP_Data) - } - - // @@protoc_insertion_point(class_scope:AP_Data) - private static final AP_Data DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new AP_Data(); - } - - public static AP_Data getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public AP_Data parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new AP_Data(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public AP_Data getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - public interface Sensor_InfoOrBuilder extends - // @@protoc_insertion_point(interface_extends:Sensor_Info) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string name = 1; - */ - String getName(); - /** - * optional string name = 1; - */ - com.google.protobuf.ByteString - getNameBytes(); - - /** - * optional string vendor = 2; - */ - String getVendor(); - /** - * optional string vendor = 2; - */ - com.google.protobuf.ByteString - getVendorBytes(); - - /** - * optional float resolution = 3; - */ - float getResolution(); - - /** - * optional float power = 4; - */ - float getPower(); - - /** - * optional int32 version = 5; - */ - int getVersion(); - - /** - * optional int32 type = 6; - */ - int getType(); - } - /** - * Protobuf type {@code Sensor_Info} - */ - public static final class Sensor_Info extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:Sensor_Info) - Sensor_InfoOrBuilder { - // Use Sensor_Info.newBuilder() to construct. - private Sensor_Info(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private Sensor_Info() { - name_ = ""; - vendor_ = ""; - resolution_ = 0F; - power_ = 0F; - version_ = 0; - type_ = 0; - } - - @Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - private Sensor_Info( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!input.skipField(tag)) { - done = true; - } - break; - } - case 10: { - String s = input.readStringRequireUtf8(); - - name_ = s; - break; - } - case 18: { - String s = input.readStringRequireUtf8(); - - vendor_ = s; - break; - } - case 29: { - - resolution_ = input.readFloat(); - break; - } - case 37: { - - power_ = input.readFloat(); - break; - } - case 40: { - - version_ = input.readInt32(); - break; - } - case 48: { - - type_ = input.readInt32(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Sensor_Info_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Sensor_Info_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Sensor_Info.class, Builder.class); - } - - public static final int NAME_FIELD_NUMBER = 1; - private volatile Object name_; - /** - * optional string name = 1; - */ - public String getName() { - Object ref = name_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - name_ = s; - return s; - } - } - /** - * optional string name = 1; - */ - public com.google.protobuf.ByteString - getNameBytes() { - Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int VENDOR_FIELD_NUMBER = 2; - private volatile Object vendor_; - /** - * optional string vendor = 2; - */ - public String getVendor() { - Object ref = vendor_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - vendor_ = s; - return s; - } - } - /** - * optional string vendor = 2; - */ - public com.google.protobuf.ByteString - getVendorBytes() { - Object ref = vendor_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - vendor_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int RESOLUTION_FIELD_NUMBER = 3; - private float resolution_; - /** - * optional float resolution = 3; - */ - public float getResolution() { - return resolution_; - } - - public static final int POWER_FIELD_NUMBER = 4; - private float power_; - /** - * optional float power = 4; - */ - public float getPower() { - return power_; - } - - public static final int VERSION_FIELD_NUMBER = 5; - private int version_; - /** - * optional int32 version = 5; - */ - public int getVersion() { - return version_; - } - - public static final int TYPE_FIELD_NUMBER = 6; - private int type_; - /** - * optional int32 type = 6; - */ - public int getType() { - return type_; - } - - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (!getNameBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_); - } - if (!getVendorBytes().isEmpty()) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 2, vendor_); - } - if (resolution_ != 0F) { - output.writeFloat(3, resolution_); - } - if (power_ != 0F) { - output.writeFloat(4, power_); - } - if (version_ != 0) { - output.writeInt32(5, version_); - } - if (type_ != 0) { - output.writeInt32(6, type_); - } - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (!getNameBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_); - } - if (!getVendorBytes().isEmpty()) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, vendor_); - } - if (resolution_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(3, resolution_); - } - if (power_ != 0F) { - size += com.google.protobuf.CodedOutputStream - .computeFloatSize(4, power_); - } - if (version_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeInt32Size(5, version_); - } - if (type_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeInt32Size(6, type_); - } - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Sensor_Info)) { - return super.equals(obj); - } - Sensor_Info other = (Sensor_Info) obj; - - boolean result = true; - result = result && getName() - .equals(other.getName()); - result = result && getVendor() - .equals(other.getVendor()); - result = result && ( - Float.floatToIntBits(getResolution()) - == Float.floatToIntBits( - other.getResolution())); - result = result && ( - Float.floatToIntBits(getPower()) - == Float.floatToIntBits( - other.getPower())); - result = result && (getVersion() - == other.getVersion()); - result = result && (getType() - == other.getType()); - return result; - } - - @Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - hash = (37 * hash) + VENDOR_FIELD_NUMBER; - hash = (53 * hash) + getVendor().hashCode(); - hash = (37 * hash) + RESOLUTION_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getResolution()); - hash = (37 * hash) + POWER_FIELD_NUMBER; - hash = (53 * hash) + Float.floatToIntBits( - getPower()); - hash = (37 * hash) + VERSION_FIELD_NUMBER; - hash = (53 * hash) + getVersion(); - hash = (37 * hash) + TYPE_FIELD_NUMBER; - hash = (53 * hash) + getType(); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static Sensor_Info parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Sensor_Info parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Sensor_Info parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static Sensor_Info parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static Sensor_Info parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Sensor_Info parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static Sensor_Info parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static Sensor_Info parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static Sensor_Info parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static Sensor_Info parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(Sensor_Info prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code Sensor_Info} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:Sensor_Info) - Sensor_InfoOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return Traj.internal_static_Sensor_Info_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return Traj.internal_static_Sensor_Info_fieldAccessorTable - .ensureFieldAccessorsInitialized( - Sensor_Info.class, Builder.class); - } - - // Construct using Traj.Sensor_Info.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - public Builder clear() { - super.clear(); - name_ = ""; - - vendor_ = ""; - - resolution_ = 0F; - - power_ = 0F; - - version_ = 0; - - type_ = 0; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return Traj.internal_static_Sensor_Info_descriptor; - } - - public Sensor_Info getDefaultInstanceForType() { - return Sensor_Info.getDefaultInstance(); - } - - public Sensor_Info build() { - Sensor_Info result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public Sensor_Info buildPartial() { - Sensor_Info result = new Sensor_Info(this); - result.name_ = name_; - result.vendor_ = vendor_; - result.resolution_ = resolution_; - result.power_ = power_; - result.version_ = version_; - result.type_ = type_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Sensor_Info) { - return mergeFrom((Sensor_Info)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(Sensor_Info other) { - if (other == Sensor_Info.getDefaultInstance()) return this; - if (!other.getName().isEmpty()) { - name_ = other.name_; - onChanged(); - } - if (!other.getVendor().isEmpty()) { - vendor_ = other.vendor_; - onChanged(); - } - if (other.getResolution() != 0F) { - setResolution(other.getResolution()); - } - if (other.getPower() != 0F) { - setPower(other.getPower()); - } - if (other.getVersion() != 0) { - setVersion(other.getVersion()); - } - if (other.getType() != 0) { - setType(other.getType()); - } - onChanged(); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Sensor_Info parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Sensor_Info) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private Object name_ = ""; - /** - * optional string name = 1; - */ - public String getName() { - Object ref = name_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - name_ = s; - return s; - } else { - return (String) ref; - } - } - /** - * optional string name = 1; - */ - public com.google.protobuf.ByteString - getNameBytes() { - Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string name = 1; - */ - public Builder setName( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - name_ = value; - onChanged(); - return this; - } - /** - * optional string name = 1; - */ - public Builder clearName() { - - name_ = getDefaultInstance().getName(); - onChanged(); - return this; - } - /** - * optional string name = 1; - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - name_ = value; - onChanged(); - return this; - } - - private Object vendor_ = ""; - /** - * optional string vendor = 2; - */ - public String getVendor() { - Object ref = vendor_; - if (!(ref instanceof String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - vendor_ = s; - return s; - } else { - return (String) ref; - } - } - /** - * optional string vendor = 2; - */ - public com.google.protobuf.ByteString - getVendorBytes() { - Object ref = vendor_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); - vendor_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string vendor = 2; - */ - public Builder setVendor( - String value) { - if (value == null) { - throw new NullPointerException(); - } - - vendor_ = value; - onChanged(); - return this; - } - /** - * optional string vendor = 2; - */ - public Builder clearVendor() { - - vendor_ = getDefaultInstance().getVendor(); - onChanged(); - return this; - } - /** - * optional string vendor = 2; - */ - public Builder setVendorBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - vendor_ = value; - onChanged(); - return this; - } - - private float resolution_ ; - /** - * optional float resolution = 3; - */ - public float getResolution() { - return resolution_; - } - /** - * optional float resolution = 3; - */ - public Builder setResolution(float value) { - - resolution_ = value; - onChanged(); - return this; - } - /** - * optional float resolution = 3; - */ - public Builder clearResolution() { - - resolution_ = 0F; - onChanged(); - return this; - } - - private float power_ ; - /** - * optional float power = 4; - */ - public float getPower() { - return power_; - } - /** - * optional float power = 4; - */ - public Builder setPower(float value) { - - power_ = value; - onChanged(); - return this; - } - /** - * optional float power = 4; - */ - public Builder clearPower() { - - power_ = 0F; - onChanged(); - return this; - } - - private int version_ ; - /** - * optional int32 version = 5; - */ - public int getVersion() { - return version_; - } - /** - * optional int32 version = 5; - */ - public Builder setVersion(int value) { - - version_ = value; - onChanged(); - return this; - } - /** - * optional int32 version = 5; - */ - public Builder clearVersion() { - - version_ = 0; - onChanged(); - return this; - } - - private int type_ ; - /** - * optional int32 type = 6; - */ - public int getType() { - return type_; - } - /** - * optional int32 type = 6; - */ - public Builder setType(int value) { - - type_ = value; - onChanged(); - return this; - } - /** - * optional int32 type = 6; - */ - public Builder clearType() { - - type_ = 0; - onChanged(); - return this; - } - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - - // @@protoc_insertion_point(builder_scope:Sensor_Info) - } - - // @@protoc_insertion_point(class_scope:Sensor_Info) - private static final Sensor_Info DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new Sensor_Info(); - } - - public static Sensor_Info getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public Sensor_Info parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Sensor_Info(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public Sensor_Info getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Trajectory_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Trajectory_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Pdr_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Pdr_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Motion_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Motion_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Position_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Position_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Pressure_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Pressure_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Light_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Light_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_GNSS_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_GNSS_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_WiFi_Sample_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_WiFi_Sample_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Mac_Scan_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Mac_Scan_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_AP_Data_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_AP_Data_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_Sensor_Info_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_Sensor_Info_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor - getDescriptor() { - return descriptor; - } - private static com.google.protobuf.Descriptors.FileDescriptor - descriptor; - static { - String[] descriptorData = { - "\n#Cloud/app/src/main/proto/traj.proto\"\337\004" + - "\n\nTrajectory\022\027\n\017android_version\030\001 \001(\t\022 \n" + - "\010imu_data\030\002 \003(\0132\016.Motion_Sample\022\035\n\010pdr_d" + - "ata\030\003 \003(\0132\013.Pdr_Sample\022\'\n\rposition_data\030" + - "\004 \003(\0132\020.Position_Sample\022\'\n\rpressure_data" + - "\030\005 \003(\0132\020.Pressure_Sample\022!\n\nlight_data\030\006" + - " \003(\0132\r.Light_Sample\022\037\n\tgnss_data\030\007 \003(\0132\014" + - ".GNSS_Sample\022\037\n\twifi_data\030\010 \003(\0132\014.WiFi_S" + - "ample\022\032\n\010aps_data\030\t \003(\0132\010.AP_Data\022\027\n\017sta" + - "rt_timestamp\030\n \001(\003\022\027\n\017data_identifier\030\013 ", - "\001(\t\022(\n\022accelerometer_info\030\014 \001(\0132\014.Sensor" + - "_Info\022$\n\016gyroscope_info\030\r \001(\0132\014.Sensor_I" + - "nfo\022*\n\024rotation_vector_info\030\016 \001(\0132\014.Sens" + - "or_Info\022\'\n\021magnetometer_info\030\017 \001(\0132\014.Sen" + - "sor_Info\022$\n\016barometer_info\030\020 \001(\0132\014.Senso" + - "r_Info\022\'\n\021light_sensor_info\030\021 \001(\0132\014.Sens" + - "or_Info\">\n\nPdr_Sample\022\032\n\022relative_timest" + - "amp\030\001 \001(\003\022\t\n\001x\030\002 \001(\002\022\t\n\001y\030\003 \001(\002\"\205\002\n\rMoti" + - "on_Sample\022\032\n\022relative_timestamp\030\001 \001(\003\022\r\n" + - "\005acc_x\030\002 \001(\002\022\r\n\005acc_y\030\003 \001(\002\022\r\n\005acc_z\030\004 \001", - "(\002\022\r\n\005gyr_x\030\005 \001(\002\022\r\n\005gyr_y\030\006 \001(\002\022\r\n\005gyr_" + - "z\030\007 \001(\002\022\031\n\021rotation_vector_x\030\010 \001(\002\022\031\n\021ro" + - "tation_vector_y\030\t \001(\002\022\031\n\021rotation_vector" + - "_z\030\n \001(\002\022\031\n\021rotation_vector_w\030\013 \001(\002\022\022\n\ns" + - "tep_count\030\014 \001(\005\"Z\n\017Position_Sample\022\032\n\022re" + - "lative_timestamp\030\001 \001(\003\022\r\n\005mag_x\030\002 \001(\002\022\r\n" + - "\005mag_y\030\003 \001(\002\022\r\n\005mag_z\030\004 \001(\002\"?\n\017Pressure_" + - "Sample\022\032\n\022relative_timestamp\030\001 \001(\003\022\020\n\010pr" + - "essure\030\002 \001(\002\"9\n\014Light_Sample\022\032\n\022relative" + - "_timestamp\030\001 \001(\003\022\r\n\005light\030\002 \001(\002\"\223\001\n\013GNSS", - "_Sample\022\032\n\022relative_timestamp\030\001 \001(\003\022\020\n\010l" + - "atitude\030\002 \001(\002\022\021\n\tlongitude\030\003 \001(\002\022\020\n\010alti" + - "tude\030\004 \001(\002\022\020\n\010accuracy\030\005 \001(\002\022\r\n\005speed\030\006 " + - "\001(\002\022\020\n\010provider\030\007 \001(\t\"G\n\013WiFi_Sample\022\032\n\022" + - "relative_timestamp\030\001 \001(\003\022\034\n\tmac_scans\030\002 " + - "\003(\0132\t.Mac_Scan\"A\n\010Mac_Scan\022\032\n\022relative_t" + - "imestamp\030\001 \001(\003\022\013\n\003mac\030\002 \001(\003\022\014\n\004rssi\030\003 \001(" + - "\005\"7\n\007AP_Data\022\013\n\003mac\030\001 \001(\003\022\014\n\004ssid\030\002 \001(\t\022" + - "\021\n\tfrequency\030\003 \001(\003\"m\n\013Sensor_Info\022\014\n\004nam" + - "e\030\001 \001(\t\022\016\n\006vendor\030\002 \001(\t\022\022\n\nresolution\030\003 ", - "\001(\002\022\r\n\005power\030\004 \001(\002\022\017\n\007version\030\005 \001(\005\022\014\n\004t" + - "ype\030\006 \001(\005b\006proto3" - }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = - new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { - public com.google.protobuf.ExtensionRegistry assignDescriptors( - com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor - .internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] { - }, assigner); - internal_static_Trajectory_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_Trajectory_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Trajectory_descriptor, - new String[] { "AndroidVersion", "ImuData", "PdrData", "PositionData", "PressureData", "LightData", "GnssData", "WifiData", "ApsData", "StartTimestamp", "DataIdentifier", "AccelerometerInfo", "GyroscopeInfo", "RotationVectorInfo", "MagnetometerInfo", "BarometerInfo", "LightSensorInfo", }); - internal_static_Pdr_Sample_descriptor = - getDescriptor().getMessageTypes().get(1); - internal_static_Pdr_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Pdr_Sample_descriptor, - new String[] { "RelativeTimestamp", "X", "Y", }); - internal_static_Motion_Sample_descriptor = - getDescriptor().getMessageTypes().get(2); - internal_static_Motion_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Motion_Sample_descriptor, - new String[] { "RelativeTimestamp", "AccX", "AccY", "AccZ", "GyrX", "GyrY", "GyrZ", "RotationVectorX", "RotationVectorY", "RotationVectorZ", "RotationVectorW", "StepCount", }); - internal_static_Position_Sample_descriptor = - getDescriptor().getMessageTypes().get(3); - internal_static_Position_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Position_Sample_descriptor, - new String[] { "RelativeTimestamp", "MagX", "MagY", "MagZ", }); - internal_static_Pressure_Sample_descriptor = - getDescriptor().getMessageTypes().get(4); - internal_static_Pressure_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Pressure_Sample_descriptor, - new String[] { "RelativeTimestamp", "Pressure", }); - internal_static_Light_Sample_descriptor = - getDescriptor().getMessageTypes().get(5); - internal_static_Light_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Light_Sample_descriptor, - new String[] { "RelativeTimestamp", "Light", }); - internal_static_GNSS_Sample_descriptor = - getDescriptor().getMessageTypes().get(6); - internal_static_GNSS_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_GNSS_Sample_descriptor, - new String[] { "RelativeTimestamp", "Latitude", "Longitude", "Altitude", "Accuracy", "Speed", "Provider", }); - internal_static_WiFi_Sample_descriptor = - getDescriptor().getMessageTypes().get(7); - internal_static_WiFi_Sample_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_WiFi_Sample_descriptor, - new String[] { "RelativeTimestamp", "MacScans", }); - internal_static_Mac_Scan_descriptor = - getDescriptor().getMessageTypes().get(8); - internal_static_Mac_Scan_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Mac_Scan_descriptor, - new String[] { "RelativeTimestamp", "Mac", "Rssi", }); - internal_static_AP_Data_descriptor = - getDescriptor().getMessageTypes().get(9); - internal_static_AP_Data_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_AP_Data_descriptor, - new String[] { "Mac", "Ssid", "Frequency", }); - internal_static_Sensor_Info_descriptor = - getDescriptor().getMessageTypes().get(10); - internal_static_Sensor_Info_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_Sensor_Info_descriptor, - new String[] { "Name", "Vendor", "Resolution", "Power", "Version", "Type", }); - } - - // @@protoc_insertion_point(outer_class_scope) -} diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java index 2d2b1cbf..5e31dc54 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java +++ b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java @@ -8,6 +8,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.openpositioning.PositionMe.presentation.fragment.ReplayFragment; import com.openpositioning.PositionMe.sensors.SensorFusion; @@ -59,12 +60,14 @@ public class TrajParser { * Represents a single replay point containing estimated PDR position, GNSS location, * orientation, speed, and timestamp. */ + public static class ReplayPoint { - public LatLng pdrLocation; // PDR-derived location estimate - public LatLng gnssLocation; // GNSS location (may be null if unavailable) - public float orientation; // Orientation in degrees - public float speed; // Speed in meters per second - public long timestamp; // Relative timestamp + public LatLng pdrLocation; + public LatLng gnssLocation; + public float orientation; + public float speed; + public long timestamp; + public int testPointNumber; // 0 = no test point, >0 = test point marker number /** * Constructs a ReplayPoint. @@ -81,6 +84,7 @@ public ReplayPoint(LatLng pdrLocation, LatLng gnssLocation, float orientation, f this.orientation = orientation; this.speed = speed; this.timestamp = timestamp; + this.testPointNumber = 0; // Default: no test point } } @@ -92,6 +96,39 @@ private static class ImuRecord { public float rotationVectorX, rotationVectorY, rotationVectorZ, rotationVectorW; // Rotation quaternion } + /** Helper class matching the protobuf JSON nested structure for IMU data. */ + private static class ImuJsonRecord { + public long relativeTimestamp; + public Vector3Json acc; + public Vector3Json gyr; + public QuaternionJson rotationVector; + public int stepCount; + } + + private static class Vector3Json { + public float x, y, z; + } + + private static class QuaternionJson { + public float x, y, z, w; + } + + /** Helper class matching the protobuf JSON nested structure for GNSS data. */ + private static class GnssJsonRecord { + public GnssPositionJson position; + public float accuracy; + public float speed; + public float bearing; + public String provider; + } + + private static class GnssPositionJson { + public long relativeTimestamp; + public double latitude, longitude; + public double altitude; + public String floor; + } + /** Represents a Pedestrian Dead Reckoning (PDR) data record storing position shifts over time. */ private static class PdrRecord { public long relativeTimestamp; @@ -102,6 +139,131 @@ private static class PdrRecord { private static class GnssRecord { public long relativeTimestamp; public double latitude, longitude; // GNSS coordinates + public float accuracy; // GNSS accuracy in meters + } + + /** Flexible GNSSPosition holder used for correctedPositions fallback parsing. */ + private static class GnssPositionRecord { + public long relativeTimestamp; + public double latitude, longitude; + } + + /** Read array by trying multiple key variants (camelCase/snake_case). */ + private static JsonArray getArrayAny(JsonObject root, String... keys) { + for (String key : keys) { + if (root.has(key) && root.get(key).isJsonArray()) { + return root.getAsJsonArray(key); + } + } + return null; + } + + /** + * Extracts the initial recording position from a trajectory JSON file. + *

+ * This quickly reads the file to find the original recording location, using these priorities: + * 1. {@code initialPosition} field (set when the user marked their start location during recording) + * 2. First GNSS reading from {@code gnssData} (absolute GPS coordinates captured during recording) + * 3. Returns {@code null} if neither is available + *

+ * + * @param filePath Path to the trajectory JSON file. + * @return A {@link LatLng} representing the recording's initial position, or null if unavailable. + */ + public static LatLng extractInitialPosition(String filePath) { + try { + File file = new File(filePath); + if (!file.exists() || !file.canRead()) { + Log.e(TAG, "Cannot read file for initial position extraction: " + filePath); + return null; + } + + BufferedReader br = new BufferedReader(new FileReader(file)); + JsonObject root = new JsonParser().parse(br).getAsJsonObject(); + br.close(); + + // Log what top-level fields the file contains for diagnostics + Log.i(TAG, "extractInitialPosition: file has keys: " + root.keySet()); + + // Priority 1: Try "initialPosition" field (protobuf camelCase of initial_position) + if (root.has("initialPosition") && root.get("initialPosition").isJsonObject()) { + JsonObject initPos = root.getAsJsonObject("initialPosition"); + double lat = initPos.has("latitude") ? initPos.get("latitude").getAsDouble() : 0.0; + double lng = initPos.has("longitude") ? initPos.get("longitude").getAsDouble() : 0.0; + Log.i(TAG, "extractInitialPosition: initialPosition field found: lat=" + lat + ", lng=" + lng); + if (lat != 0.0 || lng != 0.0) { + return new LatLng(lat, lng); + } + } else { + Log.i(TAG, "extractInitialPosition: no 'initialPosition' field in file"); + } + + // Priority 2: Try first GNSS reading from gnssData array + if (root.has("gnssData") && root.get("gnssData").isJsonArray()) { + JsonArray gnssArray = root.getAsJsonArray("gnssData"); + Log.i(TAG, "extractInitialPosition: gnssData array has " + gnssArray.size() + " entries"); + for (int i = 0; i < gnssArray.size(); i++) { + JsonObject gnssEntry = gnssArray.get(i).getAsJsonObject(); + if (gnssEntry.has("position") && gnssEntry.get("position").isJsonObject()) { + JsonObject pos = gnssEntry.getAsJsonObject("position"); + double lat = pos.has("latitude") ? pos.get("latitude").getAsDouble() : 0.0; + double lng = pos.has("longitude") ? pos.get("longitude").getAsDouble() : 0.0; + if (lat != 0.0 || lng != 0.0) { + Log.i(TAG, "extractInitialPosition: found GNSS at index " + i + ": " + lat + ", " + lng); + return new LatLng(lat, lng); + } + } + } + Log.w(TAG, "extractInitialPosition: gnssData exists but all entries have 0,0 coordinates"); + } else { + Log.w(TAG, "extractInitialPosition: no 'gnssData' array in file"); + } + + // Priority 3: Try correctedPositions + if (root.has("correctedPositions") && root.get("correctedPositions").isJsonArray()) { + JsonArray corrected = root.getAsJsonArray("correctedPositions"); + Log.i(TAG, "extractInitialPosition: correctedPositions array has " + corrected.size() + " entries"); + if (corrected.size() > 0) { + JsonObject pos = corrected.get(0).getAsJsonObject(); + double lat = pos.has("latitude") ? pos.get("latitude").getAsDouble() : 0.0; + double lng = pos.has("longitude") ? pos.get("longitude").getAsDouble() : 0.0; + if (lat != 0.0 || lng != 0.0) { + Log.i(TAG, "extractInitialPosition: from correctedPositions: " + lat + ", " + lng); + return new LatLng(lat, lng); + } + } + } else { + Log.i(TAG, "extractInitialPosition: no 'correctedPositions' array in file"); + } + + Log.w(TAG, "No initial position found in trajectory file. " + + "Future recordings will save the start position automatically."); + } catch (Exception e) { + Log.e(TAG, "Error extracting initial position from file: " + e.getMessage(), e); + } + return null; + } + + /** + * Reads the venue floor stored in a trajectory JSON file. + * Returns the floor string (e.g. "F1", "GF") or an empty string if not found. + */ + public static String extractFloor(String filePath) { + try { + BufferedReader br = new BufferedReader(new FileReader(new File(filePath))); + JsonObject root = new JsonParser().parse(br).getAsJsonObject(); + br.close(); + if (root.has("initialPosition") && root.get("initialPosition").isJsonObject()) { + JsonObject initPos = root.getAsJsonObject("initialPosition"); + if (initPos.has("floor")) { + String floor = initPos.get("floor").getAsString(); + if (!floor.isEmpty()) return floor; + } + } + } catch (Exception e) { + Log.w(TAG, "extractFloor: could not read floor from file: " + e.getMessage()); + } + return ""; } /** @@ -147,15 +309,70 @@ public static List parseTrajectoryData(String filePath, Context con long startTimestamp = root.has("startTimestamp") ? root.get("startTimestamp").getAsLong() : 0; - List imuList = parseImuData(root.getAsJsonArray("imuData")); - List pdrList = parsePdrData(root.getAsJsonArray("pdrData")); - List gnssList = parseGnssData(root.getAsJsonArray("gnssData")); + List imuList = parseImuData(getArrayAny(root, "imuData", "imu_data")); + List pdrList = parsePdrData(getArrayAny(root, "pdrData", "pdr_data")); + List gnssList = parseGnssData(getArrayAny(root, "gnssData", "gnss_data")); + List correctedReplayTrack = parseCorrectedReplayTrack( + getArrayAny(root, "correctedPositions", "corrected_positions")); + + if (!correctedReplayTrack.isEmpty()) { + int minDenseSamples = Math.max(20, pdrList.size() / 3); + if (correctedReplayTrack.size() >= minDenseSamples) { + Log.i(TAG, "Using timestamped correctedPositions as primary replay track. Count=" + + correctedReplayTrack.size()); + return correctedReplayTrack; + } + } + +// Parse test points + List testPointTimestamps = new ArrayList<>(); // [timestamp, pointNumber] + JsonArray testPointsArray = getArrayAny(root, "testPoints", "test_points"); + if (testPointsArray != null) { + for (int i = 0; i < testPointsArray.size(); i++) { + try { + JsonObject tp = testPointsArray.get(i).getAsJsonObject(); + long ts = tp.has("relativeTimestamp") ? tp.get("relativeTimestamp").getAsLong() : 0; + testPointTimestamps.add(new long[]{ts, i + 1}); // 1-indexed point number + } catch (Exception e) { + Log.w(TAG, "Failed to parse test point " + i + ": " + e.getMessage()); + } + } + Log.i(TAG, "Parsed " + testPointTimestamps.size() + " test points"); + } + + // Fallback: some trajectories do not contain pdrData but do contain correctedPositions. + // Build replay points directly from corrected absolute positions so playback still works. + if (pdrList.isEmpty() && !correctedReplayTrack.isEmpty()) { + Log.i(TAG, "Using correctedPositions fallback. ReplayPoints count: " + correctedReplayTrack.size()); + return correctedReplayTrack; + } Log.i(TAG, "Parsed data - IMU: " + imuList.size() + " records, PDR: " + pdrList.size() + " records, GNSS: " + gnssList.size() + " records"); + // GNSS-PDR Fusion (matches real-time SimplePositionFusion) + // Replicate the drift correction that happens during real-time recording. + // Without this, the replay trajectory is pure PDR which appears smaller + // because it lacks the GNSS corrections applied in real-time. + final double METERS_PER_DEG_LAT = 111139.0; + final double metersPerDegLng = METERS_PER_DEG_LAT * Math.cos(Math.toRadians(originLat)); + double correctionX = 0.0; // Accumulated east-west drift correction (meters) + double correctionY = 0.0; // Accumulated north-south drift correction (meters) + int lastGnssIndex = -1; // Track which GNSS records have been applied + + // Detect if recording was indoors (same bounds as SimplePositionFusion.checkBuildingConstraint). + // When indoors, SimplePositionFusion rejects GNSS with accuracy > 15 m to prevent + // biased outdoor GPS fixes from pulling the trajectory outside the building. + boolean isIndoor = (originLat >= 55.92282 && originLat <= 55.92332 + && originLng >= -3.17460 && originLng <= -3.17387) // Nucleus + || (originLat >= 55.92260 && originLat <= 55.92310 + && originLng >= -3.17530 && originLng <= -3.17440); // Library + // Per-fix correction cap identical to SimplePositionFusion (1.2 m) + final double MAX_CORRECTION_PER_FIX = 1.2; + for (int i = 0; i < pdrList.size(); i++) { PdrRecord pdr = pdrList.get(i); + PdrRecord prevPdr = (i > 0) ? pdrList.get(i - 1) : pdr; ImuRecord closestImu = findClosestImuRecord(imuList, pdr.relativeTimestamp); float orientationDeg = closestImu != null ? computeOrientationFromRotationVector( @@ -176,9 +393,59 @@ public static List parseTrajectoryData(String filePath, Context con if (dt > 0) speed = (float) (distance / dt); } + // Apply GNSS drift correction: find new GNSS records up to this timestamp. + // Logic mirrors SimplePositionFusion.updateWithGNSS() exactly. + for (int g = lastGnssIndex + 1; g < gnssList.size(); g++) { + GnssRecord gnss = gnssList.get(g); + if (gnss.relativeTimestamp > pdr.relativeTimestamp) break; + + float acc = gnss.accuracy > 0 ? gnss.accuracy : 30f; + // Indoor: reject GNSS worse than 15 m (matches SimplePositionFusion indoor mode) + if (isIndoor && acc > 15) { + lastGnssIndex = g; + continue; + } + // Everywhere: reject GNSS worse than 30 m + if (acc > 30) { + lastGnssIndex = g; + continue; + } + + // Estimate PDR position at this GNSS timestamp (not always exactly at current step time). + // This avoids a systematic replay bias when GNSS updates land between two PDR points. + double pdrAtGnssX = pdr.x; + double pdrAtGnssY = pdr.y; + long dtPdr = pdr.relativeTimestamp - prevPdr.relativeTimestamp; + if (i > 0 && dtPdr > 0) { + double alpha = (gnss.relativeTimestamp - prevPdr.relativeTimestamp) / (double) dtPdr; + alpha = Math.max(0.0, Math.min(1.0, alpha)); + pdrAtGnssX = prevPdr.x + alpha * (pdr.x - prevPdr.x); + pdrAtGnssY = prevPdr.y + alpha * (pdr.y - prevPdr.y); + } + + // Where PDR+correction thinks we are at this GNSS timestamp + double pdrLat = originLat + (pdrAtGnssY + correctionY) / METERS_PER_DEG_LAT; + double pdrLng = originLng + (pdrAtGnssX + correctionX) / metersPerDegLng; + + // Error between GNSS and current fused position + double errorY = (gnss.latitude - pdrLat) * METERS_PER_DEG_LAT; + double errorX = (gnss.longitude - pdrLng) * metersPerDegLng; + + // Weight: better accuracy = more trust (matches SimplePositionFusion) + double weight = Math.min(0.5, 2.5 / acc); + + // Cap per-fix correction to 1.2 m — identical to SimplePositionFusion + double cx = errorX * weight; + double cy = errorY * weight; + correctionX += Math.max(-MAX_CORRECTION_PER_FIX, Math.min(MAX_CORRECTION_PER_FIX, cx)); + correctionY += Math.max(-MAX_CORRECTION_PER_FIX, Math.min(MAX_CORRECTION_PER_FIX, cy)); + + lastGnssIndex = g; + } - double lat = originLat + pdr.y * 1E-5; - double lng = originLng + pdr.x * 1E-5; + // Final position: start + PDR + GNSS correction (same as SimplePositionFusion) + double lat = originLat + (pdr.y + correctionY) / METERS_PER_DEG_LAT; + double lng = originLng + (pdr.x + correctionX) / metersPerDegLng; LatLng pdrLocation = new LatLng(lat, lng); GnssRecord closestGnss = findClosestGnssRecord(gnssList, pdr.relativeTimestamp); @@ -189,6 +456,27 @@ public static List parseTrajectoryData(String filePath, Context con 0f, pdr.relativeTimestamp)); } + // Mark test points on closest PDR points + for (long[] testPoint : testPointTimestamps) { + long testTimestamp = testPoint[0]; + int pointNumber = (int) testPoint[1]; + + // Find the closest replay point by timestamp + ReplayPoint closest = null; + long minDiff = Long.MAX_VALUE; + for (ReplayPoint rp : result) { + long diff = Math.abs(rp.timestamp - testTimestamp); + if (diff < minDiff) { + minDiff = diff; + closest = rp; + } + } + if (closest != null) { + closest.testPointNumber = pointNumber; + Log.i(TAG, "Test point #" + pointNumber + " mapped to timestamp " + closest.timestamp); + } + } + Collections.sort(result, Comparator.comparingLong(rp -> rp.timestamp)); Log.i(TAG, "Final ReplayPoints count: " + result.size()); @@ -199,14 +487,75 @@ public static List parseTrajectoryData(String filePath, Context con return result; } -/** Parses IMU data from JSON. */ + + private static List parseCorrectedReplayTrack(JsonArray correctedArray) { + List points = new ArrayList<>(); + if (correctedArray == null || correctedArray.size() == 0) { + return points; + } + + Gson gson = new Gson(); + for (int i = 0; i < correctedArray.size(); i++) { + try { + JsonElement el = correctedArray.get(i); + GnssPositionRecord cp = gson.fromJson(el, GnssPositionRecord.class); + if (cp == null) { + continue; + } + if (cp.latitude == 0.0 && cp.longitude == 0.0) { + continue; + } + + // Timestamped points are fused track samples captured during recording. + // Untimestamped entries are legacy/manual corrections and are ignored here. + if (cp.relativeTimestamp <= 0) { + continue; + } + + points.add(new ReplayPoint( + new LatLng(cp.latitude, cp.longitude), + null, + 0f, + 0f, + cp.relativeTimestamp)); + } catch (Exception e) { + Log.w(TAG, "Failed to parse corrected position " + i + ": " + e.getMessage()); + } + } + + Collections.sort(points, Comparator.comparingLong(rp -> rp.timestamp)); + return points; + } +/** Parses IMU data from JSON - handles protobuf nested structure. */ private static List parseImuData(JsonArray imuArray) { List imuList = new ArrayList<>(); if (imuArray == null) return imuList; Gson gson = new Gson(); for (int i = 0; i < imuArray.size(); i++) { - ImuRecord record = gson.fromJson(imuArray.get(i), ImuRecord.class); - imuList.add(record); + try { + ImuJsonRecord jsonRec = gson.fromJson(imuArray.get(i), ImuJsonRecord.class); + ImuRecord record = new ImuRecord(); + record.relativeTimestamp = jsonRec.relativeTimestamp; + if (jsonRec.acc != null) { + record.accX = jsonRec.acc.x; + record.accY = jsonRec.acc.y; + record.accZ = jsonRec.acc.z; + } + if (jsonRec.gyr != null) { + record.gyrX = jsonRec.gyr.x; + record.gyrY = jsonRec.gyr.y; + record.gyrZ = jsonRec.gyr.z; + } + if (jsonRec.rotationVector != null) { + record.rotationVectorX = jsonRec.rotationVector.x; + record.rotationVectorY = jsonRec.rotationVector.y; + record.rotationVectorZ = jsonRec.rotationVector.z; + record.rotationVectorW = jsonRec.rotationVector.w; + } + imuList.add(record); + } catch (Exception e) { + Log.w(TAG, "Failed to parse IMU record " + i + ": " + e.getMessage()); + } } return imuList; }/** Parses PDR data from JSON. */ @@ -215,18 +564,36 @@ private static List parsePdrData(JsonArray pdrArray) { if (pdrArray == null) return pdrList; Gson gson = new Gson(); for (int i = 0; i < pdrArray.size(); i++) { - PdrRecord record = gson.fromJson(pdrArray.get(i), PdrRecord.class); - pdrList.add(record); + try { + PdrRecord record = gson.fromJson(pdrArray.get(i), PdrRecord.class); + pdrList.add(record); + } catch (Exception e) { + Log.w(TAG, "Failed to parse PDR record " + i + ": " + e.getMessage()); + } } return pdrList; -}/** Parses GNSS data from JSON. */ +}/** Parses GNSS data from JSON - handles protobuf nested structure. */ private static List parseGnssData(JsonArray gnssArray) { List gnssList = new ArrayList<>(); if (gnssArray == null) return gnssList; Gson gson = new Gson(); for (int i = 0; i < gnssArray.size(); i++) { - GnssRecord record = gson.fromJson(gnssArray.get(i), GnssRecord.class); - gnssList.add(record); + try { + GnssJsonRecord jsonRec = gson.fromJson(gnssArray.get(i), GnssJsonRecord.class); + GnssRecord record = new GnssRecord(); + if (jsonRec.position != null) { + record.relativeTimestamp = jsonRec.position.relativeTimestamp; + record.latitude = jsonRec.position.latitude; + record.longitude = jsonRec.position.longitude; + } + record.accuracy = jsonRec.accuracy; + // Only add if we have valid coordinates + if (record.latitude != 0.0 || record.longitude != 0.0) { + gnssList.add(record); + } + } catch (Exception e) { + Log.w(TAG, "Failed to parse GNSS record " + i + ": " + e.getMessage()); + } } return gnssList; }/** Finds the closest IMU record to the given timestamp. */ diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/remote/ServerCommunications.java b/app/src/main/java/com/openpositioning/PositionMe/data/remote/ServerCommunications.java index 7f7e74b2..8cf865ec 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/data/remote/ServerCommunications.java +++ b/app/src/main/java/com/openpositioning/PositionMe/data/remote/ServerCommunications.java @@ -20,6 +20,7 @@ import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.SystemClock; import android.widget.Toast; import androidx.annotation.NonNull; @@ -39,13 +40,17 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; @@ -88,18 +93,46 @@ public class ServerCommunications implements Observable { // Static constants necessary for communications private static final String userKey = BuildConfig.OPENPOSITIONING_API_KEY; private static final String masterKey = BuildConfig.OPENPOSITIONING_MASTER_KEY; - private static final String uploadURL = + + private static final String DEFAULT_UPLOAD_CAMPAIGN = "murchison_house"; + private static final String uploadURLBase = + "https://openpositioning.org/api/live/trajectory/upload/"; + + // Legacy upload URL (kept for reference) + private static final String uploadURL_Legacy = "https://openpositioning.org/api/live/trajectory/upload/" + userKey + "/?key=" + masterKey; - private static final String downloadURL = + + // Base download URL - skip & limit are added dynamically per request + private static final String downloadURLBase = "https://openpositioning.org/api/live/trajectory/download/" + userKey - + "?skip=0&limit=30&key=" + masterKey; + + "?key=" + masterKey; + + // Indoor map request URL + private static final String floorPlanURL = + "https://openpositioning.org/api/live/floorplan/request?key=" + masterKey; + private static final String infoRequestURL = "https://openpositioning.org/api/live/users/trajectories/" + userKey + "?key=" + masterKey; private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data"; private static final String PROTOCOL_ACCEPT_TYPE = "application/json"; + private String buildUploadUrlForCampaign(String campaign) { + String safeCampaign = campaign == null ? "" : campaign.trim(); + if (safeCampaign.isEmpty()) { + safeCampaign = DEFAULT_UPLOAD_CAMPAIGN; + } + return uploadURLBase + safeCampaign + "/" + userKey + "/?key=" + masterKey; + } + + private String extractCampaignFromTrajectory(Traj.Trajectory trajectory) { + // Always use the default campaign registered on the server. + // Deriving a campaign name from the venue/trajectory ID produces names + // the server does not recognise, causing "Invalid campaign" errors. + return DEFAULT_UPLOAD_CAMPAIGN; + } + /** @@ -129,6 +162,33 @@ public ServerCommunications(Context context) { */ public void sendTrajectory(Traj.Trajectory trajectory){ logDataSize(trajectory); + String uploadCampaign = extractCampaignFromTrajectory(trajectory); + String uploadUrl = buildUploadUrlForCampaign(uploadCampaign); + + Log.i("ServerCommunications", "Sending trajectory: campaign=" + uploadCampaign + " size=" + trajectory.toByteArray().length + "B"); + + // WiFi Throttling Check + try { + int throttleStatus = android.provider.Settings.Global.getInt( + this.context.getContentResolver(), + "wifi_scan_throttle_enabled", + -1 + ); + if (throttleStatus > 0) { + Log.w("ServerCommunications", "WARNING: WiFi Throttling is ENABLED!"); + Log.w("ServerCommunications", "Please disable it in Developer Options for optimal data collection."); + Log.w("ServerCommunications", "Settings > Developer Options > WiFi scan throttling (disable)"); + // Show in-app toast for user awareness + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "WiFi Throttling: Check Developer Options!", Toast.LENGTH_LONG).show()); + } else if (throttleStatus == 0) { + Log.i("ServerCommunications", "WiFi Throttling is DISABLED (good for data collection)"); + } else { + Log.i("ServerCommunications", "WiFi Throttling status could not be determined"); + } + } catch (Exception e) { + Log.i("ServerCommunications", "Could not check WiFi Throttling setting: " + e.getMessage()); + } // Convert the trajectory to byte array byte[] binaryTrajectory = trajectory.toByteArray(); @@ -164,13 +224,10 @@ public void sendTrajectory(Traj.Trajectory trajectory){ // Check connections available before sending data checkNetworkStatus(); - - // Check if user preference allows for syncing with mobile data - // TODO: add sync delay and enforce settings + boolean enableMobileData = this.settings.getBoolean("mobile_sync", false); - // Check if device is connected to WiFi or to mobile data with enabled preference + if(this.isWifiConn || (enableMobileData && isMobileConn)) { - // Instantiate client for HTTP requests OkHttpClient client = new OkHttpClient(); // Creaet a equest body with a file to upload in multipart/form-data format @@ -180,17 +237,29 @@ public void sendTrajectory(Traj.Trajectory trajectory){ .build(); // Create a POST request with the required headers - Request request = new Request.Builder().url(uploadURL).post(requestBody) + Request request = new Request.Builder().url(uploadUrl).post(requestBody) .addHeader("accept", PROTOCOL_ACCEPT_TYPE) .addHeader("Content-Type", PROTOCOL_CONTENT_TYPE).build(); - + // Enqueue the request to be executed asynchronously and handle the response client.newCall(request).enqueue(new Callback() { // Handle failure to get response from the server @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); - System.err.println("Failure to get response"); + + // Detailed error logging + Log.e("ServerCommunications", "UPLOAD FAILED - Network Error"); + Log.e("ServerCommunications", "Error Type: " + e.getClass().getSimpleName()); + Log.e("ServerCommunications", "Error Message: " + e.getMessage()); + Log.e("ServerCommunications", "Error Cause: " + (e.getCause() != null ? e.getCause().toString() : "Unknown")); + + // Print full stack trace + StringWriter sw = new StringWriter(); + e.printStackTrace(new java.io.PrintWriter(sw)); + Log.e("ServerCommunications", "Stack Trace:\n" + sw.toString()); + + System.err.println("Failure to get response: " + e.getMessage()); // Delete the local file and set success to false //file.delete(); success = false; @@ -217,8 +286,20 @@ private void copyFile(File src, File dst) throws IOException { //file.delete(); // System.err.println("POST error response: " + responseBody.string()); - String errorBody = responseBody.string(); + String errorBody = responseBody != null ? responseBody.string() : "(no response body)"; infoResponse = "Upload failed: " + errorBody; + + // Detailed error logging + Log.e("ServerCommunications", "UPLOAD FAILED - HTTP Error"); + Log.e("ServerCommunications", "HTTP Status Code: " + response.code() + " " + response.message()); + Log.e("ServerCommunications", "Response Headers:"); + for (int i = 0; i < response.headers().size(); i++) { + Log.e("ServerCommunications", " " + response.headers().name(i) + ": " + response.headers().value(i)); + } + Log.e("ServerCommunications", "Response Body: " + errorBody); + Log.e("ServerCommunications", "Request URL was: " + uploadUrl); + Log.e("ServerCommunications", "File uploaded: " + file.getName() + " (" + (file.length()/1024) + " KB)"); + new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); // show error message to users @@ -227,14 +308,21 @@ private void copyFile(File src, File dst) throws IOException { notifyObservers(1); throw new IOException("Unexpected code " + response); } + + // Log successful response + Log.i("ServerCommunications", "UPLOAD SUCCESSFUL"); + Log.i("ServerCommunications", "HTTP Status: " + response.code() + " " + response.message()); // Print the response headers Headers responseHeaders = response.headers(); for (int i = 0, size = responseHeaders.size(); i < size; i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + Log.i("ServerCommunications", " Response Header: " + responseHeaders.name(i) + ": " + responseHeaders.value(i)); } // Print a confirmation of a successful POST to API - System.out.println("Successful post response: " + responseBody.string()); + String successBody = responseBody != null ? responseBody.string() : ""; + System.out.println("Successful post response: " + successBody); + Log.i("ServerCommunications", "Response Body: " + (successBody.isEmpty() ? "(empty body)" : successBody)); System.out.println("Get file: " + file.getName()); String originalPath = file.getAbsolutePath(); @@ -261,6 +349,20 @@ private void copyFile(File src, File dst) throws IOException { else { // If the device is not connected to network or allowed to send, do not send trajectory // and notify observers and user + + // Detailed network status logging + Log.e("ServerCommunications", "UPLOAD FAILED - No Network Connection"); + Log.e("ServerCommunications", "Reason:"); + if (!this.isWifiConn && !this.isMobileConn) { + Log.e("ServerCommunications", " - Device is not connected to any network (WiFi or Mobile)"); + } else if (!this.isWifiConn && this.isMobileConn && !enableMobileData) { + Log.e("ServerCommunications", " - Only Mobile data available but 'mobile_sync' setting is disabled"); + Log.e("ServerCommunications", " - Enable it in Settings > Preferences > Mobile Sync"); + } + Log.e("ServerCommunications", "WiFi Connected: " + this.isWifiConn); + Log.e("ServerCommunications", "Mobile Connected: " + this.isMobileConn); + Log.e("ServerCommunications", "Mobile Sync Enabled: " + enableMobileData); + System.err.println("No uploading allowed right now!"); success = false; notifyObservers(1); @@ -274,6 +376,7 @@ private void copyFile(File src, File dst) throws IOException { * @param localTrajectory the File object of the local trajectory to be uploaded */ public void uploadLocalTrajectory(File localTrajectory) { + String uploadUrl = buildUploadUrlForCampaign(DEFAULT_UPLOAD_CAMPAIGN); // Instantiate client for HTTP requests OkHttpClient client = new OkHttpClient(); @@ -299,7 +402,7 @@ public void uploadLocalTrajectory(File localTrajectory) { .build(); // Create a POST request with the required headers - okhttp3.Request request = new okhttp3.Request.Builder().url(uploadURL).post(requestBody) + okhttp3.Request request = new okhttp3.Request.Builder().url(uploadUrl).post(requestBody) .addHeader("accept", PROTOCOL_ACCEPT_TYPE) .addHeader("Content-Type", PROTOCOL_CONTENT_TYPE).build(); @@ -463,32 +566,54 @@ private void saveDownloadRecord(long startTimestamp, String fileName, String id, /** * Perform API request for downloading a Trajectory uploaded to the server. The trajectory is - * retrieved from a zip file, with the method accepting a position argument specifying the - * trajectory to be downloaded. The trajectory is then converted to a protobuf object and - * then to a JSON string to be downloaded to the device's Downloads folder. + * retrieved from a zip file, matching by trajectory ID to ensure the correct file is downloaded. + * The trajectory is then converted to a protobuf object and then to a JSON string to be + * downloaded to the device's Downloads folder. * - * @param position the position of the trajectory in the zip file to retrieve - * @param id the ID of the trajectory + * @param position the position of the trajectory in the UI list (used as fallback) + * @param id the ID of the trajectory (primary matching criterion) * @param dateSubmitted the date the trajectory was submitted */ public void downloadTrajectory(int position, String id, String dateSubmitted) { loadDownloadRecords(); // Load existing records from app-specific directory - // Initialise OkHttp client - OkHttpClient client = new OkHttpClient(); + // Build dynamic URL with a wider window to avoid missing the intended item + // when server ordering differs from UI assumptions. + int skip = 0; + int limit = 200; + String dynamicURL = downloadURLBase + "&skip=" + skip + "&limit=" + limit; + + // Initialise OkHttp client with longer timeout for large downloads + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS) + .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS) + .build(); // Create GET request with required header okhttp3.Request request = new okhttp3.Request.Builder() - .url(downloadURL) + .url(dynamicURL) .addHeader("accept", PROTOCOL_ACCEPT_TYPE) .get() .build(); + Log.i("ServerCommunications", "Downloading trajectory id=" + id + " url=" + dynamicURL); + + long submittedMs = -1L; + try { + submittedMs = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) + .parse(dateSubmitted).getTime(); + } catch (Exception ignored) { + } + final long submittedMsFinal = submittedMs; + // Enqueue the GET request for asynchronous execution client.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { + Log.e("ServerCommunications", "Download request failed: " + e.getMessage()); e.printStackTrace(); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Download failed: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } @Override @@ -496,37 +621,132 @@ public void onResponse(Call call, Response response) throws IOException { try (ResponseBody responseBody = response.body()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - // Extract the nth entry from the zip - InputStream inputStream = responseBody.byteStream(); - ZipInputStream zipInputStream = new ZipInputStream(inputStream); - - java.util.zip.ZipEntry zipEntry; - int zipCount = 0; - while ((zipEntry = zipInputStream.getNextEntry()) != null) { - if (zipCount == position) { - // break if zip entry position matches the desired position - break; + // Read all bytes from response + byte[] responseBytes = responseBody.bytes(); + Log.i("ServerCommunications", "Downloaded zip size: " + responseBytes.length + " bytes"); + + Traj.Trajectory receivedTrajectory = null; + String matchedEntryName = null; + boolean strongIdMatch = false; + + // Score each candidate entry using multiple signals (ID/date/time/position), + // then select the best one. This is safer than position-only fallback. + class Candidate { + final Traj.Trajectory trajectory; + final String entryName; + final int score; + final boolean strong; + + Candidate(Traj.Trajectory t, String n, int s, boolean strong) { + this.trajectory = t; + this.entryName = n; + this.score = s; + this.strong = strong; } - zipCount++; } - - // Initialise a byte array output stream - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - // Read the zipped data and write it to the byte array output stream - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = zipInputStream.read(buffer)) != -1) { - byteArrayOutputStream.write(buffer, 0, bytesRead); + Candidate best = null; + int targetZipIndex = position - skip; // index inside this zip window + + try (ZipInputStream zipInputStream = new ZipInputStream(new java.io.ByteArrayInputStream(responseBytes))) { + java.util.zip.ZipEntry zipEntry; + int zipCount = 0; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + String entryName = zipEntry.getName(); + + // Read entry bytes + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = zipInputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, bytesRead); + } + byte[] byteArray = byteArrayOutputStream.toByteArray(); + + // Parse protobuf and score candidate. + try { + Traj.Trajectory candidate = Traj.Trajectory.parseFrom(byteArray); + String candidateId = String.valueOf(candidate.getTrajectoryId()); + int score = 0; + boolean strong = false; + + boolean nameMatch = entryName.contains(id) + || entryName.contains("_" + id + "_") + || entryName.endsWith("_" + id + ".zip") + || entryName.equals(id); + if (nameMatch) { + score += 1000; + strong = true; + } + if (candidateId.equals(id)) { + score += 900; + strong = true; + } + + String dateOnly = (dateSubmitted != null && dateSubmitted.length() >= 10) + ? dateSubmitted.substring(0, 10) + : ""; + if (!dateOnly.isEmpty() && entryName.contains(dateOnly)) { + score += 250; + } + + if (zipCount == targetZipIndex) { + score += 120; + } + + long startTs = candidate.getStartTimestamp(); + if (submittedMsFinal > 0 && startTs > 0) { + long diffMin = Math.abs(startTs - submittedMsFinal) / 60000L; + score += Math.max(0, 180 - (int) diffMin); + } + + int pdrCount = candidate.getPdrDataCount(); + int correctedCount = candidate.getCorrectedPositionsCount(); + if (pdrCount > 1) score += 80; + if (correctedCount > 1) score += 20; + + if (best == null || score > best.score) { + best = new Candidate(candidate, entryName, score, strong); + } + + } catch (Exception e) { + Log.w("ServerCommunications", "Failed to parse zip entry [" + zipCount + "] as protobuf: " + e.getMessage()); + } + + zipCount++; + } } + if (best != null) { + receivedTrajectory = best.trajectory; + matchedEntryName = best.entryName; + strongIdMatch = best.strong; + Log.i("ServerCommunications", "Selected entry: " + matchedEntryName + + " score=" + best.score + " strong=" + strongIdMatch + + " pdr=" + receivedTrajectory.getPdrDataCount() + + " corrected=" + receivedTrajectory.getCorrectedPositionsCount()); + } + + if (receivedTrajectory == null) { + Log.e("ServerCommunications", "Failed to find trajectory in zip file. Requested ID=" + id); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Trajectory not found in current download window.", Toast.LENGTH_SHORT).show()); + return; + } - // Convert the byte array to protobuf - byte[] byteArray = byteArrayOutputStream.toByteArray(); - Traj.Trajectory receivedTrajectory = Traj.Trajectory.parseFrom(byteArray); + // Debug only: protobuf trajectoryId may differ from server record id. + String downloadedId = String.valueOf(receivedTrajectory.getTrajectoryId()); + if (!downloadedId.equals(id)) { + Log.w("ServerCommunications", "Server ID and protobuf trajectoryId differ. serverId=" + + id + ", protobufId=" + downloadedId + ", entry=" + matchedEntryName + + ", strongMatch=" + strongIdMatch); + } // Inspect the size of the received trajectory logDataSize(receivedTrajectory); + + Log.i("ServerCommunications", "Downloaded trajectory ID: " + receivedTrajectory.getTrajectoryId()); + Log.i("ServerCommunications", "Downloaded from entry: " + matchedEntryName); // Print a message in the console long startTimestamp = receivedTrajectory.getStartTimestamp(); @@ -543,15 +763,9 @@ public void onResponse(Call call, Response response) throws IOException { String receivedTrajectoryString = JsonFormat.printer().print(receivedTrajectory); fileWriter.write(receivedTrajectoryString); fileWriter.flush(); - System.err.println("Received trajectory stored in: " + file.getAbsolutePath()); + Log.i("ServerCommunications", "Received trajectory stored in: " + file.getAbsolutePath()); } catch (IOException ee) { - System.err.println("Trajectory download failed"); - } finally { - // Close all streams and entries to release resources - zipInputStream.closeEntry(); - byteArrayOutputStream.close(); - zipInputStream.close(); - inputStream.close(); + Log.e("ServerCommunications", "Trajectory download failed: " + ee.getMessage()); } // Save the download record @@ -592,7 +806,7 @@ public void sendInfoRequest() { response); // Get the requested information from the response body and save it in a string - // TODO: add printing to the screen somewhere + // Optionally surface this response to the UI through an observer callback. infoResponse = responseBody.string(); // Print a message in the console and notify observers System.out.println("Response received"); @@ -622,14 +836,10 @@ private void checkNetworkStatus() { private void logDataSize(Traj.Trajectory trajectory) { - Log.i("ServerCommunications", "IMU Data size: " + trajectory.getImuDataCount()); - Log.i("ServerCommunications", "Position Data size: " + trajectory.getPositionDataCount()); - Log.i("ServerCommunications", "Pressure Data size: " + trajectory.getPressureDataCount()); - Log.i("ServerCommunications", "Light Data size: " + trajectory.getLightDataCount()); - Log.i("ServerCommunications", "GNSS Data size: " + trajectory.getGnssDataCount()); - Log.i("ServerCommunications", "WiFi Data size: " + trajectory.getWifiDataCount()); - Log.i("ServerCommunications", "APS Data size: " + trajectory.getApsDataCount()); - Log.i("ServerCommunications", "PDR Data size: " + trajectory.getPdrDataCount()); + Log.i("ServerCommunications", "Trajectory data: IMU=" + trajectory.getImuDataCount() + + " GNSS=" + trajectory.getGnssDataCount() + + " WiFi=" + trajectory.getWifiFingerprintsCount() + + " PDR=" + trajectory.getPdrDataCount()); } /** @@ -664,4 +874,45 @@ else if (index == 1 && o instanceof MainActivity) { } } } -} \ No newline at end of file + + + + /** + * Request nearby indoor map data. + * Use OkHttp to send asynchronous requests. + * + * @param latitude Current latitude + * @param longitude Current longitude + * @param callback Callback interfaces are used to handle success or failure responses. + */ + public void downloadFloorPlan(double latitude, double longitude, Callback callback) { + OkHttpClient client = new OkHttpClient(); + + // Build the request body + // Construct a JSON to send latitude and longitude coordinates + JSONObject jsonBody = new JSONObject(); + try { + jsonBody.put("latitude", latitude); + jsonBody.put("longitude", longitude); + // WiFi fingerprint data can be added as needed. + } catch (Exception e) { + e.printStackTrace(); + } + + RequestBody requestBody = RequestBody.create( + MediaType.parse("application/json; charset=utf-8"), + jsonBody.toString() + ); + + // Create a POST request + Request request = new Request.Builder() + .url(floorPlanURL) + .post(requestBody) + .addHeader("accept", PROTOCOL_ACCEPT_TYPE) + .build(); + + // Execute request + client.newCall(request).enqueue(callback); + } + +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/RecordingActivity.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/RecordingActivity.java index c0d82ae2..60824c38 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/RecordingActivity.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/RecordingActivity.java @@ -1,6 +1,7 @@ package com.openpositioning.PositionMe.presentation.activity; import android.os.Bundle; +import android.util.Log; import android.view.WindowManager; import androidx.annotation.Nullable; @@ -8,85 +9,105 @@ import androidx.fragment.app.FragmentTransaction; import com.openpositioning.PositionMe.R; -import com.openpositioning.PositionMe.presentation.fragment.StartLocationFragment; -import com.openpositioning.PositionMe.presentation.fragment.RecordingFragment; import com.openpositioning.PositionMe.presentation.fragment.CorrectionFragment; - +import com.openpositioning.PositionMe.presentation.fragment.RecordingFragment; +import com.openpositioning.PositionMe.presentation.fragment.StartLocationFragment; +import com.openpositioning.PositionMe.presentation.fragment.VenueManager; /** - * The RecordingActivity manages the recording flow of the application, guiding the user through a sequence - * of screens for location selection, recording, and correction before finalizing the process. - *

- * This activity follows a structured workflow: - *

    - *
  1. StartLocationFragment - Allows users to select their starting location.
  2. - *
  3. RecordingFragment - Handles the recording process and contains a TrajectoryMapFragment.
  4. - *
  5. CorrectionFragment - Enables users to review and correct recorded data before completion.
  6. - *
- *

- * The activity ensures that the screen remains on during the recording process to prevent interruptions. - * It also provides fragment transactions for seamless navigation between different stages of the workflow. - *

- * This class is referenced in various fragments such as HomeFragment, StartLocationFragment, - * RecordingFragment, and CorrectionFragment to control navigation through the recording flow. - * - * @see StartLocationFragment The first step in the recording process where users select their starting location. - * @see RecordingFragment Handles data recording and map visualization. - * @see CorrectionFragment Allows users to review and make corrections before finalizing the process. - * @see com.openpositioning.PositionMe.R.layout#activity_recording The associated layout for this activity. - * - * @author ShuGu + * Controls the recording flow: start location -> recording -> correction. */ - public class RecordingActivity extends AppCompatActivity { + private static final String TAG = "RecordingActivity"; + + private VenueManager venueManager; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recording); + venueManager = VenueManager.getInstance(this); + + if (venueManager.hasSelectedVenue()) { + Log.d(TAG, "Recording with venue: id=" + venueManager.getSelectedVenueId() + + ", name=" + venueManager.getSelectedVenueName() + + ", floor=" + venueManager.getSelectedFloor()); + } else { + Log.d(TAG, "Recording without venue context"); + } + if (savedInstanceState == null) { - showStartLocationScreen(); // Start with the user selecting the start location + showStartLocationScreen(); } - // Keep screen on getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - /** - * Show the StartLocationFragment (beginning of flow). - */ public void showStartLocationScreen() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.mainFragmentContainer, new StartLocationFragment()); + + StartLocationFragment fragment = new StartLocationFragment(); + fragment.setArguments(createVenueBundle()); + + ft.replace(R.id.mainFragmentContainer, fragment); ft.commit(); } - /** - * Show the RecordingFragment, which contains the TrajectoryMapFragment internally. - */ public void showRecordingScreen() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.mainFragmentContainer, new RecordingFragment()); + + RecordingFragment fragment = new RecordingFragment(); + fragment.setArguments(createVenueBundle()); + + ft.replace(R.id.mainFragmentContainer, fragment); ft.addToBackStack(null); ft.commit(); } - /** - * Show the CorrectionFragment after the user stops recording. - */ public void showCorrectionScreen() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.mainFragmentContainer, new CorrectionFragment()); + + CorrectionFragment fragment = new CorrectionFragment(); + fragment.setArguments(createVenueBundle()); + + ft.replace(R.id.mainFragmentContainer, fragment); ft.addToBackStack(null); ft.commit(); } - /** - * Finish the Activity (or do any final steps) once corrections are done. - */ public void finishFlow() { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); finish(); } -} + + private Bundle createVenueBundle() { + Bundle args = new Bundle(); + args.putBoolean("has_venue", venueManager.hasSelectedVenue()); + args.putString("venue_id", venueManager.getSelectedVenueId()); + args.putString("venue_name", venueManager.getSelectedVenueName()); + args.putString("venue_floor", venueManager.getSelectedFloor()); + return args; + } + + public String getSelectedVenueId() { + return venueManager.getSelectedVenueId(); + } + + public String getSelectedVenueName() { + return venueManager.getSelectedVenueName(); + } + + public String getSelectedFloor() { + return venueManager.getSelectedFloor(); + } + + public boolean hasSelectedVenue() { + return venueManager.hasSelectedVenue(); + } + + public VenueManager getVenueManager() { + return venueManager; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/ReplayActivity.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/ReplayActivity.java index c6a30472..4f3824a2 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/ReplayActivity.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/ReplayActivity.java @@ -7,7 +7,9 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import com.google.android.gms.maps.model.LatLng; import com.openpositioning.PositionMe.R; +import com.openpositioning.PositionMe.data.local.TrajParser; import com.openpositioning.PositionMe.presentation.fragment.ReplayFragment; import com.openpositioning.PositionMe.presentation.fragment.StartLocationFragment; @@ -45,8 +47,17 @@ public class ReplayActivity extends AppCompatActivity { public static final String EXTRA_INITIAL_LAT = "extra_initial_lat"; public static final String EXTRA_INITIAL_LON = "extra_initial_lon"; public static final String EXTRA_TRAJECTORY_FILE_PATH = "extra_trajectory_file_path"; + // Keys for passing the file's original recording position to StartLocationFragment + public static final String EXTRA_FILE_INITIAL_LAT = "extra_file_initial_lat"; + public static final String EXTRA_FILE_INITIAL_LON = "extra_file_initial_lon"; + public static final String EXTRA_FLOOR = "extra_floor"; private String filePath; + // The original recording position extracted from the trajectory file + private float fileInitialLat = 0f; + private float fileInitialLon = 0f; + // The venue floor extracted from the trajectory file (e.g. "F1", "GF") + private String fileFloor = ""; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -69,21 +80,56 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { Log.e(TAG, "Trajectory file does NOT exist: " + filePath); } else { Log.i(TAG, "Trajectory file exists: " + filePath); + + // Extract the original recording position from the trajectory file + LatLng filePosition = TrajParser.extractInitialPosition(filePath); + if (filePosition != null) { + fileInitialLat = (float) filePosition.latitude; + fileInitialLon = (float) filePosition.longitude; + Log.i(TAG, "Extracted recording position from file: Lat=" + fileInitialLat + ", Lon=" + fileInitialLon); + } else { + Log.w(TAG, "No initial position found in trajectory file, will use current GPS as fallback."); + } + // Extract the venue floor from the trajectory file + fileFloor = TrajParser.extractFloor(filePath); + Log.i(TAG, "Extracted floor from file: " + (fileFloor.isEmpty() ? "(none)" : fileFloor)); } // Show StartLocationFragment first to let user pick location if (savedInstanceState == null) { - showStartLocationFragment(); + // If we successfully extracted a position from the file, skip the + // StartLocationFragment and go directly to ReplayFragment with this position. + // The StartLocationFragment is only useful when recording (to let the user + // fine-tune their start location). During replay, the recording's original + // position should be used automatically. + if (fileInitialLat != 0f || fileInitialLon != 0f) { + Log.i(TAG, "File has a valid initial position, skipping StartLocationFragment."); + showReplayFragment(filePath, fileInitialLat, fileInitialLon); + } else { + Log.w(TAG, "No initial position from file, showing StartLocationFragment."); + showStartLocationFragment(); + } } } /** * Display a StartLocationFragment to let user set their start location. - * Displays the ReplayFragment and passes the trajectory file path as an argument. + * Passes the file's original recording position so the map defaults to the correct location. */ private void showStartLocationFragment() { Log.d(TAG, "Showing StartLocationFragment..."); StartLocationFragment startLocationFragment = new StartLocationFragment(); + + // Pass the file's initial position to the fragment + if (fileInitialLat != 0f || fileInitialLon != 0f) { + Bundle args = new Bundle(); + args.putFloat(EXTRA_FILE_INITIAL_LAT, fileInitialLat); + args.putFloat(EXTRA_FILE_INITIAL_LON, fileInitialLon); + startLocationFragment.setArguments(args); + Log.d(TAG, "Passing file recording position to StartLocationFragment: " + + fileInitialLat + ", " + fileInitialLon); + } + getSupportFragmentManager() .beginTransaction() .replace(R.id.replayActivityContainer, startLocationFragment) @@ -111,6 +157,7 @@ public void showReplayFragment(String filePath, float initialLat, float initialL args.putString(EXTRA_TRAJECTORY_FILE_PATH, filePath); args.putFloat(EXTRA_INITIAL_LAT, initialLat); args.putFloat(EXTRA_INITIAL_LON, initialLon); + args.putString(EXTRA_FLOOR, fileFloor); replayFragment.setArguments(args); getSupportFragmentManager() diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/CorrectionFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/CorrectionFragment.java index 8f94cb27..019c2244 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/CorrectionFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/CorrectionFragment.java @@ -3,6 +3,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -16,29 +17,27 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; -import com.openpositioning.PositionMe.R; -import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; -import com.openpositioning.PositionMe.sensors.SensorFusion; -import com.openpositioning.PositionMe.utils.PathView; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; +import com.openpositioning.PositionMe.R; +import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; +import com.openpositioning.PositionMe.sensors.SensorFusion; +import com.openpositioning.PositionMe.utils.PathView; /** - * A simple {@link Fragment} subclass. Corrections Fragment is displayed after a recording session - * is finished to enable manual adjustments to the PDR. The adjustments are not saved as of now. + * Allows the user to manually adjust step length after a recording session. */ public class CorrectionFragment extends Fragment { - //Map variable + private static final String TAG = "CorrectionFragment"; + public GoogleMap mMap; - //Button to go to next private Button button; - //Singleton SensorFusion class - private SensorFusion sensorFusion = SensorFusion.getInstance(); + private final SensorFusion sensorFusion = SensorFusion.getInstance(); private TextView averageStepLengthText; private EditText stepLengthInput; private float averageStepLength; @@ -49,6 +48,11 @@ public class CorrectionFragment extends Fragment { private static LatLng start; private PathView pathView; + private boolean hasVenue = false; + private String venueId = ""; + private String venueName = ""; + private String venueFloor = ""; + public CorrectionFragment() { // Required empty public constructor } @@ -60,38 +64,52 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, if (activity != null && activity.getSupportActionBar() != null) { activity.getSupportActionBar().hide(); } + View rootView = inflater.inflate(R.layout.fragment_correction, container, false); - // Send trajectory data to the cloud + Bundle args = getArguments(); + if (args != null) { + hasVenue = args.getBoolean("has_venue", false); + venueId = args.getString("venue_id", ""); + venueName = args.getString("venue_name", ""); + venueFloor = args.getString("venue_floor", ""); + + if (hasVenue) { + Log.d(TAG, "Venue context received: id=" + venueId + + ", name=" + venueName + + ", floor=" + venueFloor); + } else { + Log.d(TAG, "No venue selected; recording treated as outdoor"); + } + } + sensorFusion.sendTrajectoryToCloud(); - //Obtain start position float[] startPosition = sensorFusion.getGNSSLatitude(true); - // Initialize map fragment - SupportMapFragment supportMapFragment=(SupportMapFragment) + SupportMapFragment supportMapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map); - supportMapFragment.getMapAsync(new OnMapReadyCallback() { - @Override - public void onMapReady(GoogleMap map) { - mMap = map; - mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); - mMap.getUiSettings().setCompassEnabled(true); - mMap.getUiSettings().setTiltGesturesEnabled(true); - mMap.getUiSettings().setRotateGesturesEnabled(true); - mMap.getUiSettings().setScrollGesturesEnabled(true); - - // Add a marker at the start position - start = new LatLng(startPosition[0], startPosition[1]); - mMap.addMarker(new MarkerOptions().position(start).title("Start Position")); - - // Calculate zoom for demonstration - double zoom = Math.log(156543.03392f * Math.cos(startPosition[0] * Math.PI / 180) - * scalingRatio) / Math.log(2); - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(start, (float) zoom)); - } - }); + if (supportMapFragment != null) { + supportMapFragment.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + mMap.getUiSettings().setCompassEnabled(true); + mMap.getUiSettings().setTiltGesturesEnabled(true); + mMap.getUiSettings().setRotateGesturesEnabled(true); + mMap.getUiSettings().setScrollGesturesEnabled(true); + + start = new LatLng(startPosition[0], startPosition[1]); + mMap.addMarker(new MarkerOptions().position(start).title("Start Position")); + + double zoom = Math.log(156543.03392f * Math.cos(startPosition[0] * Math.PI / 180) + * scalingRatio) / Math.log(2); + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(start, (float) zoom)); + } + }); + } return rootView; } @@ -100,19 +118,17 @@ public void onMapReady(GoogleMap map) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - this.averageStepLengthText = view.findViewById(R.id.averageStepView); - this.stepLengthInput = view.findViewById(R.id.inputStepLength); - this.pathView = view.findViewById(R.id.pathView1); + averageStepLengthText = view.findViewById(R.id.averageStepView); + stepLengthInput = view.findViewById(R.id.inputStepLength); + pathView = view.findViewById(R.id.pathView1); averageStepLength = sensorFusion.passAverageStepLength(); averageStepLengthText.setText(getString(R.string.averageStepLgn) + ": " + String.format("%.2f", averageStepLength)); - // Listen for ENTER key - this.stepLengthInput.setOnKeyListener((v, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_ENTER) { + stepLengthInput.setOnKeyListener((v, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_ENTER && changedText != null && changedText.length() > 0) { newStepLength = Float.parseFloat(changedText.toString()); - // Rescale path sensorFusion.redrawPath(newStepLength / averageStepLength); averageStepLengthText.setText(getString(R.string.averageStepLgn) + ": " + String.format("%.2f", newStepLength)); @@ -127,35 +143,36 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat return false; }); - this.stepLengthInput.addTextChangedListener(new TextWatcher() { + stepLengthInput.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count,int after) {} + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + @Override - public void onTextChanged(CharSequence s, int start, int before,int count) {} + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + @Override public void afterTextChanged(Editable s) { changedText = s; } }); - // Button to finalize corrections - this.button = view.findViewById(R.id.correction_done); - this.button.setOnClickListener(new View.OnClickListener() { + button = view.findViewById(R.id.correction_done); + button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - // ************* CHANGED CODE HERE ************* - // Before: - // NavDirections action = CorrectionFragmentDirections.actionCorrectionFragmentToHomeFragment(); - // Navigation.findNavController(view).navigate(action); - // ((AppCompatActivity)getActivity()).getSupportActionBar().show(); - - // Now, simply tell the Activity we are done: + if (hasVenue) { + Log.d(TAG, "Trajectory finalized for venue=" + venueName + ", floor=" + venueFloor); + } else { + Log.d(TAG, "Trajectory finalized without venue context"); + } ((RecordingActivity) requireActivity()).finishFlow(); } }); } public void setScalingRatio(float scalingRatio) { - this.scalingRatio = scalingRatio; + CorrectionFragment.scalingRatio = scalingRatio; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/FilesFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/FilesFragment.java index 83bc4ef1..1ed54ea3 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/FilesFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/FilesFragment.java @@ -118,7 +118,6 @@ public void onClick(View view) { new Handler(Looper.getMainLooper()).postDelayed(() -> { if (filesList.getAdapter() != null) { filesList.getAdapter().notifyDataSetChanged(); - System.out.println("RecyclerView refreshed after page load."); } }, 500); } @@ -203,19 +202,9 @@ private void updateView(List> entryList) { // Pass ID and date_submitted serverCommunications.downloadTrajectory(position, id, dateSubmitted); - -// new AlertDialog.Builder(getContext()) -// .setTitle("File downloaded") -// .setMessage("Trajectory downloaded to local storage") -// .setPositiveButton(R.string.ok, null) -// .setNegativeButton(R.string.show_storage, (dialogInterface, i) -> { -// startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)); -// }) -// .setIcon(R.drawable.ic_baseline_download_24) -// .show(); }); filesList.setAdapter(listAdapter); // Force refresh RecyclerView to ensure downloadRecords changes are detected listAdapter.notifyDataSetChanged(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/HomeFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/HomeFragment.java index 8371b04e..47e12cdf 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/HomeFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/HomeFragment.java @@ -32,27 +32,38 @@ import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; /** - * A simple {@link Fragment} subclass. The home fragment is the start screen of the application. - * The home fragment acts as a hub for all other fragments, with buttons and icons for navigation. - * The default screen when opening the application + * HomeFragment - Main screen with venue selection display * - * @see RecordingFragment - * @see FilesFragment - * @see MeasurementsFragment - * @see SettingsFragment + * Features: + * - Display current selected venue for data collection + * - Navigate to other fragments + * - Show map with current location + * + * Integration with venue selection: + * - Displays venue selected in MapsFragment + * - Updates automatically when venue changes * * @author Mate Stodulka + * @author Your Team (venue display integration) */ public class HomeFragment extends Fragment implements OnMapReadyCallback { - // Interactive UI elements to navigate to other fragments + // Interactive UI elements private MaterialButton goToInfo; private Button start; private Button measurements; private Button files; + private Button indoorButton; private TextView gnssStatusTextView; - // For the map + // Venue display elements + private androidx.cardview.widget.CardView venueCard; + private TextView venueNameTextView; + private TextView venueFloorTextView; + private MaterialButton changeVenueButton; + private MaterialButton clearVenueButton; + + // Map components private GoogleMap mMap; private SupportMapFragment mapFragment; @@ -67,7 +78,7 @@ public void onCreate(Bundle savedInstanceState) { /** * {@inheritDoc} - * Ensure the action bar is shown at the top of the screen. Set the title visible to Home. + * Ensure the action bar is shown at the top of the screen. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -79,12 +90,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, } /** - * Initialise UI elements and set onClick actions for the buttons. + * Initialize UI elements and set onClick actions */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + // Initialize existing buttons + setupNavigationButtons(view); + + // Initialize venue display elements + setupVenueDisplay(view); + + // Initialize map + setupMap(view); + } + + /** + * Setup navigation buttons + */ + private void setupNavigationButtons(View view) { // Sensor Info button goToInfo = view.findViewById(R.id.sensorInfoButton); goToInfo.setOnClickListener(v -> { @@ -116,20 +141,105 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Navigation.findNavController(v).navigate(action); }); - // TextView to display GNSS disabled message + // Indoor positioning button + indoorButton = view.findViewById(R.id.indoorButton); + if (indoorButton != null) { + indoorButton.setOnClickListener(v -> { + Navigation.findNavController(v).navigate(R.id.action_homeFragment_to_mapsFragment); + }); + } + + // GNSS status text gnssStatusTextView = view.findViewById(R.id.gnssStatusTextView); + } + + /** + * Initializes venue widgets. + */ + private void setupVenueDisplay(View view) { + venueCard = view.findViewById(R.id.venueCard); + venueNameTextView = view.findViewById(R.id.venueNameTextView); + venueFloorTextView = view.findViewById(R.id.venueFloorTextView); + changeVenueButton = view.findViewById(R.id.changeVenueButton); + clearVenueButton = view.findViewById(R.id.clearVenueButton); + + // Setup change venue button + if (changeVenueButton != null) { + changeVenueButton.setOnClickListener(v -> { + // Navigate to MapsFragment to select a different venue + Navigation.findNavController(v).navigate(R.id.action_homeFragment_to_mapsFragment); + }); + } + + // Setup clear venue button + if (clearVenueButton != null) { + clearVenueButton.setOnClickListener(v -> { + VenueManager.getInstance(requireContext()).clearVenueSelection(); + updateVenueDisplay(); + android.widget.Toast.makeText(getContext(), + "Venue selection cleared", + android.widget.Toast.LENGTH_SHORT).show(); + }); + } + + // Update venue display with current selection + updateVenueDisplay(); + } + + /** + * Refreshes the venue widgets based on current selection. + */ + private void updateVenueDisplay() { + VenueManager venueManager = VenueManager.getInstance(requireContext()); + + if (venueManager.hasSelectedVenue()) { + // Show venue information + if (venueCard != null) { + venueCard.setVisibility(View.VISIBLE); + } + + if (venueNameTextView != null) { + venueNameTextView.setText(venueManager.getSelectedVenueName()); + } + + if (venueFloorTextView != null) { + String floor = venueManager.getSelectedFloor(); + if (!floor.isEmpty()) { + venueFloorTextView.setText("Floor: " + floor); + venueFloorTextView.setVisibility(View.VISIBLE); + } else { + venueFloorTextView.setVisibility(View.GONE); + } + } + + if (changeVenueButton != null) { + changeVenueButton.setVisibility(View.VISIBLE); + } + + if (clearVenueButton != null) { + clearVenueButton.setVisibility(View.VISIBLE); + } + } else { + // No venue selected - hide the card or show "no venue" message + if (venueCard != null) { + venueCard.setVisibility(View.GONE); + } + } + } - // Locate the MapFragment nested in this fragment + /** + * Setup map fragment + */ + private void setupMap(View view) { mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragmentContainer); if (mapFragment != null) { - // Asynchronously initialize the map mapFragment.getMapAsync(this); } } /** - * Callback triggered when the Google Map is ready to be used. + * Callback triggered when the Google Map is ready */ @Override public void onMapReady(@NonNull GoogleMap googleMap) { @@ -141,22 +251,24 @@ public void onMapReady(@NonNull GoogleMap googleMap) { public void onResume() { super.onResume(); checkAndUpdatePermissions(); + + // Refresh venue display when returning to this fragment + updateVenueDisplay(); } /** - * Checks if GNSS/Location is enabled on the device. + * Check if GNSS/Location is enabled on the device */ private boolean isGnssEnabled() { LocationManager locationManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); - // Checks both GPS and network provider. Adjust as needed. boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); boolean networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); return (gpsEnabled || networkEnabled); } /** - * Move the map to the University of Edinburgh and display a message. + * Move the map to the University of Edinburgh and display a message */ private void showEdinburghAndMessage(String message) { gnssStatusTextView.setText(message); @@ -169,19 +281,18 @@ private void showEdinburghAndMessage(String message) { .title("University of Edinburgh")); } + /** + * Check and update location permissions + */ private void checkAndUpdatePermissions() { - if (mMap == null) { return; } - // Check if GNSS/Location is enabled boolean gnssEnabled = isGnssEnabled(); if (gnssEnabled) { - // Hide the "GNSS Disabled" message gnssStatusTextView.setVisibility(View.GONE); - // Check runtime permissions for location if (ActivityCompat.checkSelfPermission( requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || @@ -189,29 +300,11 @@ private void checkAndUpdatePermissions() { requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - // Enable the MyLocation layer of Google Map mMap.setMyLocationEnabled(true); - - // Optionally move the camera to last known or default location: - // (You could retrieve it from FusedLocationProvider or similar). - // Here, just leaving it on default. - // If you want to center on the user as soon as it loads, do something like: - /* - FusedLocationProviderClient fusedLocationClient = - LocationServices.getFusedLocationProviderClient(requireContext()); - fusedLocationClient.getLastLocation().addOnSuccessListener(location -> { - if (location != null) { - LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude()); - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 15f)); - } - }); - */ } else { - // If no permission, simply show a default location or prompt for permissions showEdinburghAndMessage("Permission not granted. Please enable in settings."); } } else { - // If GNSS is disabled, show University of Edinburgh + message showEdinburghAndMessage("GNSS is disabled. Please enable in settings."); } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MapsFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MapsFragment.java new file mode 100644 index 00000000..85f0e567 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MapsFragment.java @@ -0,0 +1,1085 @@ +package com.openpositioning.PositionMe.presentation.fragment; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.Polygon; +import com.google.android.gms.maps.model.PolygonOptions; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; +import com.openpositioning.PositionMe.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * MapsFragment - Indoor mapping with venue selection + * + * Features: + * - Display building outlines on map + * - Show indoor floor plans when building is selected + * - Allow user to select venue for data collection + * - Persist selected venue using VenueManager + * + * @author Your Team + */ +public class MapsFragment extends Fragment { + + private static final String TAG = "MapsFragment"; + + // Building location data structure + private static class BuildingLocation { + String name; + String apiName; + LatLng center; + double radiusMeters; + int outlineColor; + int fillColor; + float markerHue; + + BuildingLocation(String name, String apiName, LatLng center, double radiusMeters, + int outlineColor, int fillColor, float markerHue) { + this.name = name; + this.apiName = apiName; + this.center = center; + this.radiusMeters = radiusMeters; + this.outlineColor = outlineColor; + this.fillColor = fillColor; + this.markerHue = markerHue; + } + } + + // Target buildings with GPS coordinates + private static final BuildingLocation[] TARGET_BUILDINGS = { + new BuildingLocation( + "Murchison House", + "Murchison House", + new LatLng(55.92412, -3.1792), + 20.0, + Color.RED, + Color.argb(51, 255, 0, 0), + BitmapDescriptorFactory.HUE_RED + ), + new BuildingLocation( + "Noreen and Kenneth Murray Library", + "Library", + new LatLng(55.9229, -3.1750), + 10.0, + Color.GREEN, + Color.argb(51, 0, 255, 0), + BitmapDescriptorFactory.HUE_GREEN + ), + new BuildingLocation( + "The Nucleus Building", + "The Nucleus", + new LatLng(55.92301, -3.1742), + 20.0, + Color.BLUE, + Color.argb(51, 0, 0, 255), + BitmapDescriptorFactory.HUE_BLUE + ), + new BuildingLocation( + "Fleeming Jenkin Building", + "Fleeming Jenkin", + new LatLng(55.92248, -3.17299), + 20.0, + Color.MAGENTA, + Color.argb(51, 255, 0, 255), + BitmapDescriptorFactory.HUE_MAGENTA + ) + }; + + // UI and data members + private GoogleMap googleMap; + private FusedLocationProviderClient fusedLocationClient; + private final Map allBuildingsData = new HashMap<>(); + private final Map buildingPolygonMap = new HashMap<>(); + private String currentSelectedBuilding = null; + private String currentSelectedFloor = null; + private final List currentWallLines = new ArrayList<>(); + private final List currentAreaPolygons = new ArrayList<>(); + private final List currentPoiMarkers = new ArrayList<>(); + private final Map floorButtons = new HashMap<>(); + private GroundOverlay currentGroundOverlay = null; + private static final float FLOOR_IMAGE_TRANSPARENCY = 0.35f; + + // Manual overlay offsets tuned from Recording page so indoor floor images + // align with API walls in the same way on Indoor map page. + private static final double NUCLEUS_OVERLAY_LAT_OFFSET = 0.000015; + private static final double NUCLEUS_OVERLAY_LNG_OFFSET = -0.000059; + private static final double LIBRARY_OVERLAY_LAT_OFFSET = 0.000024; + private static final double LIBRARY_OVERLAY_LNG_OFFSET = 0.000057; + + private static class UprightOverlayConfig { + final LatLng center; + final float widthM; + final float bearingDeg; + + UprightOverlayConfig(LatLng center, float widthM, float bearingDeg) { + this.center = center; + this.widthM = widthM; + this.bearingDeg = bearingDeg; + } + } + + private static class FloorDelta { + final double latDelta; + final double lngDelta; + final float widthDeltaM; + final float bearingDeltaDeg; + + FloorDelta(double latDelta, double lngDelta, float widthDeltaM, float bearingDeltaDeg) { + this.latDelta = latDelta; + this.lngDelta = lngDelta; + this.widthDeltaM = widthDeltaM; + this.bearingDeltaDeg = bearingDeltaDeg; + } + } + + private static final FloorDelta ZERO_FLOOR_DELTA = new FloorDelta(0.0, 0.0, 0f, 0f); + + // UI components + private View floorSelectorContainer; + private LinearLayout floorButtonLayout; + private Button backToOutlineButton; + + // Venue selection button + private com.google.android.material.button.MaterialButton selectVenueButton; + + // Venue selection state + private String selectedVenueId = null; + private boolean isVenueSelected = false; + + private final OnMapReadyCallback callback = new OnMapReadyCallback() { + @SuppressLint("MissingPermission") + @Override + public void onMapReady(@NonNull GoogleMap map) { + googleMap = map; + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + googleMap.getUiSettings().setZoomControlsEnabled(true); + + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + googleMap.setMyLocationEnabled(true); + } + + setupBuildingClickListener(); + + // Move camera to campus center + LatLng campusCenter = new LatLng(55.9234, -3.1761); + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(campusCenter, 17f)); + + drawIndependentBuildingOutlines(); + } + }; + + /** + * Draw building outlines on the map + */ + private void drawIndependentBuildingOutlines() { + for (BuildingLocation building : TARGET_BUILDINGS) { + List circlePoints = createCircle(building.center, building.radiusMeters); + + Polygon polygon = googleMap.addPolygon(new PolygonOptions() + .addAll(circlePoints) + .strokeColor(building.outlineColor) + .strokeWidth(10f) + .fillColor(building.fillColor) + .clickable(true) + .zIndex(50)); + + buildingPolygonMap.put(polygon, building.name); + + googleMap.addMarker(new MarkerOptions() + .position(building.center) + .title(building.name) + .snippet("Click outline to view indoor map") + .icon(BitmapDescriptorFactory.defaultMarker(building.markerHue)) + .zIndex(60)); + + } + + Toast.makeText(getContext(), "Building outlines loaded", Toast.LENGTH_SHORT).show(); + } + + /** + * Create circular polygon points + */ + private List createCircle(LatLng center, double radiusMeters) { + List points = new ArrayList<>(); + int numPoints = 36; + double earthRadius = 6371000; // meters + + for (int i = 0; i < numPoints; i++) { + double angle = 2.0 * Math.PI * i / numPoints; + double dx = radiusMeters * Math.cos(angle); + double dy = radiusMeters * Math.sin(angle); + + double dLat = dy / earthRadius; + double dLon = dx / (earthRadius * Math.cos(Math.toRadians(center.latitude))); + + double newLat = center.latitude + Math.toDegrees(dLat); + double newLon = center.longitude + Math.toDegrees(dLon); + points.add(new LatLng(newLat, newLon)); + } + + return points; + } + + private void loadBuildingData(String buildingName) { + BuildingLocation selectedBuilding = null; + for (BuildingLocation building : TARGET_BUILDINGS) { + if (building.name.equals(buildingName)) { + selectedBuilding = building; + break; + } + } + + if (selectedBuilding == null) { + Toast.makeText(getContext(), "Unknown building: " + buildingName, Toast.LENGTH_SHORT).show(); + return; + } + + Toast.makeText(getContext(), "Loading indoor map for " + buildingName, Toast.LENGTH_SHORT).show(); + final BuildingLocation buildingToLoad = selectedBuilding; + + NetworkUtils.fetchFloorPlan(buildingToLoad.center.latitude, buildingToLoad.center.longitude, + new NetworkUtils.Callback() { + @Override + public void onSuccess(NetworkUtils.BuildingData data) { + if (data.floors.isEmpty()) { + Toast.makeText(getContext(), "No indoor map available for " + buildingToLoad.name, Toast.LENGTH_SHORT).show(); + Log.w(TAG, buildingToLoad.name + ": no floor data"); + return; + } + allBuildingsData.put(buildingToLoad.name, data); + showIndoorMap(buildingToLoad.name); + } + + @Override + public void onError(String error) { + Toast.makeText(getContext(), "Failed to load " + buildingToLoad.name, Toast.LENGTH_SHORT).show(); + Log.e(TAG, buildingToLoad.name + " error: " + error); + } + }); + } + + /** + * Setup click listener for building polygons + */ + private void setupBuildingClickListener() { + googleMap.setOnPolygonClickListener(polygon -> { + String buildingName = buildingPolygonMap.get(polygon); + if (buildingName != null) { + Log.d(TAG, "Building clicked: " + buildingName); + showIndoorMap(buildingName); + } + }); + } + + /** + * Display indoor map for selected building + */ + private void showIndoorMap(String buildingName) { + currentSelectedBuilding = buildingName; + NetworkUtils.BuildingData data = allBuildingsData.get(buildingName); + + if (data == null || data.floors.isEmpty()) { + loadBuildingData(buildingName); + return; + } + + // Show back button + if (backToOutlineButton != null) { + backToOutlineButton.setVisibility(View.VISIBLE); + } + + // Show venue selection button + if (selectVenueButton != null) { + selectVenueButton.setVisibility(View.VISIBLE); + selectVenueButton.setText("Select " + buildingName + " for Data Collection"); + selectVenueButton.setBackgroundTintList( + ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.md_theme_secondary))); + selectVenueButton.setTextColor(ContextCompat.getColor(requireContext(), android.R.color.white)); + } + + // Setup floor selector + setupFloorSelector(data.floors, buildingName); + + // Display first floor + List sortedFloors = sortFloorNames(new ArrayList<>(data.floors.keySet())); + if (!sortedFloors.isEmpty()) { + String firstFloor = sortedFloors.get(0); + currentSelectedFloor = firstFloor; + drawFloor(firstFloor, data); + } + + Toast.makeText(getContext(), "Indoor map loaded for " + buildingName, Toast.LENGTH_SHORT).show(); + } + + /** + * Handles venue selection from the map screen. + */ + private void handleVenueSelection() { + if (currentSelectedBuilding == null) { + Toast.makeText(getContext(), "Please select a building first", Toast.LENGTH_SHORT).show(); + return; + } + + // Save venue selection using VenueManager + VenueManager venueManager = VenueManager.getInstance(requireContext()); + + // Generate venue ID from building name + String venueId = generateVenueId(currentSelectedBuilding); + + // Get current floor or use default + String floor = currentSelectedFloor != null ? currentSelectedFloor : "Ground Floor"; + + // Save to VenueManager + venueManager.setSelectedVenue(currentSelectedBuilding, venueId, floor); + + isVenueSelected = true; + selectedVenueId = venueId; + + // Update button appearance + if (selectVenueButton != null) { + selectVenueButton.setText("Right " + currentSelectedBuilding + " Selected"); + selectVenueButton.setBackgroundTintList( + android.content.res.ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), android.R.color.holo_green_dark) + ) + ); + } + + Toast.makeText(getContext(), + "Venue selected: " + currentSelectedBuilding + " - " + floor, + Toast.LENGTH_LONG).show(); + + Log.d(TAG, "Venue selected: " + currentSelectedBuilding + " (ID: " + venueId + ", Floor: " + floor + ")"); + } + + /** + * Generate venue ID from building name + */ + private String generateVenueId(String buildingName) { + // Create a simple ID from the building name + return buildingName.toLowerCase() + .replaceAll("[^a-z0-9]", "_") + .replaceAll("_+", "_"); + } + + /** + * Return to building outline view + */ + private void returnToBuildingOutline() { + clearIndoorLayers(); + currentSelectedBuilding = null; + currentSelectedFloor = null; + + if (backToOutlineButton != null) { + backToOutlineButton.setVisibility(View.GONE); + } + + // Hide venue selection button + if (selectVenueButton != null) { + selectVenueButton.setVisibility(View.GONE); + } + + if (floorSelectorContainer != null) { + floorSelectorContainer.setVisibility(View.GONE); + } + + LatLng campusCenter = new LatLng(55.9234, -3.1761); + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(campusCenter, 17f)); + + Log.d(TAG, "Returned to building outline view"); + } + + /** + * Draw floor plan (walls, areas, POIs) + */ + private void drawFloor(String floorName, NetworkUtils.BuildingData buildingData) { + currentSelectedFloor = floorName; + clearIndoorLayers(); + + NetworkUtils.FloorData floorData = buildingData.floors.get(floorName); + if (floorData == null) { + Log.w(TAG, "Floor data not found: " + floorName); + return; + } + + // Draw configured floor image first so API walls/areas align on top. + addIndoorFloorImageOverlay(floorName, floorData, buildingData); + + // Draw walls (black lines) + for (List wall : floorData.walls) { + if (wall.size() < 2) continue; + Polyline line = googleMap.addPolyline(new PolylineOptions() + .addAll(wall) + .color(Color.BLACK) + .width(6f) + .zIndex(110)); + currentWallLines.add(line); + } + + // Draw areas (filled polygons) + for (List area : floorData.areas) { + if (area.size() < 3) continue; + Polygon poly = googleMap.addPolygon(new PolygonOptions() + .addAll(area) + .strokeColor(Color.DKGRAY) + .strokeWidth(2f) + .fillColor(Color.argb(100, 200, 200, 200)) + .zIndex(105)); + currentAreaPolygons.add(poly); + } + + // Draw POI markers + for (NetworkUtils.Poi poi : floorData.pois) { + if (poi.position != null) { + Marker marker = googleMap.addMarker(new MarkerOptions() + .position(poi.position) + .title(poi.label.isEmpty() ? poi.type : poi.label) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) + .zIndex(120)); + currentPoiMarkers.add(marker); + } + } + + // Adjust camera to show floor + adjustCameraToFloor(floorData); + + Log.d(TAG, "Floor drawn: " + floorName + + " (" + floorData.walls.size() + " walls, " + + floorData.areas.size() + " areas, " + + floorData.pois.size() + " POIs)"); + } + + /** + * Adjust camera to fit floor data + */ + private void adjustCameraToFloor(NetworkUtils.FloorData floorData) { + List allPoints = new ArrayList<>(); + + for (List wall : floorData.walls) { + allPoints.addAll(wall); + } + for (List area : floorData.areas) { + allPoints.addAll(area); + } + + if (!allPoints.isEmpty()) { + try { + LatLngBounds.Builder builder = new LatLngBounds.Builder(); + for (LatLng point : allPoints) { + builder.include(point); + } + LatLngBounds bounds = builder.build(); + googleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100)); + } catch (Exception e) { + Log.e(TAG, "Camera adjustment failed", e); + } + } + } + + /** + * Clear all indoor map layers + */ + private void clearIndoorLayers() { + for (Polyline line : currentWallLines) line.remove(); + currentWallLines.clear(); + + for (Polygon poly : currentAreaPolygons) poly.remove(); + currentAreaPolygons.clear(); + + for (Marker marker : currentPoiMarkers) marker.remove(); + currentPoiMarkers.clear(); + + if (currentGroundOverlay != null) { + currentGroundOverlay.remove(); + currentGroundOverlay = null; + } + + floorButtons.clear(); + } + + private UprightOverlayConfig withOffset(UprightOverlayConfig config, double latOffset, double lngOffset) { + return new UprightOverlayConfig( + new LatLng(config.center.latitude + latOffset, config.center.longitude + lngOffset), + config.widthM, + config.bearingDeg); + } + + private UprightOverlayConfig applyFloorDelta(UprightOverlayConfig base, FloorDelta delta) { + return new UprightOverlayConfig( + new LatLng(base.center.latitude + delta.latDelta, base.center.longitude + delta.lngDelta), + base.widthM + delta.widthDeltaM, + base.bearingDeg + delta.bearingDeltaDeg); + } + + private FloorDelta getNucleusFloorDelta(int floor) { + switch (floor) { + case -1: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 0: + return ZERO_FLOOR_DELTA; + case 1: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 2: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 3: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + default: + return ZERO_FLOOR_DELTA; + } + } + + private FloorDelta getLibraryFloorDelta(int floor) { + switch (floor) { + case 0: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 1: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 2: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 3: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + default: + return ZERO_FLOOR_DELTA; + } + } + + private void addIndoorFloorImageOverlay(String floorName, + NetworkUtils.FloorData floorData, + NetworkUtils.BuildingData buildingData) { + if (googleMap == null || currentSelectedBuilding == null) { + return; + } + + int floorNumber; + try { + floorNumber = extractFloorNumber(floorName); + } catch (Exception e) { + return; + } + + int drawableRes = resolveFloorImageResource( + currentSelectedBuilding, + floorNumber, + floorName, + buildingData != null ? buildingData.floors.keySet() : null); + if (drawableRes == 0) { + return; + } + + UprightOverlayConfig fixedConfig = getUprightOverlayConfig( + currentSelectedBuilding, + floorNumber, + floorName, + buildingData != null ? buildingData.floors.keySet() : null); + + if (fixedConfig != null) { + currentGroundOverlay = googleMap.addGroundOverlay(new GroundOverlayOptions() + .image(BitmapDescriptorFactory.fromResource(drawableRes)) + .position(fixedConfig.center, fixedConfig.widthM) + .bearing(fixedConfig.bearingDeg) + .transparency(FLOOR_IMAGE_TRANSPARENCY) + .zIndex(100f)); + return; + } + + LatLngBounds bounds = buildApiAlignedBounds(floorData, currentSelectedBuilding); + if (bounds == null) { + return; + } + + currentGroundOverlay = googleMap.addGroundOverlay(new GroundOverlayOptions() + .image(BitmapDescriptorFactory.fromResource(drawableRes)) + .positionFromBounds(bounds) + .transparency(FLOOR_IMAGE_TRANSPARENCY) + .zIndex(100f)); + } + + private UprightOverlayConfig getUprightOverlayConfig(String buildingName, + int floorNumber, + String floorName, + Set allFloorNames) { + String normalized = buildingName == null ? "" : buildingName.toLowerCase(); + + boolean isNucleus = normalized.equals("the nucleus building") + || normalized.equals("the nucleus") + || normalized.equals("nucleus"); + boolean isLibrary = normalized.equals("noreen and kenneth murray library") + || normalized.equals("library") + || normalized.contains("murray library"); + + if (isNucleus) { + UprightOverlayConfig nucleusBase = new UprightOverlayConfig( + new LatLng(55.923041, -3.174234), + 46f, + 0f); + UprightOverlayConfig tuned = applyFloorDelta(nucleusBase, getNucleusFloorDelta(floorNumber)); + return withOffset(tuned, NUCLEUS_OVERLAY_LAT_OFFSET, NUCLEUS_OVERLAY_LNG_OFFSET); + } + + if (isLibrary) { + int mappedFloor = floorNumber; + boolean hasExplicitGround = hasGroundLikeFloorLabel(allFloorNames); + if (!hasExplicitGround && mappedFloor >= 1) { + mappedFloor -= 1; + } + if (isGroundLikeLabel(floorName)) { + mappedFloor = 0; + } + + UprightOverlayConfig libraryBase = new UprightOverlayConfig( + new LatLng(55.9229, -3.1750), + 26.0f, + 0f); + UprightOverlayConfig tuned = applyFloorDelta(libraryBase, getLibraryFloorDelta(mappedFloor)); + return withOffset(tuned, LIBRARY_OVERLAY_LAT_OFFSET, LIBRARY_OVERLAY_LNG_OFFSET); + } + + return null; + } + + private int resolveFloorImageResource(String buildingName, + int floorNumber, + String floorName, + Set allFloorNames) { + String normalized = buildingName.toLowerCase(); + + boolean isNucleus = normalized.equals("the nucleus building") + || normalized.equals("the nucleus") + || normalized.equals("nucleus"); + boolean isLibrary = normalized.equals("noreen and kenneth murray library") + || normalized.equals("library") + || normalized.contains("murray library"); + + if (isNucleus) { + switch (floorNumber) { + case -1: + return R.drawable.nucleuslg; + case 0: + return R.drawable.nucleusg; + case 1: + return R.drawable.nucleus1; + case 2: + return R.drawable.nucleus2; + case 3: + return R.drawable.nucleus3; + default: + return 0; + } + } + + if (isLibrary) { + int mappedFloor = floorNumber; + boolean hasExplicitGround = hasGroundLikeFloorLabel(allFloorNames); + if (!hasExplicitGround && mappedFloor >= 1) { + mappedFloor -= 1; + } + + if (isGroundLikeLabel(floorName) || mappedFloor == 0) { + return R.drawable.libraryg; + } + + switch (mappedFloor) { + case 1: + return R.drawable.library1; + case 2: + return R.drawable.library2; + case 3: + return R.drawable.library3; + default: + return 0; + } + } + + return 0; + } + + private boolean hasGroundLikeFloorLabel(Set floorNames) { + if (floorNames == null || floorNames.isEmpty()) { + return false; + } + for (String floorName : floorNames) { + if (isGroundLikeLabel(floorName)) { + return true; + } + } + return false; + } + + private boolean isGroundLikeLabel(String floorName) { + if (floorName == null) { + return false; + } + String normalized = floorName.toLowerCase().replace("[", "").replace("]", "").trim(); + return normalized.equals("g") + || normalized.equals("gf") + || normalized.equals("ground") + || normalized.equals("ground_floor") + || normalized.equals("0"); + } + + private LatLngBounds buildApiAlignedBounds(NetworkUtils.FloorData floorData, String buildingName) { + double minLat = Double.POSITIVE_INFINITY; + double maxLat = Double.NEGATIVE_INFINITY; + double minLng = Double.POSITIVE_INFINITY; + double maxLng = Double.NEGATIVE_INFINITY; + + minLat = updateMinLatLngFromPolylines(floorData.walls, minLat, true); + maxLat = updateMaxLatLngFromPolylines(floorData.walls, maxLat, true); + minLng = updateMinLatLngFromPolylines(floorData.walls, minLng, false); + maxLng = updateMaxLatLngFromPolylines(floorData.walls, maxLng, false); + + if (!Double.isFinite(minLat) || !Double.isFinite(maxLat) + || !Double.isFinite(minLng) || !Double.isFinite(maxLng)) { + minLat = updateMinLatLngFromPolylines(floorData.areas, minLat, true); + maxLat = updateMaxLatLngFromPolylines(floorData.areas, maxLat, true); + minLng = updateMinLatLngFromPolylines(floorData.areas, minLng, false); + maxLng = updateMaxLatLngFromPolylines(floorData.areas, maxLng, false); + } + + if (!Double.isFinite(minLat) || !Double.isFinite(maxLat) + || !Double.isFinite(minLng) || !Double.isFinite(maxLng)) { + return fallbackBoundsFromBuildingCenter(buildingName); + } + + double latPad = Math.max((maxLat - minLat) * 0.03, 0.00001); + double lngPad = Math.max((maxLng - minLng) * 0.03, 0.00001); + + LatLng southWest = new LatLng(minLat - latPad, minLng - lngPad); + LatLng northEast = new LatLng(maxLat + latPad, maxLng + lngPad); + LatLngBounds apiBounds = new LatLngBounds(southWest, northEast); + return constrainBoundsToBuilding(apiBounds, buildingName); + } + + private LatLngBounds constrainBoundsToBuilding(LatLngBounds sourceBounds, String buildingName) { + LatLngBounds buildingBounds = fallbackBoundsFromBuildingCenter(buildingName); + if (buildingBounds == null) { + return sourceBounds; + } + + LatLng sw = sourceBounds.southwest; + LatLng ne = sourceBounds.northeast; + LatLng bsw = buildingBounds.southwest; + LatLng bne = buildingBounds.northeast; + + double sourceLatSpan = ne.latitude - sw.latitude; + double sourceLngSpan = ne.longitude - sw.longitude; + double buildingLatSpan = bne.latitude - bsw.latitude; + double buildingLngSpan = bne.longitude - bsw.longitude; + + if (sourceLatSpan <= 0 || sourceLngSpan <= 0 || buildingLatSpan <= 0 || buildingLngSpan <= 0) { + return buildingBounds; + } + + double targetLatSpan = Math.min(sourceLatSpan, buildingLatSpan * 0.95); + double targetLngSpan = Math.min(sourceLngSpan, buildingLngSpan * 0.95); + + double sourceCenterLat = (sw.latitude + ne.latitude) * 0.5; + double sourceCenterLng = (sw.longitude + ne.longitude) * 0.5; + + double minCenterLat = bsw.latitude + targetLatSpan * 0.5; + double maxCenterLat = bne.latitude - targetLatSpan * 0.5; + double minCenterLng = bsw.longitude + targetLngSpan * 0.5; + double maxCenterLng = bne.longitude - targetLngSpan * 0.5; + + double centerLat = Math.min(Math.max(sourceCenterLat, minCenterLat), maxCenterLat); + double centerLng = Math.min(Math.max(sourceCenterLng, minCenterLng), maxCenterLng); + + double south = centerLat - targetLatSpan * 0.5; + double north = centerLat + targetLatSpan * 0.5; + double west = centerLng - targetLngSpan * 0.5; + double east = centerLng + targetLngSpan * 0.5; + + return new LatLngBounds(new LatLng(south, west), new LatLng(north, east)); + } + + private double updateMinLatLngFromPolylines(List> groups, double currentMin, boolean latitude) { + if (groups == null) return currentMin; + double min = currentMin; + for (List group : groups) { + if (group == null) continue; + for (LatLng point : group) { + if (point == null) continue; + min = Math.min(min, latitude ? point.latitude : point.longitude); + } + } + return min; + } + + private double updateMaxLatLngFromPolylines(List> groups, double currentMax, boolean latitude) { + if (groups == null) return currentMax; + double max = currentMax; + for (List group : groups) { + if (group == null) continue; + for (LatLng point : group) { + if (point == null) continue; + max = Math.max(max, latitude ? point.latitude : point.longitude); + } + } + return max; + } + + private LatLngBounds fallbackBoundsFromBuildingCenter(String buildingName) { + for (BuildingLocation building : TARGET_BUILDINGS) { + if (!building.name.equals(buildingName)) { + continue; + } + + double radiusM = building.radiusMeters * 2.2; + double dLat = radiusM / 111000.0; + double dLng = radiusM / (111000.0 * Math.cos(Math.toRadians(building.center.latitude))); + + LatLng southWest = new LatLng(building.center.latitude - dLat, building.center.longitude - dLng); + LatLng northEast = new LatLng(building.center.latitude + dLat, building.center.longitude + dLng); + return new LatLngBounds(southWest, northEast); + } + return null; + } + + /** + * Setup floor selector UI + */ + private void setupFloorSelector(Map floors, String buildingName) { + if (floorButtonLayout == null || getContext() == null) return; + + floorSelectorContainer.setVisibility(View.VISIBLE); + floorButtonLayout.removeAllViews(); + floorButtons.clear(); + + List sortedFloors = sortFloorNames(new ArrayList<>(floors.keySet())); + + for (String floorName : sortedFloors) { + com.google.android.material.button.MaterialButton btn = + new com.google.android.material.button.MaterialButton(getContext()); + + btn.setText(floorName); + btn.setTextSize(18); + btn.setTypeface(btn.getTypeface(), android.graphics.Typeface.BOLD); + btn.setMinWidth(84); + btn.setMinimumWidth(84); + btn.setMinimumHeight(46); + btn.setInsetTop(0); + btn.setInsetBottom(0); + btn.setCornerRadius(12); + btn.setElevation(0f); + btn.setStrokeWidth(1); + btn.setLetterSpacing(0.03f); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + params.setMargins(6, 4, 6, 4); + btn.setLayoutParams(params); + + styleFloorButton(btn, floorName.equals(currentSelectedFloor)); + + btn.setOnClickListener(v -> { + NetworkUtils.BuildingData data = allBuildingsData.get(currentSelectedBuilding); + if (data != null) { + drawFloor(floorName, data); + updateFloorButtonStyles(floorName); + + // Keep selected floor synchronized across fragments. + if (isVenueSelected) { + VenueManager.getInstance(requireContext()) + .setSelectedVenue(currentSelectedBuilding, selectedVenueId, floorName); + } + } + }); + + floorButtonLayout.addView(btn); + floorButtons.put(floorName, btn); + } + + updateFloorButtonStyles(currentSelectedFloor); + + Log.d(TAG, "Floor selector: " + sortedFloors.size() + " floors"); + } + + private void updateFloorButtonStyles(String selectedFloor) { + for (Map.Entry entry : floorButtons.entrySet()) { + styleFloorButton(entry.getValue(), entry.getKey().equals(selectedFloor)); + } + } + + private void styleFloorButton(com.google.android.material.button.MaterialButton button, boolean selected) { + if (getContext() == null || button == null) return; + + if (selected) { + button.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_primary))); + button.setTextColor(ContextCompat.getColor(getContext(), R.color.md_theme_onPrimary)); + button.setStrokeColor(ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_primary))); + } else { + button.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_surfaceContainerHighest))); + button.setTextColor(ContextCompat.getColor(getContext(), R.color.md_theme_onSurface)); + button.setStrokeColor(ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_outlineVariant))); + } + } + + /** + * Sort floor names numerically + */ + private List sortFloorNames(List floorNames) { + Collections.sort(floorNames, (f1, f2) -> { + try { + int n1 = extractFloorNumber(f1); + int n2 = extractFloorNumber(f2); + return Integer.compare(n2, n1); + } catch (Exception e) { + return f1.compareTo(f2); + } + }); + return floorNames; + } + + /** + * Extract floor number from floor name + */ + private int extractFloorNumber(String floorName) { + String normalized = floorName.toLowerCase().replace("[", "").replace("]", "").trim(); + + if (normalized.isEmpty()) return 0; + + if (normalized.equals("g") || normalized.equals("gf") || normalized.equals("ground") + || normalized.equals("ground_floor") || normalized.equals("ug") + || normalized.equals("upper_ground")) { + return 0; + } + + if (normalized.equals("lg") || normalized.equals("lower_ground") + || normalized.equals("lower_ground_floor")) { + return -1; + } + + if (normalized.contains("basement")) { + Integer basementIndex = extractFirstInteger(normalized); + return basementIndex != null ? -Math.abs(basementIndex) : -1; + } + + if (normalized.matches("b\\d+")) { + return -Integer.parseInt(normalized.substring(1)); + } + + if (normalized.matches("b[-_ ]?\\d+")) { + String digits = normalized.replaceAll("[^0-9]", ""); + return digits.isEmpty() ? -1 : -Integer.parseInt(digits); + } + + if (normalized.matches("f\\d+")) { + return Integer.parseInt(normalized.substring(1)); + } + + String clean = normalized.replaceAll("[^0-9-]", ""); + if (clean.isEmpty() || clean.equals("-")) return 0; + return Integer.parseInt(clean); + } + + private Integer extractFirstInteger(String value) { + String digits = value.replaceAll("[^0-9]", ""); + if (digits.isEmpty()) { + return null; + } + return Integer.parseInt(digits); + } + + /** + * Get selected venue ID (for data submission) + */ + public String getSelectedVenueId() { + return selectedVenueId; + } + + /** + * Check if venue is selected + */ + public boolean isVenueSelected() { + return isVenueSelected; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_maps, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()); + floorSelectorContainer = view.findViewById(R.id.floorSelectorContainer); + floorButtonLayout = view.findViewById(R.id.floorButtonLayout); + + backToOutlineButton = view.findViewById(R.id.backToOutlineButton); + if (backToOutlineButton != null) { + backToOutlineButton.setVisibility(View.GONE); + backToOutlineButton.setOnClickListener(v -> returnToBuildingOutline()); + } + + // Setup venue selection button + selectVenueButton = view.findViewById(R.id.selectVenueButton); + if (selectVenueButton != null) { + selectVenueButton.setVisibility(View.GONE); + selectVenueButton.setOnClickListener(v -> handleVenueSelection()); + } + + SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map); + if (mapFragment != null) { + mapFragment.getMapAsync(callback); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + googleMap = null; + fusedLocationClient = null; + allBuildingsData.clear(); + buildingPolygonMap.clear(); + currentSelectedBuilding = null; + currentSelectedFloor = null; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MeasurementsFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MeasurementsFragment.java index 20c43987..b255868a 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MeasurementsFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/MeasurementsFragment.java @@ -47,6 +47,7 @@ public class MeasurementsFragment extends Fragment { // UI elements private ConstraintLayout sensorMeasurementList; private RecyclerView wifiListView; + private TextView wifiFingerprintStatus; // List of string resource IDs private int[] prefaces; private int[] gnssPrefaces; @@ -124,6 +125,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); sensorMeasurementList = (ConstraintLayout) getView().findViewById(R.id.sensorMeasurementList); wifiListView = (RecyclerView) getView().findViewById(R.id.wifiList); + wifiFingerprintStatus = (TextView) getView().findViewById(R.id.wifiFingerprintStatus); wifiListView.setLayoutManager(new LinearLayoutManager(getActivity())); } @@ -164,6 +166,10 @@ else if(values.length == 2){ ((TextView) currentRow.getChildAt(i + 1)).setText(valueString); } } + // Update WiFi Fingerprint Collection Status + int fingerprintCount = sensorFusion.getWiFiFingerprintCount(); + wifiFingerprintStatus.setText(String.format("Fingerprints: %d", fingerprintCount)); + // Get all WiFi values - convert to list of strings List wifiObjects = sensorFusion.getWifiList(); // If there are WiFi networks visible, update the recycler view with the data. diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/NetworkUtils.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/NetworkUtils.java new file mode 100644 index 00000000..199c837a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/NetworkUtils.java @@ -0,0 +1,244 @@ +package com.openpositioning.PositionMe.presentation.fragment; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import com.google.android.gms.maps.model.LatLng; +import org.json.JSONArray; +import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class NetworkUtils { + + private static final String TAG = "NetworkUtils"; + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + private static final Handler mainHandler = new Handler(Looper.getMainLooper()); + private static final String API_KEY = "Fbduaqg2iffH51lef1Eh7A"; + + public interface Callback { + void onSuccess(BuildingData buildingData); + void onError(String error); + } + + public static class BuildingData { + public List> outlines = new ArrayList<>(); + public Map floors = new HashMap<>(); + } + + public static class FloorData { + public List> walls = new ArrayList<>(); // Pure walls (solid black lines) + public List> areas = new ArrayList<>(); // Rooms and furniture areas (filled colour) + public List pois = new ArrayList<>(); + } + + public static class Poi { + public LatLng position; + public String label; + public String type; + + public Poi(LatLng position, String label, String type) { + this.position = position; + this.label = label; + this.type = type; + } + } + + public static void fetchFloorPlan(double latitude, double longitude, Callback callback) { + executor.execute(() -> { + HttpURLConnection conn = null; + try { + String urlString = "https://openpositioning.org/api/live/floorplan/request/" + + API_KEY + "?key=ewireless"; + URL url = new URL(urlString); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/json"); + + String jsonInputString = String.format(Locale.US, "{\"lat\": %.6f, \"lon\": %.6f, \"macs\": []}", latitude, longitude); + + try (java.io.OutputStream os = conn.getOutputStream()) { + os.write(jsonInputString.getBytes("utf-8")); + } + + int responseCode = conn.getResponseCode(); + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) response.append(line.trim()); + + BuildingData data = parseBuildingData(response.toString()); + mainHandler.post(() -> callback.onSuccess(data)); + } else { + mainHandler.post(() -> callback.onError("HTTP Error: " + responseCode)); + } + } catch (Exception e) { + e.printStackTrace(); + mainHandler.post(() -> callback.onError("Exception: " + e.getMessage())); + } finally { + if (conn != null) conn.disconnect(); + } + }); + } + + private static BuildingData parseBuildingData(String responseJson) { + BuildingData data = new BuildingData(); + try { + JSONArray rootArray = new JSONArray(responseJson); + for (int b = 0; b < rootArray.length(); b++) { + JSONObject building = rootArray.getJSONObject(b); + + if (building.has("outline")) { + parseOutline(building.getString("outline"), data.outlines); + } + + if (building.has("map_shapes")) { + JSONObject mapShapes = new JSONObject(building.getString("map_shapes")); + Iterator keys = mapShapes.keys(); + + while (keys.hasNext()) { + String floorName = keys.next(); + FloorData floorData = new FloorData(); + JSONObject floorGeoJson = mapShapes.getJSONObject(floorName); + parseFloorFeatures(floorGeoJson, floorData); + data.floors.put(floorName, floorData); + } + } + } + } catch (Exception e) { + Log.e(TAG, "Parsing error", e); + } + return data; + } + + private static void parseOutline(String jsonStr, List> outlines) throws Exception { + JSONObject json = new JSONObject(jsonStr); + if (json.has("features")) { + JSONArray features = json.getJSONArray("features"); + for (int i = 0; i < features.length(); i++) { + extractCoordinates(features.getJSONObject(i), outlines); + } + } + } + + private static void parseFloorFeatures(JSONObject floorJson, FloorData floorData) throws Exception { + if (!floorJson.has("features")) return; + JSONArray features = floorJson.getJSONArray("features"); + + for (int i = 0; i < features.length(); i++) { + JSONObject feature = features.getJSONObject(i); + JSONObject geometry = feature.getJSONObject("geometry"); + JSONObject properties = feature.optJSONObject("properties"); + + String type = ""; + String name = ""; + if (properties != null) { + type = properties.optString("indoor_type", "").toLowerCase(); + name = properties.optString("name", ""); + } + + // Prefer semantic tags over raw geometry when both are available. + if (isAreaType(type)) { + extractCoordinates(feature, floorData.areas); + } + else if (type.contains("wall")) { + extractCoordinates(feature, floorData.walls); + } + else { + String geomType = geometry.optString("type"); + if (geomType.contains("Polygon")) { + extractCoordinates(feature, floorData.areas); + } else { + extractCoordinates(feature, floorData.walls); + } + } + + // Capture POIs for labels and special markers. + if (!name.isEmpty() || isPoiType(type)) { + LatLng center = getCenter(geometry); + if (center != null) { + floorData.pois.add(new Poi(center, name, type)); + } + } + } + } + + // Features treated as fillable areas on the map. + private static boolean isAreaType(String type) { + return type.contains("room") || + type.contains("corridor") || + type.contains("hall") || + type.contains("table") || + type.contains("desk") || + type.contains("chair") || + type.contains("stair") || + type.contains("lift") || + type.contains("elevator") || + type.contains("office") || + type.contains("area"); + } + + // What items should display icons? + private static boolean isPoiType(String type) { + return type.contains("lift") || type.contains("elevator") || type.contains("toilet") + || type.contains("stair") || type.contains("room") || type.contains("printer"); + } + + private static void extractCoordinates(JSONObject feature, List> targetList) throws Exception { + JSONObject geometry = feature.getJSONObject("geometry"); + String type = geometry.optString("type"); + JSONArray coords = geometry.getJSONArray("coordinates"); + + if ("LineString".equalsIgnoreCase(type)) { + targetList.add(jsonArrayToLatLng(coords)); + } else if ("MultiLineString".equalsIgnoreCase(type)) { + for (int j = 0; j < coords.length(); j++) { + targetList.add(jsonArrayToLatLng(coords.getJSONArray(j))); + } + } else if ("Polygon".equalsIgnoreCase(type)) { + targetList.add(jsonArrayToLatLng(coords.getJSONArray(0))); + } else if ("MultiPolygon".equalsIgnoreCase(type)) { + for(int k=0; k jsonArrayToLatLng(JSONArray array) throws Exception { + List points = new ArrayList<>(); + if (array.length() > 0 && !(array.get(0) instanceof JSONArray)) { + return points; + } + for (int i = 0; i < array.length(); i++) { + JSONArray p = array.getJSONArray(i); + points.add(new LatLng(p.getDouble(1), p.getDouble(0))); + } + return points; + } + + private static LatLng getCenter(JSONObject geometry) { + try { + JSONArray coords = geometry.getJSONArray("coordinates"); + while (coords.length() > 0 && coords.get(0) instanceof JSONArray) { + coords = coords.getJSONArray(0); + } + if (coords.length() >= 2) { + return new LatLng(coords.getDouble(1), coords.getDouble(0)); + } + } catch (Exception e) { return null; } + return null; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java index 6362a971..97b17a30 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/RecordingFragment.java @@ -1,91 +1,357 @@ package com.openpositioning.PositionMe.presentation.fragment; +import android.Manifest; +import android.annotation.SuppressLint; import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.graphics.Color; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; import android.os.Bundle; +import android.os.Build; import android.os.CountDownTimer; import android.os.Handler; +import android.os.ParcelUuid; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - +import android.animation.ValueAnimator; +import android.view.MotionEvent; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.Button; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import com.google.android.material.button.MaterialButton; +import android.widget.Toast; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.switchmaterial.SwitchMaterial; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.Circle; +import com.google.android.gms.maps.model.CircleOptions; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.Polygon; +import com.google.android.gms.maps.model.PolygonOptions; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; + import com.openpositioning.PositionMe.R; import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; import com.openpositioning.PositionMe.sensors.SensorFusion; import com.openpositioning.PositionMe.sensors.SensorTypes; +import com.openpositioning.PositionMe.sensors.Wifi; +import com.openpositioning.PositionMe.utils.FusionManager; import com.openpositioning.PositionMe.utils.UtilFunctions; -import com.google.android.gms.maps.model.LatLng; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** - * Fragment responsible for managing the recording process of trajectory data. - *

- * The RecordingFragment serves as the interface for users to initiate, monitor, and - * complete trajectory recording. It integrates sensor fusion data to track user movement - * and updates a map view in real time. Additionally, it provides UI controls to cancel, - * stop, and monitor recording progress. - *

- * Features: - * - Starts and stops trajectory recording. - * - Displays real-time sensor data such as elevation and distance traveled. - * - Provides UI controls to cancel or complete recording. - * - Uses {@link TrajectoryMapFragment} to visualize recorded paths. - * - Manages GNSS tracking and error display. + * Enhanced RecordingFragment with Indoor Venue Selection * - * @see TrajectoryMapFragment The map fragment displaying the recorded trajectory. - * @see RecordingActivity The activity managing the recording workflow. - * @see SensorFusion Handles sensor data collection. - * @see SensorTypes Enumeration of available sensor types. + * New Features: + * - Display building outlines during recording + * - Allow users to select venue by clicking building outline + * - Show indoor floor plans and floor selector + * - Auto-record venue information for data submission + * - Integrated trajectory display with venue context * - * @author Shu Gu + * @author Original Team + Your Enhancements */ +public class RecordingFragment extends Fragment implements OnMapReadyCallback { + + private static final String TAG = "RecordingFragment"; + + // Building Location Data + private static class BuildingLocation { + String name; + String apiName; + LatLng center; + double radiusMeters; + int outlineColor; + int fillColor; + float markerHue; -public class RecordingFragment extends Fragment { + BuildingLocation(String name, String apiName, LatLng center, double radiusMeters, + int outlineColor, int fillColor, float markerHue) { + this.name = name; + this.apiName = apiName; + this.center = center; + this.radiusMeters = radiusMeters; + this.outlineColor = outlineColor; + this.fillColor = fillColor; + this.markerHue = markerHue; + } + } + + // Target buildings + private static final BuildingLocation[] TARGET_BUILDINGS = { + new BuildingLocation( + "Murchison House", + "Murchison House", + new LatLng(55.92412, -3.1792), + 20.0, + Color.RED, + Color.argb(51, 255, 0, 0), + BitmapDescriptorFactory.HUE_RED + ), + new BuildingLocation( + "Noreen and Kenneth Murray Library", + "Library", + new LatLng(55.9229, -3.1750), + 10.0, + Color.GREEN, + Color.argb(51, 0, 255, 0), + BitmapDescriptorFactory.HUE_GREEN + ), + new BuildingLocation( + "The Nucleus Building", + "The Nucleus", + new LatLng(55.92301, -3.1742), + 20.0, + Color.BLUE, + Color.argb(51, 0, 0, 255), + BitmapDescriptorFactory.HUE_BLUE + ), + new BuildingLocation( + "Fleeming Jenkin Building", + "Fleeming Jenkin", + new LatLng(55.92248, -3.17299), + 20.0, + Color.MAGENTA, + Color.argb(51, 255, 0, 255), + BitmapDescriptorFactory.HUE_MAGENTA + ) + }; - // UI elements + // UI Elements private MaterialButton completeButton, cancelButton; private ImageView recIcon; private ProgressBar timeRemaining; private TextView elevation, distanceTravelled, gnssError; - // App settings - private SharedPreferences settings; + // Venue selection UI + private TextView venueInfoText; + private MaterialButton changeVenueButton; + private View floorSelectorContainer; + private MaterialButton floorToggleButton; // created programmatically + private LinearLayout floorButtonLayout; + private Button backToRecordingButton; + private boolean isFloorSelectorExpanded = false; + private int floorSelectorExpandedWidth = 0; + + // SENSOR DATA UI ELEMENTS + private TextView trajectoryIdText; + private TextView wifiFingerprintsCount; + private TextView correctedPositionsCount; + private TextView initialPositionStatus; + private ImageView initialPositionIndicator; + private MaterialButton setInitialPositionButton; + + // TEST POINTS UI ELEMENTS + private MaterialButton markTestPointButton; + private TextView testPointsCount; + + // Smooth Trajectory toggle + private SwitchMaterial smoothTrajectorySwitch; + private boolean smoothTrajectoryEnabled = false; + + // Bottom drawer (slide-to-hide info panel) + private SwipeDownLinearLayout bottomDrawer; + private MaterialButton expandDrawerTab; + private final List testPointMarkers = new ArrayList<>(); + + // Map & Location + private GoogleMap googleMap; + private FusedLocationProviderClient fusedLocationClient; + private LatLng currentLocation; + private Marker userMarker; + private Polyline trajectoryPolyline; + private Marker gnssMarker; + + // Color-coded source polylines (PDR=blue, GNSS=green, WiFi=orange, Fused=red) + private Polyline pdrPolyline; + private Polyline gnssPolyline2; + private Polyline wifiPolyline; + private Polyline fusedPolyline; + private static final int MAX_RAW_OBSERVATIONS = 5; + private static final double RAW_OBSERVATION_RADIUS_M = 0.8; + private static final long RAW_OBSERVATION_MIN_INTERVAL_MS = 1200; + private static final float RAW_OBSERVATION_MIN_DISTANCE_M = 0.6f; + private final List gnssObservationCircles = new ArrayList<>(); + private final List wifiObservationCircles = new ArrayList<>(); + private final List pdrObservationCircles = new ArrayList<>(); + private LatLng lastGnssObservation = null; + private LatLng lastWifiObservation = null; + private LatLng lastPdrObservation = null; + private long lastGnssObservationTime = 0L; + private long lastWifiObservationTime = 0L; + private long lastPdrObservationTime = 0L; + // Track last source to detect source changes + private FusionManager.PositionSource lastPolylineSource = null; + // Floor display TextView + private TextView floorDisplayText; + // Last trajectory update time + private long lastTrajectoryUpdateTime = 0; + private static final long TRAJECTORY_UPDATE_INTERVAL_MS = 350; + + // Indoor map support + private final Map allBuildingsData = new HashMap<>(); + private final Map buildingPolygonMap = new HashMap<>(); + private String currentSelectedBuilding = null; + private String currentSelectedFloor = null; + private GroundOverlay currentFloorImageOverlay = null; + private final List currentWallLines = new ArrayList<>(); + private final List currentAreaPolygons = new ArrayList<>(); + private final List currentPoiMarkers = new ArrayList<>(); + private static final float FLOOR_IMAGE_TRANSPARENCY = 0.35f; + + // Manual overlay offsets (degrees). Tune these to move floor images. + // +LAT moves UP (north), -LAT moves DOWN (south). + // +LNG moves RIGHT (east), -LNG moves LEFT (west). + private static final double NUCLEUS_OVERLAY_LAT_OFFSET = 0.000015; + private static final double NUCLEUS_OVERLAY_LNG_OFFSET = -0.000059; + private static final double LIBRARY_OVERLAY_LAT_OFFSET = 0.000024; + private static final double LIBRARY_OVERLAY_LNG_OFFSET = 0.000057; + + private static class UprightOverlayConfig { + final LatLng center; + final float widthM; + final float bearingDeg; + + UprightOverlayConfig(LatLng center, float widthM, float bearingDeg) { + this.center = center; + this.widthM = widthM; + this.bearingDeg = bearingDeg; + } + } + + // Per-floor delta config relative to ground-floor base. + private static class FloorDelta { + final double latDelta; + final double lngDelta; + final float widthDeltaM; + final float bearingDeltaDeg; + + FloorDelta(double latDelta, double lngDelta, float widthDeltaM, float bearingDeltaDeg) { + this.latDelta = latDelta; + this.lngDelta = lngDelta; + this.widthDeltaM = widthDeltaM; + this.bearingDeltaDeg = bearingDeltaDeg; + } + } + + private static final FloorDelta ZERO_FLOOR_DELTA = new FloorDelta(0.0, 0.0, 0f, 0f); + + private UprightOverlayConfig withOffset(UprightOverlayConfig config, double latOffset, double lngOffset) { + return new UprightOverlayConfig( + new LatLng(config.center.latitude + latOffset, config.center.longitude + lngOffset), + config.widthM, + config.bearingDeg); + } - // Sensor & data logic + private UprightOverlayConfig applyFloorDelta(UprightOverlayConfig base, FloorDelta delta) { + return new UprightOverlayConfig( + new LatLng(base.center.latitude + delta.latDelta, base.center.longitude + delta.lngDelta), + base.widthM + delta.widthDeltaM, + base.bearingDeg + delta.bearingDeltaDeg); + } + + private FloorDelta getNucleusFloorDelta(int floor) { + switch (floor) { + case -1: + // B1 relative to G + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 0: + return ZERO_FLOOR_DELTA; + case 1: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 2: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + case 3: + return new FloorDelta(-0.000011, 0.000034, 4.0f, 0f); + default: + return ZERO_FLOOR_DELTA; + } + } + + private FloorDelta getLibraryFloorDelta(int floor) { + switch (floor) { + case 0: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 1: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 2: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + case 3: + return new FloorDelta(-0.000002, -0.000022, 0.0f, 0f); + default: + return ZERO_FLOOR_DELTA; + } + } + + // Recording Logic + private SharedPreferences settings; private SensorFusion sensorFusion; private Handler refreshDataHandler; private CountDownTimer autoStop; - - // Distance tracking private float distance = 0f; private float previousPosX = 0f; private float previousPosY = 0f; - // References to the child map fragment - private TrajectoryMapFragment trajectoryMapFragment; + // Venue tracking + private boolean hasVenue = false; + private String venueId = ""; + private String venueName = ""; + private String venueFloor = ""; + // Default keeps automatic floor following. Tapping a floor button switches + // to manual lock until user taps AUTO. + private boolean autoFloorFollowEnabled = true; + // UI Update Interval (ms) + private static final int UI_REFRESH_INTERVAL_MS = 100; // 10 FPS for smooth movement + + // Runnable for UI Updates private final Runnable refreshDataTask = new Runnable() { @Override public void run() { updateUIandPosition(); - // Loop again - refreshDataHandler.postDelayed(refreshDataTask, 200); + refreshDataHandler.postDelayed(refreshDataTask, UI_REFRESH_INTERVAL_MS); } }; @@ -100,6 +366,16 @@ public void onCreate(Bundle savedInstanceState) { Context context = requireActivity(); this.settings = PreferenceManager.getDefaultSharedPreferences(context); this.refreshDataHandler = new Handler(); + + // Read venue info from arguments + Bundle args = getArguments(); + if (args != null) { + hasVenue = args.getBoolean("has_venue", false); + venueId = args.getString("venue_id", ""); + venueName = args.getString("venue_name", ""); + venueFloor = args.getString("venue_floor", ""); + + } } @Nullable @@ -107,86 +383,67 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - // Inflate only the "recording" UI parts (no map) return inflater.inflate(R.layout.fragment_recording, container, false); } @Override - public void onViewCreated(@NonNull View view, - @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // Child Fragment: the container in fragment_recording.xml - // where TrajectoryMapFragment is placed - trajectoryMapFragment = (TrajectoryMapFragment) - getChildFragmentManager().findFragmentById(R.id.trajectoryMapFragmentContainer); - - // If not present, create it - if (trajectoryMapFragment == null) { - trajectoryMapFragment = new TrajectoryMapFragment(); - getChildFragmentManager() - .beginTransaction() - .replace(R.id.trajectoryMapFragmentContainer, trajectoryMapFragment) - .commit(); - } + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()); // Initialize UI references elevation = view.findViewById(R.id.currentElevation); distanceTravelled = view.findViewById(R.id.currentDistanceTraveled); gnssError = view.findViewById(R.id.gnssError); - completeButton = view.findViewById(R.id.stopButton); cancelButton = view.findViewById(R.id.cancelButton); recIcon = view.findViewById(R.id.redDot); timeRemaining = view.findViewById(R.id.timeRemainingBar); - // Hide or initialize default values - gnssError.setVisibility(View.GONE); - elevation.setText(getString(R.string.elevation, "0")); - distanceTravelled.setText(getString(R.string.meter, "0")); + // Venue selection UI + venueInfoText = view.findViewById(R.id.venueInfoText); + changeVenueButton = view.findViewById(R.id.changeVenueButton); + floorSelectorContainer = view.findViewById(R.id.floorSelectorContainer); + floorButtonLayout = view.findViewById(R.id.floorButtonLayout); + backToRecordingButton = view.findViewById(R.id.backToRecordingButton); - // Buttons - completeButton.setOnClickListener(v -> { - // Stop recording & go to correction - if (autoStop != null) autoStop.cancel(); - sensorFusion.stopRecording(); - // Show Correction screen - ((RecordingActivity) requireActivity()).showCorrectionScreen(); - }); + // SENSOR DATA UI ELEMENTS + trajectoryIdText = view.findViewById(R.id.trajectoryIdText); + wifiFingerprintsCount = view.findViewById(R.id.wifiFingerprintsCount); + correctedPositionsCount = view.findViewById(R.id.correctedPositionsCount); + initialPositionStatus = view.findViewById(R.id.initialPositionStatus); + initialPositionIndicator = view.findViewById(R.id.initialPositionIndicator); + setInitialPositionButton = view.findViewById(R.id.setInitialPositionButton); + // Floor display (particle filter / map matcher output) + floorDisplayText = view.findViewById(R.id.floorDisplayText); + // TEST POINTS UI ELEMENTS + markTestPointButton = view.findViewById(R.id.markTestPointButton); + testPointsCount = view.findViewById(R.id.testPointsCount); - // Cancel button with confirmation dialog - cancelButton.setOnClickListener(v -> { - AlertDialog dialog = new AlertDialog.Builder(requireActivity()) - .setTitle("Confirm Cancel") - .setMessage("Are you sure you want to cancel the recording? Your progress will be lost permanently!") - .setNegativeButton("Yes", (dialogInterface, which) -> { - // User confirmed cancellation - sensorFusion.stopRecording(); - if (autoStop != null) autoStop.cancel(); - requireActivity().onBackPressed(); - }) - .setPositiveButton("No", (dialogInterface, which) -> { - // User cancelled the dialog. Do nothing. - dialogInterface.dismiss(); - }) - .create(); // Create the dialog but do not show it yet - - // Show the dialog and change the button color - dialog.setOnShowListener(dialogInterface -> { - Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); - negativeButton.setTextColor(Color.RED); // Set "Yes" button color to red - }); - - dialog.show(); // Finally, show the dialog + // Smooth Trajectory toggle + smoothTrajectorySwitch = view.findViewById(R.id.smoothTrajectorySwitch); + smoothTrajectorySwitch.setOnCheckedChangeListener((btn, isChecked) -> { + smoothTrajectoryEnabled = isChecked; + // Reset so the filter initialises from the current position + positionInitialized = false; }); - // The blinking effect for recIcon - blinkingRecordingIcon(); + // Initialize map + SupportMapFragment mapFragment = (SupportMapFragment) + getChildFragmentManager().findFragmentById(R.id.recordingMap); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } - // Start the timed or indefinite UI refresh + // Setup UI + setupRecordingControls(); + setupVenueDisplay(); + setupBottomDrawer(view); + + // Start recording refresh if (this.settings.getBoolean("split_trajectory", false)) { - // A maximum recording time is set long limit = this.settings.getInt("split_duration", 30) * 60000L; timeRemaining.setMax((int) (limit / 1000)); timeRemaining.setProgress(0); @@ -206,73 +463,1671 @@ public void onFinish() { } }.start(); } else { - // No set time limit, just keep refreshing refreshDataHandler.post(refreshDataTask); } + + // Blinking recording icon + blinkingRecordingIcon(); + + // Start automated WiFi fingerprint collection + startWiFiFingerprintCollection(); + + // Start automated BLE data collection + startBLEDataCollection(); + + // Reset trajectory tracking variables for new recording + lastTrajectoryPoint = null; + secondLastTrajectoryPoint = null; + positionInitialized = false; + smoothedLat = 0.0; + smoothedLng = 0.0; + } + + // WiFi FINGERPRINT COLLECTION + private Runnable wiFiFingerprintTask; + private static final long WIFI_FINGERPRINT_INTERVAL = 3000; // 3 seconds + + private void startWiFiFingerprintCollection() { + wiFiFingerprintTask = new Runnable() { + @Override + public void run() { + // Collect WiFi fingerprints from current available networks + List wifiList = sensorFusion.getWifiList(); + if (wifiList != null && !wifiList.isEmpty()) { + long currentTime = System.currentTimeMillis(); + for (Wifi wifi : wifiList) { + // BSSID is already a long value, get level (signal strength) + long bssid = wifi.getBssid(); + int rssi = wifi.getLevel(); + + // Add WiFi fingerprint + sensorFusion.addWiFiFingerprint(currentTime, bssid, rssi); + + // Log detailed WiFi AP data together with RTT capability flag. + // In production, would check actual device RTT capability + boolean rttEnabled = false; // Default: assume no RTT support + // Check if this AP supports RTT (would need actual RTT scanning) + + // Add AP data with RTT flag + sensorFusion.addWiFiAPData(bssid, wifi.getSsid(), wifi.getFrequency(), rttEnabled); + + } + + // Update UI + int count = sensorFusion.getWiFiFingerprintCount(); + wifiFingerprintsCount.setText(String.valueOf(count)); + } + + // Schedule next collection + refreshDataHandler.postDelayed(this, WIFI_FINGERPRINT_INTERVAL); + } + }; + + refreshDataHandler.postDelayed(wiFiFingerprintTask, WIFI_FINGERPRINT_INTERVAL); + } + + // BLE DATA COLLECTION + private Runnable blEDataTask; + private static final long BLE_DATA_INTERVAL = 5000; // 5 seconds + + private void startBLEDataCollection() { + blEDataTask = new Runnable() { + @Override + public void run() { + collectPairedBleDevices(); + + // Schedule next collection + refreshDataHandler.postDelayed(this, BLE_DATA_INTERVAL); + } + }; + + refreshDataHandler.postDelayed(blEDataTask, BLE_DATA_INTERVAL); + } + + @SuppressLint("MissingPermission") + private void collectPairedBleDevices() { + try { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter == null || !adapter.isEnabled()) { + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.BLUETOOTH_CONNECT) + != PackageManager.PERMISSION_GRANTED) { + return; + } + + Set bondedDevices = adapter.getBondedDevices(); + for (BluetoothDevice device : bondedDevices) { + List serviceUuids = new ArrayList<>(); + ParcelUuid[] uuids = device.getUuids(); + if (uuids != null) { + for (ParcelUuid uuid : uuids) { + serviceUuids.add(uuid.toString()); + } + } + + String name = device.getName() != null ? device.getName() : "Unknown"; + sensorFusion.addBLEData(device.getAddress(), name, 0, 0, serviceUuids); + } + } catch (Exception e) { + Log.e(TAG, "Error collecting BLE data: " + e.getMessage()); + } + } + + @Override + public void onMapReady(@NonNull GoogleMap map) { + googleMap = map; + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + googleMap.getUiSettings().setZoomControlsEnabled(true); + + // Enable user location if permission granted + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + googleMap.setMyLocationEnabled(true); + } + + // Setup building click listener + setupBuildingClickListener(); + + // Initialize legacy trajectory polyline + trajectoryPolyline = googleMap.addPolyline(new PolylineOptions() + .color(Color.RED) + .width(8f) + .geodesic(true)); + + // Initialize color-coded source polylines + // PDR = Blue, GNSS = Green, WiFi = Orange, Fused = Red + pdrPolyline = googleMap.addPolyline(new PolylineOptions() + .color(Color.BLUE).width(6f).geodesic(true).zIndex(10)); + gnssPolyline2 = googleMap.addPolyline(new PolylineOptions() + .color(Color.GREEN).width(6f).geodesic(true).zIndex(10)); + wifiPolyline = googleMap.addPolyline(new PolylineOptions() + .color(Color.parseColor("#FF8C00")).width(6f).geodesic(true).zIndex(10)); // Dark orange + fusedPolyline = googleMap.addPolyline(new PolylineOptions() + .color(Color.RED).width(8f).geodesic(true).zIndex(11)); + + // Move camera to campus center + LatLng campusCenter = new LatLng(55.9234, -3.1761); + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(campusCenter, 17f)); + + // Draw building outlines + drawBuildingOutlines(); } + // BUILDING OUTLINES + private void drawBuildingOutlines() { + if (googleMap == null) return; + + for (BuildingLocation building : TARGET_BUILDINGS) { + List circlePoints = createCircle(building.center, building.radiusMeters); + + Polygon polygon = googleMap.addPolygon(new PolygonOptions() + .addAll(circlePoints) + .strokeColor(building.outlineColor) + .strokeWidth(10f) + .fillColor(building.fillColor) + .clickable(true) + .zIndex(50)); + + buildingPolygonMap.put(polygon, building.name); + + googleMap.addMarker(new MarkerOptions() + .position(building.center) + .title(building.name) + .snippet("Click to select venue") + .icon(BitmapDescriptorFactory.defaultMarker(building.markerHue)) + .zIndex(60)); + } + + Toast.makeText(getContext(), "Tap building outline to select venue", Toast.LENGTH_SHORT).show(); + } + + private List createCircle(LatLng center, double radiusMeters) { + List points = new ArrayList<>(); + int numPoints = 36; + double earthRadius = 6371000; // meters + + for (int i = 0; i < numPoints; i++) { + double angle = 2.0 * Math.PI * i / numPoints; + double dx = radiusMeters * Math.cos(angle); + double dy = radiusMeters * Math.sin(angle); + double deltaLat = dy / earthRadius; + double deltaLon = dx / (earthRadius * Math.cos(Math.PI * center.latitude / 180)); + double lat = center.latitude + (deltaLat * 180 / Math.PI); + double lon = center.longitude + (deltaLon * 180 / Math.PI); + points.add(new LatLng(lat, lon)); + } + return points; + } + + // BUILDING CLICK LISTENER + private void setupBuildingClickListener() { + if (googleMap == null) return; + + // Setup map click listener for marking corrected positions + googleMap.setOnMapClickListener(latLng -> { + // Add corrected position marker + addCorrectedPositionMarker(latLng); + }); + + // Long press to set position anchor (correct drift) + googleMap.setOnMapLongClickListener(latLng -> { + setPositionAnchor(latLng); + }); + + googleMap.setOnPolygonClickListener(polygon -> { + String buildingName = buildingPolygonMap.get(polygon); + if (buildingName == null) { + return; + } + + if (allBuildingsData.containsKey(buildingName)) { + showVenueSelectionDialog(buildingName); + } else { + loadBuildingDataForSelection(buildingName); + } + }); + } + + // POSITION ANCHOR (DRIFT CORRECTION) + private Marker anchorMarker = null; + /** - * Update the UI with sensor data and pass map updates to TrajectoryMapFragment. + * Set position anchor to correct accumulated drift. + * Long-press on map where you actually are to fix positioning. */ - private void updateUIandPosition() { - float[] pdrValues = sensorFusion.getSensorValueMap().get(SensorTypes.PDR); - if (pdrValues == null) return; - - // Distance - distance += Math.sqrt(Math.pow(pdrValues[0] - previousPosX, 2) - + Math.pow(pdrValues[1] - previousPosY, 2)); - distanceTravelled.setText(getString(R.string.meter, String.format("%.2f", distance))); - - // Elevation - float elevationVal = sensorFusion.getElevation(); - elevation.setText(getString(R.string.elevation, String.format("%.1f", elevationVal))); - - // Current location - // Convert PDR coordinates to actual LatLng if you have a known starting lat/lon - // Or simply pass relative data for the TrajectoryMapFragment to handle - // For example: - float[] latLngArray = sensorFusion.getGNSSLatitude(true); - if (latLngArray != null) { - LatLng oldLocation = trajectoryMapFragment.getCurrentLocation(); // or store locally - LatLng newLocation = UtilFunctions.calculateNewPos( - oldLocation == null ? new LatLng(latLngArray[0], latLngArray[1]) : oldLocation, - new float[]{ pdrValues[0] - previousPosX, pdrValues[1] - previousPosY } + private void setPositionAnchor(LatLng latLng) { + if (googleMap == null) return; + + // Remove old anchor marker + if (anchorMarker != null) { + anchorMarker.remove(); + } + + // Add anchor marker (green color) + anchorMarker = googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)) + .title("Position Anchor") + .snippet("Long-press to set new anchor")); + + // Set anchor in fusion algorithm + sensorFusion.setPositionAnchor(latLng.latitude, latLng.longitude); + + // Also update current position variables for smoother transition + currentLocation = latLng; + smoothedLat = latLng.latitude; + smoothedLng = latLng.longitude; + positionInitialized = true; + lastTrajectoryPoint = latLng; + + // Update trajectory to include the corrected position + if (trajectoryPolyline != null) { + List points = trajectoryPolyline.getPoints(); + points.add(latLng); + trajectoryPolyline.setPoints(points); + } + + Log.d(TAG, "Position anchor set at: " + latLng.latitude + ", " + latLng.longitude); + Toast.makeText(getContext(), "Position corrected! Drift will now reduce.", Toast.LENGTH_SHORT).show(); + } + + // CORRECTED POSITION MARKING + private static final float CORRECTION_MARKER_COLOR = BitmapDescriptorFactory.HUE_YELLOW; + private final List correctionMarkers = new ArrayList<>(); + + private void addCorrectedPositionMarker(LatLng latLng) { + if (googleMap == null) return; + + // Add marker to map + Marker marker = googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(BitmapDescriptorFactory.defaultMarker(CORRECTION_MARKER_COLOR)) + .title("Corrected Position #" + (sensorFusion.getCorrectedPositionCount() + 1)) + .snippet("Tap to remove")); + + correctionMarkers.add(marker); + + // Add position to SensorFusion + sensorFusion.addCorrectedPosition((float) latLng.latitude, (float) latLng.longitude); + + // Update UI count + int count = sensorFusion.getCorrectedPositionCount(); + correctedPositionsCount.setText(String.valueOf(count)); + + Log.d(TAG, "Position #" + count + " marked at: " + latLng.latitude + ", " + latLng.longitude); + Toast.makeText(getContext(), "Position marked (#" + count + ")", Toast.LENGTH_SHORT).show(); + } + + // TEST POINT MARKING + private static final float TEST_POINT_MARKER_COLOR = BitmapDescriptorFactory.HUE_VIOLET; + + private void addTestPointMarker(LatLng latLng, int pointNumber) { + if (googleMap == null) return; + + // Create numbered marker bitmap + BitmapDescriptor markerIcon = createNumberedMarker(pointNumber); + + // Add marker to map with numbered bitmap + Marker marker = googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(markerIcon) + .title("Test Point #" + pointNumber) + .snippet("Marked at: " + String.format("%.4f, %.4f", latLng.latitude, latLng.longitude)) + .zIndex(95)); + + testPointMarkers.add(marker); + + Log.d(TAG, "Test Point #" + pointNumber + " marker added at: " + latLng.latitude + ", " + latLng.longitude); + } + + /** + * Create a custom numbered marker bitmap + * Generates a purple marker with white number displayed on it + * @param number The number to display on the marker + * @return BitmapDescriptor for the marker + */ + private BitmapDescriptor createNumberedMarker(int number) { + // Create canvas for marker (size: 96x96 pixels) + Bitmap bitmap = Bitmap.createBitmap(96, 96, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Draw purple circle background (to match TEST_POINT_MARKER_COLOR - HUE_VIOLET) + Paint backgroundPaint = new Paint(); + backgroundPaint.setColor(Color.parseColor("#7C4DFF")); // purple/violet + backgroundPaint.setAntiAlias(true); + canvas.drawCircle(48, 48, 40, backgroundPaint); + + // Draw white number on top + Paint textPaint = new Paint(); + textPaint.setColor(Color.WHITE); + textPaint.setTextSize(50f); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setTypeface(Typeface.DEFAULT_BOLD); + textPaint.setAntiAlias(true); + + // Draw text centered in circle + Rect textBounds = new Rect(); + String numberStr = String.valueOf(number); + textPaint.getTextBounds(numberStr, 0, numberStr.length(), textBounds); + int textHeight = textBounds.height(); + canvas.drawText(numberStr, 48, 48 + textHeight / 2, textPaint); + + // Convert to BitmapDescriptor + return BitmapDescriptorFactory.fromBitmap(bitmap); + } + + // VENUE SELECTION DIALOG + private void showVenueSelectionDialog(String buildingName) { + new AlertDialog.Builder(requireContext()) + .setTitle("Select Venue") + .setMessage("Record trajectory in " + buildingName + "?") + .setPositiveButton("Select", (dialog, which) -> { + selectVenue(buildingName); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private void selectVenue(String buildingName) { + currentSelectedBuilding = buildingName; + NetworkUtils.BuildingData data = allBuildingsData.get(buildingName); + + if (data != null && !data.floors.isEmpty()) { + // Update venue manager + String firstFloor = new ArrayList<>(data.floors.keySet()).get(0); + venueId = buildingName; // Use building name as ID + venueName = buildingName; + venueFloor = firstFloor; + hasVenue = true; + + VenueManager.getInstance(requireContext()) + .setSelectedVenue(venueName, venueId, venueFloor); + + // Show floor selector + setupFloorSelector(data.floors, buildingName); + drawFloor(firstFloor, data); + + // Update UI + updateVenueDisplay(); + + Toast.makeText(getContext(), "Venue selected: " + buildingName, Toast.LENGTH_SHORT).show(); + Log.d(TAG, "Venue selected: " + buildingName + " - " + firstFloor); + } + } + + // FLOOR DISPLAY + private void drawFloor(String floorName, NetworkUtils.BuildingData data) { + if (googleMap == null) return; + + // Clear previous floor + clearCurrentFloor(); + + currentSelectedFloor = floorName; + NetworkUtils.FloorData floorData = data.floors.get(floorName); + if (floorData == null) return; + + // Draw configured floor image first so API walls/areas align on top. + addIndoorFloorImageOverlay(floorName, floorData, data); + + // Draw walls (black lines) + for (List wall : floorData.walls) { + if (wall.size() >= 2) { + Polyline line = googleMap.addPolyline(new PolylineOptions() + .addAll(wall) + .color(Color.BLACK) + .width(3f) + .zIndex(90)); + currentWallLines.add(line); + } + } + + // Draw areas (filled polygons) + for (List area : floorData.areas) { + if (area.size() >= 3) { + Polygon poly = googleMap.addPolygon(new PolygonOptions() + .addAll(area) + .strokeColor(Color.DKGRAY) + .strokeWidth(2f) + .fillColor(Color.argb(35, 200, 200, 200)) + .zIndex(80)); + currentAreaPolygons.add(poly); + } + } + + // Draw POIs (icons) + for (NetworkUtils.Poi poi : floorData.pois) { + Marker marker = googleMap.addMarker(new MarkerOptions() + .position(poi.position) + .title(poi.label) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)) + .zIndex(100)); + currentPoiMarkers.add(marker); + } + + // Apply API wall constraints to fusion so the trajectory cannot cross walls. + int wallSegments = sensorFusion.configureIndoorWallConstraints( + floorData.walls, + extractFloorNumber(floorName) ); - // Pass the location + orientation to the map - if (trajectoryMapFragment != null) { - trajectoryMapFragment.updateUserLocation(newLocation, - (float) Math.toDegrees(sensorFusion.passOrientation())); + // Supply lift/stair POI centres so floor switching is constrained to + // known transition zones (Feature 1) and the elevator/stair mode can be + // identified (Feature 2). Display-only: no effect on PDR or position fusion. + List liftCenters = new ArrayList<>(); + List stairCenters = new ArrayList<>(); + for (NetworkUtils.Poi poi : floorData.pois) { + String t = poi.type; + if (t.contains("lift") || t.contains("elevator")) { + liftCenters.add(poi.position); + } else if (t.contains("stair")) { + stairCenters.add(poi.position); + } + } + sensorFusion.setFloorTransitionZones(liftCenters, stairCenters); + + Log.d(TAG, "Floor drawn: " + currentWallLines.size() + " walls, " + + currentAreaPolygons.size() + " areas, map-constrained wall segments=" + wallSegments); + } + + private void clearCurrentFloor() { + if (currentFloorImageOverlay != null) { + currentFloorImageOverlay.remove(); + currentFloorImageOverlay = null; + } + for (Polyline line : currentWallLines) line.remove(); + for (Polygon poly : currentAreaPolygons) poly.remove(); + for (Marker marker : currentPoiMarkers) marker.remove(); + currentWallLines.clear(); + currentAreaPolygons.clear(); + currentPoiMarkers.clear(); + } + + private void addIndoorFloorImageOverlay(String floorName, + NetworkUtils.FloorData floorData, + NetworkUtils.BuildingData buildingData) { + if (googleMap == null || currentSelectedBuilding == null) { + return; + } + + int floorNumber; + try { + floorNumber = extractFloorNumber(floorName); + } catch (Exception e) { + return; + } + + int drawableRes = resolveFloorImageResource( + currentSelectedBuilding, + floorNumber, + floorName, + buildingData != null ? buildingData.floors.keySet() : null); + if (drawableRes == 0) { + return; + } + + UprightOverlayConfig fixedConfig = getUprightOverlayConfig( + currentSelectedBuilding, + floorNumber, + floorName, + buildingData != null ? buildingData.floors.keySet() : null); + + if (fixedConfig != null) { + currentFloorImageOverlay = googleMap.addGroundOverlay(new GroundOverlayOptions() + .image(BitmapDescriptorFactory.fromResource(drawableRes)) + .position(fixedConfig.center, fixedConfig.widthM) + .bearing(fixedConfig.bearingDeg) + .transparency(FLOOR_IMAGE_TRANSPARENCY) + .zIndex(70f)); + + return; + } + + LatLngBounds bounds = buildApiAlignedBounds(floorData, currentSelectedBuilding); + if (bounds == null) { + return; + } + + currentFloorImageOverlay = googleMap.addGroundOverlay(new GroundOverlayOptions() + .image(BitmapDescriptorFactory.fromResource(drawableRes)) + .positionFromBounds(bounds) + .transparency(FLOOR_IMAGE_TRANSPARENCY) + .zIndex(70f)); + + } + + private UprightOverlayConfig getUprightOverlayConfig(String buildingName, + int floorNumber, + String floorName, + Set allFloorNames) { + String normalized = buildingName == null ? "" : buildingName.toLowerCase(); + + boolean isNucleus = normalized.equals("the nucleus building") + || normalized.equals("the nucleus") + || normalized.equals("nucleus"); + boolean isLibrary = normalized.equals("noreen and kenneth murray library") + || normalized.equals("library") + || normalized.contains("murray library"); + + if (isNucleus) { + UprightOverlayConfig nucleusBase = new UprightOverlayConfig( + new LatLng(55.923041, -3.174234), + 46f, + 0f); + UprightOverlayConfig tuned = applyFloorDelta(nucleusBase, getNucleusFloorDelta(floorNumber)); + return withOffset( + tuned, + NUCLEUS_OVERLAY_LAT_OFFSET, + NUCLEUS_OVERLAY_LNG_OFFSET); + } + + if (isLibrary) { + int mappedFloor = floorNumber; + boolean hasExplicitGround = hasGroundLikeFloorLabel(allFloorNames); + if (!hasExplicitGround && mappedFloor >= 1) { + mappedFloor -= 1; + } + if (isGroundLikeLabel(floorName)) { + mappedFloor = 0; + } + + UprightOverlayConfig libraryBase = new UprightOverlayConfig( + new LatLng(55.9229, -3.1750), + 26.0f, + 0f); + UprightOverlayConfig tuned = applyFloorDelta(libraryBase, getLibraryFloorDelta(mappedFloor)); + return withOffset( + tuned, + LIBRARY_OVERLAY_LAT_OFFSET, + LIBRARY_OVERLAY_LNG_OFFSET); + } + + return null; + } + + private int resolveFloorImageResource(String buildingName, + int floorNumber, + String floorName, + Set allFloorNames) { + String normalized = buildingName.toLowerCase(); + + boolean isNucleus = normalized.equals("the nucleus building") + || normalized.equals("the nucleus") + || normalized.equals("nucleus"); + boolean isLibrary = normalized.equals("noreen and kenneth murray library") + || normalized.equals("library") + || normalized.contains("murray library"); + + if (isNucleus) { + switch (floorNumber) { + case -1: + return R.drawable.nucleuslg; + case 0: + return R.drawable.nucleusg; + case 1: + return R.drawable.nucleus1; + case 2: + return R.drawable.nucleus2; + case 3: + return R.drawable.nucleus3; + default: + return 0; + } + } + + if (isLibrary) { + // Some Library API payloads are 1-based (1 means ground floor). + // If no explicit G/GF/0 label exists, shift positive floors by -1. + int mappedFloor = floorNumber; + boolean hasExplicitGround = hasGroundLikeFloorLabel(allFloorNames); + if (!hasExplicitGround && mappedFloor >= 1) { + mappedFloor -= 1; + } + + if (isGroundLikeLabel(floorName) || mappedFloor == 0) { + return R.drawable.libraryg; + } + + switch (mappedFloor) { + case 1: + return R.drawable.library1; + case 2: + return R.drawable.library2; + case 3: + return R.drawable.library3; + default: + return 0; + } + } + + return 0; + } + + private boolean hasGroundLikeFloorLabel(Set floorNames) { + if (floorNames == null || floorNames.isEmpty()) { + return false; + } + for (String floorName : floorNames) { + if (isGroundLikeLabel(floorName)) { + return true; + } + } + return false; + } + + private boolean isGroundLikeLabel(String floorName) { + if (floorName == null) { + return false; + } + String normalized = floorName.toLowerCase().replace("[", "").replace("]", "").trim(); + return normalized.equals("g") + || normalized.equals("gf") + || normalized.equals("ground") + || normalized.equals("ground_floor") + || normalized.equals("0"); + } + + private LatLngBounds buildApiAlignedBounds(NetworkUtils.FloorData floorData, String buildingName) { + double minLat = Double.POSITIVE_INFINITY; + double maxLat = Double.NEGATIVE_INFINITY; + double minLng = Double.POSITIVE_INFINITY; + double maxLng = Double.NEGATIVE_INFINITY; + + // Prefer wall geometry for floor-image alignment. Areas/POIs may contain + // outliers and can make overlays spill into neighboring buildings. + minLat = updateMinLatLngFromPolylines(floorData.walls, minLat, true); + maxLat = updateMaxLatLngFromPolylines(floorData.walls, maxLat, true); + minLng = updateMinLatLngFromPolylines(floorData.walls, minLng, false); + maxLng = updateMaxLatLngFromPolylines(floorData.walls, maxLng, false); + + // If walls are not available, fall back to areas. + if (!Double.isFinite(minLat) || !Double.isFinite(maxLat) + || !Double.isFinite(minLng) || !Double.isFinite(maxLng)) { + minLat = updateMinLatLngFromPolylines(floorData.areas, minLat, true); + maxLat = updateMaxLatLngFromPolylines(floorData.areas, maxLat, true); + minLng = updateMinLatLngFromPolylines(floorData.areas, minLng, false); + maxLng = updateMaxLatLngFromPolylines(floorData.areas, maxLng, false); + } + + if (!Double.isFinite(minLat) || !Double.isFinite(maxLat) + || !Double.isFinite(minLng) || !Double.isFinite(maxLng)) { + return fallbackBoundsFromBuildingCenter(buildingName); + } + + double latPad = Math.max((maxLat - minLat) * 0.03, 0.00001); + double lngPad = Math.max((maxLng - minLng) * 0.03, 0.00001); + + LatLng southWest = new LatLng(minLat - latPad, minLng - lngPad); + LatLng northEast = new LatLng(maxLat + latPad, maxLng + lngPad); + LatLngBounds apiBounds = new LatLngBounds(southWest, northEast); + return constrainBoundsToBuilding(apiBounds, buildingName); + } + + private LatLngBounds constrainBoundsToBuilding(LatLngBounds sourceBounds, String buildingName) { + LatLngBounds buildingBounds = fallbackBoundsFromBuildingCenter(buildingName); + if (buildingBounds == null) { + return sourceBounds; + } + + LatLng sw = sourceBounds.southwest; + LatLng ne = sourceBounds.northeast; + LatLng bsw = buildingBounds.southwest; + LatLng bne = buildingBounds.northeast; + + double sourceLatSpan = ne.latitude - sw.latitude; + double sourceLngSpan = ne.longitude - sw.longitude; + double buildingLatSpan = bne.latitude - bsw.latitude; + double buildingLngSpan = bne.longitude - bsw.longitude; + + if (sourceLatSpan <= 0 || sourceLngSpan <= 0 || buildingLatSpan <= 0 || buildingLngSpan <= 0) { + return buildingBounds; + } + + // Keep bounds centered to avoid one-sided clipping that visually shifts + // overlays toward lower-left. + double targetLatSpan = Math.min(sourceLatSpan, buildingLatSpan * 0.95); + double targetLngSpan = Math.min(sourceLngSpan, buildingLngSpan * 0.95); + + double sourceCenterLat = (sw.latitude + ne.latitude) * 0.5; + double sourceCenterLng = (sw.longitude + ne.longitude) * 0.5; + + double minCenterLat = bsw.latitude + targetLatSpan * 0.5; + double maxCenterLat = bne.latitude - targetLatSpan * 0.5; + double minCenterLng = bsw.longitude + targetLngSpan * 0.5; + double maxCenterLng = bne.longitude - targetLngSpan * 0.5; + + double centerLat = Math.min(Math.max(sourceCenterLat, minCenterLat), maxCenterLat); + double centerLng = Math.min(Math.max(sourceCenterLng, minCenterLng), maxCenterLng); + + double south = centerLat - targetLatSpan * 0.5; + double north = centerLat + targetLatSpan * 0.5; + double west = centerLng - targetLngSpan * 0.5; + double east = centerLng + targetLngSpan * 0.5; + + return new LatLngBounds(new LatLng(south, west), new LatLng(north, east)); + } + + private double updateMinLatLngFromPolylines(List> groups, double currentMin, boolean latitude) { + if (groups == null) return currentMin; + double min = currentMin; + for (List group : groups) { + if (group == null) continue; + for (LatLng point : group) { + if (point == null) continue; + min = Math.min(min, latitude ? point.latitude : point.longitude); + } + } + return min; + } + + private double updateMaxLatLngFromPolylines(List> groups, double currentMax, boolean latitude) { + if (groups == null) return currentMax; + double max = currentMax; + for (List group : groups) { + if (group == null) continue; + for (LatLng point : group) { + if (point == null) continue; + max = Math.max(max, latitude ? point.latitude : point.longitude); + } + } + return max; + } + + private LatLngBounds fallbackBoundsFromBuildingCenter(String buildingName) { + for (BuildingLocation building : TARGET_BUILDINGS) { + if (!building.name.equals(buildingName)) { + continue; } + + // Use a generous but building-local bound so one building image + // cannot span and cover neighboring buildings. + double radiusM = building.radiusMeters * 2.2; + double dLat = radiusM / 111000.0; + double dLng = radiusM / (111000.0 * Math.cos(Math.toRadians(building.center.latitude))); + + LatLng southWest = new LatLng(building.center.latitude - dLat, building.center.longitude - dLng); + LatLng northEast = new LatLng(building.center.latitude + dLat, building.center.longitude + dLng); + return new LatLngBounds(southWest, northEast); + } + return null; + } + + // FLOOR SELECTOR + private void setupFloorSelector(Map floors, String buildingName) { + if (floorButtonLayout == null || getContext() == null) return; + + backToRecordingButton.setVisibility(View.VISIBLE); + floorButtonLayout.removeAllViews(); + isFloorSelectorExpanded = false; + + List sortedFloors = sortFloorNames(new ArrayList<>(floors.keySet())); + + // Calculate full expanded width: toggle + (AUTO + floors) * per-button-width + int togglePx = dpToPx(56); + int otherBtnPx = 110 + dpToPx(2) * 2; // button width + left/right margin + int cardPadPx = dpToPx(4) * 2; // LinearLayout padding + floorSelectorExpandedWidth = togglePx + otherBtnPx * (1 + sortedFloors.size()) + cardPadPx; + + // Toggle button (drag handle + expand/collapse) + String initialLabel = (autoFloorFollowEnabled || venueFloor.isEmpty()) ? "AUTO" : venueFloor; + floorToggleButton = new MaterialButton(getContext()); + floorToggleButton.setText(initialLabel); + floorToggleButton.setTextSize(11); + floorToggleButton.setTypeface(null, Typeface.BOLD); + LinearLayout.LayoutParams toggleParams = new LinearLayout.LayoutParams(togglePx, togglePx); + floorToggleButton.setLayoutParams(toggleParams); + floorToggleButton.setPadding(dpToPx(4), dpToPx(4), dpToPx(4), dpToPx(4)); + floorToggleButton.setCornerRadius(dpToPx(10)); + floorToggleButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_primary))); + setupFloorToggleDragAndTap(floorToggleButton); + floorButtonLayout.addView(floorToggleButton); + + // AUTO button + MaterialButton autoButton = new MaterialButton(getContext()); + autoButton.setText("AUTO"); + autoButton.setTextSize(12); + LinearLayout.LayoutParams autoParams = new LinearLayout.LayoutParams(110, LinearLayout.LayoutParams.WRAP_CONTENT); + autoParams.setMargins(dpToPx(2), dpToPx(2), dpToPx(2), dpToPx(2)); + autoButton.setLayoutParams(autoParams); + autoButton.setPadding(8, 4, 8, 4); + autoButton.setCornerRadius(6); + autoButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_primary))); + autoButton.setOnClickListener(v -> { + autoFloorFollowEnabled = true; + floorToggleButton.setText("AUTO"); + collapseFloorSelector(); + Toast.makeText(getContext(), "Auto floor ON", Toast.LENGTH_SHORT).show(); + }); + floorButtonLayout.addView(autoButton); + + // Floor buttons + for (String floorName : sortedFloors) { + MaterialButton btn = new MaterialButton(getContext()); + btn.setText(floorName); + btn.setTextSize(13); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(110, LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(dpToPx(2), dpToPx(2), dpToPx(2), dpToPx(2)); + btn.setLayoutParams(params); + btn.setPadding(8, 4, 8, 4); + btn.setCornerRadius(6); + btn.setBackgroundTintList(android.content.res.ColorStateList.valueOf( + ContextCompat.getColor(getContext(), R.color.md_theme_secondary))); + btn.setOnClickListener(v -> { + NetworkUtils.BuildingData data = allBuildingsData.get(currentSelectedBuilding); + if (data != null) { + autoFloorFollowEnabled = false; + drawFloor(floorName, data); + venueFloor = floorName; + floorToggleButton.setText(floorName); + collapseFloorSelector(); + VenueManager.getInstance(requireContext()).setSelectedVenue(venueName, venueId, floorName); + updateVenueDisplay(); + Toast.makeText(getContext(), "Floor: " + floorName + " (manual)", Toast.LENGTH_SHORT).show(); + } + }); + floorButtonLayout.addView(btn); + } + + // Show as collapsed square + ViewGroup.LayoutParams lp = floorSelectorContainer.getLayoutParams(); + lp.width = dpToPx(56); + floorSelectorContainer.setLayoutParams(lp); + floorSelectorContainer.setVisibility(View.VISIBLE); + + backToRecordingButton.setOnClickListener(v -> returnToRecording()); + } + + @SuppressLint("ClickableViewAccessibility") + private void setupFloorToggleDragAndTap(MaterialButton toggleBtn) { + final float[] down = new float[4]; // rawX, rawY, transX, transY + final boolean[] dragging = {false}; + final int threshold = dpToPx(6); + + toggleBtn.setOnTouchListener((v, event) -> { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + down[0] = event.getRawX(); + down[1] = event.getRawY(); + down[2] = floorSelectorContainer.getTranslationX(); + down[3] = floorSelectorContainer.getTranslationY(); + dragging[0] = false; + return true; + case MotionEvent.ACTION_MOVE: + float dx = event.getRawX() - down[0]; + float dy = event.getRawY() - down[1]; + if (!dragging[0] && (Math.abs(dx) > threshold || Math.abs(dy) > threshold)) { + dragging[0] = true; + } + if (dragging[0]) { + floorSelectorContainer.setTranslationX(down[2] + dx); + floorSelectorContainer.setTranslationY(down[3] + dy); + } + return true; + case MotionEvent.ACTION_UP: + if (!dragging[0]) { + // It's a tap: toggle expand/collapse + if (isFloorSelectorExpanded) { + collapseFloorSelector(); + } else { + expandFloorSelector(); + } + } + return true; + } + return false; + }); + } + + private void expandFloorSelector() { + int startWidth = dpToPx(56); + ValueAnimator anim = ValueAnimator.ofInt(startWidth, floorSelectorExpandedWidth); + anim.setDuration(220); + anim.setInterpolator(new DecelerateInterpolator()); + anim.addUpdateListener(animation -> { + ViewGroup.LayoutParams lp = floorSelectorContainer.getLayoutParams(); + lp.width = (int) animation.getAnimatedValue(); + floorSelectorContainer.setLayoutParams(lp); + }); + anim.start(); + isFloorSelectorExpanded = true; + } + + private void collapseFloorSelector() { + int startWidth = floorSelectorContainer.getWidth(); + int endWidth = dpToPx(56); + ValueAnimator anim = ValueAnimator.ofInt(startWidth, endWidth); + anim.setDuration(180); + anim.setInterpolator(new DecelerateInterpolator()); + anim.addUpdateListener(animation -> { + ViewGroup.LayoutParams lp = floorSelectorContainer.getLayoutParams(); + lp.width = (int) animation.getAnimatedValue(); + floorSelectorContainer.setLayoutParams(lp); + }); + anim.start(); + isFloorSelectorExpanded = false; + } + + private int dpToPx(int dp) { + return Math.round(dp * getResources().getDisplayMetrics().density); + } + + private void returnToRecording() { + floorSelectorContainer.setTranslationX(0); + floorSelectorContainer.setTranslationY(0); + floorSelectorContainer.setVisibility(View.GONE); + isFloorSelectorExpanded = false; + floorToggleButton = null; + backToRecordingButton.setVisibility(View.GONE); + clearCurrentFloor(); + } + + private List sortFloorNames(List floorNames) { + Collections.sort(floorNames, (f1, f2) -> { + try { + int n1 = extractFloorNumber(f1); + int n2 = extractFloorNumber(f2); + return Integer.compare(n2, n1); + } catch (Exception e) { + return f1.compareTo(f2); + } + }); + return floorNames; + } + + private int extractFloorNumber(String floorName) { + String normalized = floorName.toLowerCase().replace("[", "").replace("]", "").trim(); + + if (normalized.isEmpty()) return 0; + + if (normalized.equals("g") || normalized.equals("gf") || normalized.equals("ground") + || normalized.equals("ground_floor") || normalized.equals("ug") + || normalized.equals("upper_ground")) { + return 0; + } + + if (normalized.equals("lg") || normalized.equals("lower_ground") + || normalized.equals("lower_ground_floor")) { + return -1; + } + + if (normalized.contains("basement")) { + Integer basementIndex = extractFirstInteger(normalized); + return basementIndex != null ? -Math.abs(basementIndex) : -1; + } + + if (normalized.matches("b\\d+")) { + return -Integer.parseInt(normalized.substring(1)); + } + + if (normalized.matches("b[-_ ]?\\d+")) { + String digits = normalized.replaceAll("[^0-9]", ""); + return digits.isEmpty() ? -1 : -Integer.parseInt(digits); + } + + if (normalized.matches("f\\d+")) { + return Integer.parseInt(normalized.substring(1)); } - // GNSS logic if you want to show GNSS error, etc. - float[] gnss = sensorFusion.getSensorValueMap().get(SensorTypes.GNSSLATLONG); - if (gnss != null && trajectoryMapFragment != null) { - // If user toggles showing GNSS in the map, call e.g. - if (trajectoryMapFragment.isGnssEnabled()) { - LatLng gnssLocation = new LatLng(gnss[0], gnss[1]); - LatLng currentLoc = trajectoryMapFragment.getCurrentLocation(); - if (currentLoc != null) { - double errorDist = UtilFunctions.distanceBetweenPoints(currentLoc, gnssLocation); - gnssError.setVisibility(View.VISIBLE); - gnssError.setText(String.format(getString(R.string.gnss_error) + "%.2fm", errorDist)); + String clean = normalized.replaceAll("[^0-9-]", ""); + if (clean.isEmpty() || clean.equals("-")) return 0; + return Integer.parseInt(clean); + } + + private Integer extractFirstInteger(String value) { + String digits = value.replaceAll("[^0-9]", ""); + if (digits.isEmpty()) { + return null; + } + return Integer.parseInt(digits); + } + + private int normalizeFloorForVenue(int inferredFloor, float elevationM) { + boolean isNucleus = "The Nucleus Building".equals(currentSelectedBuilding) + || "The Nucleus Building".equals(venueName) + || "The Nucleus".equals(venueName); + + if (!isNucleus) { + return inferredFloor; + } + + // If WiFi has provided an absolute floor anchor, trust the dual-phase result + // (WiFi floor + barometer delta) directly. This survives HVAC positive-pressure + // anomalies on F1 that push raw elevation to -2.8 m even on the ground floor. + if (sensorFusion.hasWifiFloorAnchor()) { + return inferredFloor; + } + + // Before WiFi anchor: trust the zone-gated result from getInferredFloor(). + // The previous raw-elevation heuristic was removed because the elevation estimate + // is affected by accelerometer noise (shaking causes ±5 m swings), which caused + // floor changes even when the user was nowhere near a lift or staircase. + + return inferredFloor; + } + + private void syncInferredFloorToVenue(int inferredFloor) { + if (!autoFloorFollowEnabled) { + return; + } + + if (!hasVenue || currentSelectedBuilding == null) { + venueFloor = String.valueOf(inferredFloor); + return; + } + + NetworkUtils.BuildingData data = allBuildingsData.get(currentSelectedBuilding); + if (data == null || data.floors == null || data.floors.isEmpty()) { + venueFloor = String.valueOf(inferredFloor); + return; + } + + String targetFloorName = findClosestFloorName(inferredFloor, data.floors.keySet()); + if (targetFloorName == null) { + venueFloor = String.valueOf(inferredFloor); + return; + } + + venueFloor = targetFloorName; + + if (!targetFloorName.equals(currentSelectedFloor)) { + Log.w(TAG, "[FloorChange] *** FLOOR CHANGING: " + currentSelectedFloor + " → " + targetFloorName + + " | inferredFloor=" + inferredFloor + + " | elev=" + sensorFusion.getElevation() + + " | wifiAnchor=" + sensorFusion.hasWifiFloorAnchor() + " ***"); + drawFloor(targetFloorName, data); + VenueManager.getInstance(requireContext()) + .setSelectedVenue(venueName, venueId, targetFloorName); + updateVenueDisplay(); + Log.d(TAG, "Auto floor switched to: " + targetFloorName + " (inferred=" + inferredFloor + ")"); + } + } + + private String findClosestFloorName(int inferredFloor, Collection floorNames) { + String exactMatch = null; + String closest = null; + int minDistance = Integer.MAX_VALUE; + + for (String name : floorNames) { + int mappedFloor; + try { + mappedFloor = extractFloorNumber(name); + } catch (Exception ignored) { + continue; + } + + if (mappedFloor == inferredFloor) { + exactMatch = name; + break; + } + + int distance = Math.abs(mappedFloor - inferredFloor); + if (distance < minDistance) { + minDistance = distance; + closest = name; + } + } + + return exactMatch != null ? exactMatch : closest; + } + + // LOAD BUILDING FROM API + private void loadBuildingDataForSelection(String buildingName) { + BuildingLocation selectedBuilding = null; + for (BuildingLocation building : TARGET_BUILDINGS) { + if (building.name.equals(buildingName)) { + selectedBuilding = building; + break; + } + } + + if (selectedBuilding == null) { + Toast.makeText(getContext(), "Unknown building: " + buildingName, Toast.LENGTH_SHORT).show(); + return; + } + + Toast.makeText(getContext(), "Loading " + buildingName + "...", Toast.LENGTH_SHORT).show(); + + final BuildingLocation buildingToLoad = selectedBuilding; + NetworkUtils.fetchFloorPlan( + buildingToLoad.center.latitude, + buildingToLoad.center.longitude, + new NetworkUtils.Callback() { + @Override + public void onSuccess(NetworkUtils.BuildingData buildingData) { + if (isAdded() && getContext() != null) { + allBuildingsData.put(buildingToLoad.name, buildingData); + Log.d(TAG, "Loaded " + buildingToLoad.name + ": " + + buildingData.floors.size() + " floors"); + showVenueSelectionDialog(buildingToLoad.name); + } + } + + @Override + public void onError(String error) { + if (isAdded() && getContext() != null) { + Toast.makeText(getContext(), "Failed to load " + buildingToLoad.name, Toast.LENGTH_SHORT).show(); + } + Log.e(TAG, "Failed to load " + buildingToLoad.name + ": " + error); + } + } + ); + } + + // VENUE DISPLAY UI + private void setupVenueDisplay() { + updateVenueDisplay(); + + if (changeVenueButton != null) { + changeVenueButton.setOnClickListener(v -> { + // Allow user to change venue during recording + Toast.makeText(getContext(), + "Tap building outline to select venue", + Toast.LENGTH_LONG).show(); + }); + } + } + + private void updateVenueDisplay() { + if (venueInfoText == null) return; + + if (hasVenue && !venueName.isEmpty()) { + String displayText = "📍 " + venueName; + if (!venueFloor.isEmpty()) { + displayText += " - " + venueFloor; + } + venueInfoText.setText(displayText); + venueInfoText.setVisibility(View.VISIBLE); + if (changeVenueButton != null) { + changeVenueButton.setVisibility(View.VISIBLE); + } + } else { + venueInfoText.setText("📍 Outdoor (No venue selected)"); + venueInfoText.setVisibility(View.VISIBLE); + if (changeVenueButton != null) { + changeVenueButton.setVisibility(View.VISIBLE); + } + } + } + + // BOTTOM DRAWER + + private void setupBottomDrawer(View view) { + bottomDrawer = view.findViewById(R.id.bottomDrawer); + expandDrawerTab = view.findViewById(R.id.expandDrawerTab); + + // Swipe anywhere on the white panel to hide it + bottomDrawer.setOnSwipeDownListener(new SwipeDownLinearLayout.OnSwipeDownListener() { + @Override + public void onDrag(float dy) { + // Panel follows the finger in real time + bottomDrawer.setTranslationY(dy); + } + + @Override + public void onRelease(float dy) { + if (dy > bottomDrawer.getHeight() * 0.25f) { + // Swiped far enough — complete the hide + hideBottomDrawer(); + } else { + // Not far enough — spring back to original position + bottomDrawer.animate() + .translationY(0) + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .start(); } - trajectoryMapFragment.updateGNSS(gnssLocation); + } + }); + + expandDrawerTab.setOnClickListener(v -> showBottomDrawer()); + } + + private void hideBottomDrawer() { + // Animate from wherever the finger released to fully off-screen + bottomDrawer.animate() + .translationY(bottomDrawer.getHeight()) + .setDuration(220) + .setInterpolator(new DecelerateInterpolator()) + .withEndAction(() -> { + bottomDrawer.setVisibility(View.INVISIBLE); + expandDrawerTab.setVisibility(View.VISIBLE); + }) + .start(); + } + + private void showBottomDrawer() { + expandDrawerTab.setVisibility(View.GONE); + bottomDrawer.setVisibility(View.VISIBLE); + bottomDrawer.setTranslationY(bottomDrawer.getHeight()); + bottomDrawer.animate() + .translationY(0) + .setDuration(280) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + // RECORDING CONTROLS + private void setupRecordingControls() { + // Complete button + completeButton.setOnClickListener(v -> { + if (autoStop != null) autoStop.cancel(); + sensorFusion.stopRecording(); + + // Pass venue info to correction screen + Bundle venueBundle = new Bundle(); + venueBundle.putBoolean("has_venue", hasVenue); + venueBundle.putString("venue_id", venueId); + venueBundle.putString("venue_name", venueName); + venueBundle.putString("venue_floor", venueFloor); + + Log.d(TAG, "Recording completed with venue: " + venueName); + ((RecordingActivity) requireActivity()).showCorrectionScreen(); + }); + + // Cancel button + cancelButton.setOnClickListener(v -> { + AlertDialog dialog = new AlertDialog.Builder(requireActivity()) + .setTitle("Confirm Cancel") + .setMessage("Cancel recording? Progress will be lost!") + .setNegativeButton("Yes", (dialogInterface, which) -> { + sensorFusion.stopRecording(); + if (autoStop != null) autoStop.cancel(); + requireActivity().onBackPressed(); + }) + .setPositiveButton("No", (dialogInterface, which) -> { + dialogInterface.dismiss(); + }) + .create(); + + dialog.setOnShowListener(dialogInterface -> { + android.widget.Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); + negativeButton.setTextColor(Color.RED); + }); + + dialog.show(); + }); + + // Set Initial Position button + setInitialPositionButton.setOnClickListener(v -> { + try { + float[] gnssPos = sensorFusion.getGNSSLatitude(true); + if (gnssPos != null && gnssPos.length >= 2) { + sensorFusion.setInitialPosition(gnssPos[0], gnssPos[1], 0); + initialPositionStatus.setText("Set ✓"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_green_dark)); + initialPositionIndicator.setVisibility(View.VISIBLE); + Toast.makeText(requireContext(), "Position: " + String.format("%.4f, %.4f", gnssPos[0], gnssPos[1]), Toast.LENGTH_LONG).show(); + Log.i(TAG, String.format("Initial position set | Lat: %.6f | Lon: %.6f", gnssPos[0], gnssPos[1])); + } else { + LatLng fallbackLocation = currentLocation != null ? currentLocation : + new LatLng(55.9234, -3.1761); // Default to campus center + sensorFusion.setInitialPosition((float) fallbackLocation.latitude, (float) fallbackLocation.longitude, 0); + initialPositionStatus.setText("Set ✓ (approx)"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_orange_dark)); + initialPositionIndicator.setVisibility(View.VISIBLE); + Toast.makeText(requireContext(), "Using approximate position (GPS unavailable)", Toast.LENGTH_LONG).show(); + Log.w(TAG, String.format("GPS unavailable, using fallback | Lat: %.6f | Lon: %.6f", fallbackLocation.latitude, fallbackLocation.longitude)); + } + } catch (Exception e) { + Log.e(TAG, "Error in Set Position button: " + e.getMessage(), e); + Toast.makeText(requireContext(), "Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); + } + }); + + // Mark Test Point button + markTestPointButton.setOnClickListener(v -> { + try { + // Get current location + LatLng testPointLocation = currentLocation != null ? currentLocation : + new LatLng(55.9234, -3.1761); + + // Add test point via SensorFusion + int pointNumber = sensorFusion.addTestPoint( + testPointLocation.latitude, + testPointLocation.longitude, + venueFloor + ); + + // Add marker on map + addTestPointMarker(testPointLocation, pointNumber); + + // Update UI count + testPointsCount.setText(String.valueOf(sensorFusion.getTestPointCount())); + + Toast.makeText(requireContext(), "Test Point #" + pointNumber + " marked", Toast.LENGTH_SHORT).show(); + Log.d(TAG, "Test point #" + pointNumber + " marked at: " + testPointLocation.latitude + ", " + testPointLocation.longitude); + + } catch (Exception e) { + Log.e(TAG, "Error marking test point: " + e.getMessage(), e); + Toast.makeText(requireContext(), "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + + // Initialize UI defaults + if (gnssError != null) gnssError.setVisibility(View.GONE); + if (elevation != null) elevation.setText(getString(R.string.elevation, "0")); + if (distanceTravelled != null) distanceTravelled.setText(getString(R.string.meter, "0")); + + // Initialize new sensor data UI + updateTrajectoryIdDisplay(); + // Check actual initial position state instead of hardcoding "Not set" + if (initialPositionStatus != null) { + if (sensorFusion.isInitialPositionSet()) { + initialPositionStatus.setText("Set ✓"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_green_dark)); + if (initialPositionIndicator != null) initialPositionIndicator.setVisibility(View.VISIBLE); } else { - gnssError.setVisibility(View.GONE); - trajectoryMapFragment.clearGNSS(); + initialPositionStatus.setText("Not set"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_red_dark)); + if (initialPositionIndicator != null) initialPositionIndicator.setVisibility(View.GONE); + } + } + if (wifiFingerprintsCount != null) wifiFingerprintsCount.setText("0"); + if (correctedPositionsCount != null) correctedPositionsCount.setText("0"); + if (testPointsCount != null) testPointsCount.setText("0"); // Initialize test points counter + } + + // UI UPDATE + private void updateTrajectoryIdDisplay() { + if (trajectoryIdText == null) return; + String trajectoryId = sensorFusion.getTrajectoryId(); + if (trajectoryId != null && !trajectoryId.isEmpty()) { + trajectoryIdText.setText(trajectoryId); + } else { + trajectoryIdText.setText("--"); + } + } + + // Minimum distance (in meters) to add a new trajectory point + private static final float MIN_TRAJECTORY_POINT_DISTANCE = 0.25f; + private LatLng lastTrajectoryPoint = null; + private LatLng secondLastTrajectoryPoint = null; // For direction checking + private LatLng lastCameraLocation = null; + private long lastCameraUpdateTime = 0; + private static final long CAMERA_UPDATE_INTERVAL_MS = 250; + private static final float MIN_CAMERA_MOVE_DISTANCE_M = 0.25f; + + // Anti-jitter: smooth position output + private double smoothedLat = 0.0; + private double smoothedLng = 0.0; + private boolean positionInitialized = false; + private static final float POSITION_SMOOTHING_BASE = 0.85f; + private static final float POSITION_SMOOTHING_FAST = 0.97f; + + private void updateUIandPosition() { + // Sensor data counts + updateTrajectoryIdDisplay(); + + if (wifiFingerprintsCount != null) { + wifiFingerprintsCount.setText(String.valueOf(sensorFusion.getWiFiFingerprintCount())); + } + if (correctedPositionsCount != null) { + correctedPositionsCount.setText(String.valueOf(sensorFusion.getCorrectedPositionCount())); + } + if (testPointsCount != null) { + testPointsCount.setText(String.valueOf(sensorFusion.getTestPointCount())); + } + if (initialPositionStatus != null) { + if (sensorFusion.isInitialPositionSet()) { + initialPositionStatus.setText("Set ✓"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_green_dark)); + if (initialPositionIndicator != null) initialPositionIndicator.setVisibility(View.VISIBLE); + } else { + initialPositionStatus.setText("Not set"); + initialPositionStatus.setTextColor(requireContext().getColor(android.R.color.holo_red_dark)); + if (initialPositionIndicator != null) initialPositionIndicator.setVisibility(View.GONE); + } + } + + // Elevation & distance + float[] pdrValues = sensorFusion.getSmoothedPDRPosition(); + if (pdrValues != null) { + float deltaX = pdrValues[0] - previousPosX; + float deltaY = pdrValues[1] - previousPosY; + float movementDelta = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (movementDelta > 0.01f && distanceTravelled != null) { + distance += movementDelta; + distanceTravelled.setText(getString(R.string.meter, String.format("%.2f", distance))); + } + previousPosX = pdrValues[0]; + previousPosY = pdrValues[1]; + } + + if (elevation != null) { + elevation.setText(getString(R.string.elevation, + String.format("%.1f", sensorFusion.getElevation()))); + } + + // Floor display + int inferredFloor = sensorFusion.getInferredFloor(); + float elevForLog = sensorFusion.getElevation(); + int normalizedFloor = normalizeFloorForVenue(inferredFloor, elevForLog); + if (inferredFloor != normalizedFloor) { + Log.w(TAG, "[ZoneGate] normalizeFloorForVenue OVERRIDE: inferred=" + inferredFloor + + " → normalized=" + normalizedFloor + " elev=" + elevForLog + + " wifiAnchor=" + sensorFusion.hasWifiFloorAnchor()); + } + if (floorDisplayText != null) { + floorDisplayText.setText("Floor: " + normalizedFloor); + } + syncInferredFloorToVenue(normalizedFloor); + + // ── Position: prefer Particle Filter, fall back to SimplePositionFusion ── + LatLng rawLocation = sensorFusion.getParticleFilterPosition(); + FusionManager.PositionSource source = sensorFusion.getLastPositionSource(); + + if (rawLocation == null) { + // Fallback to legacy fused position + rawLocation = sensorFusion.getFusedPosition(); + source = FusionManager.PositionSource.PDR; + } + + if (rawLocation == null) { + // Last resort: use latest GNSS fix + float[] latLngArray = sensorFusion.getGNSSLatitude(false); + if (latLngArray != null && latLngArray[0] != 0) { + rawLocation = new LatLng(latLngArray[0], latLngArray[1]); + source = FusionManager.PositionSource.GNSS; + } + } + + if (rawLocation == null || googleMap == null) return; + + long now = System.currentTimeMillis(); + + // Position smoothing (controlled by the Smooth Trajectory toggle) + // OFF (default): use EKF+PF fusion output directly — no extra lag. + // ON: apply a low-pass filter (alpha=0.85) on top of the fusion output. + // This deliberately introduces systematic lag so the user can see + // the trailing-marker artefact when reversing direction. + if (smoothTrajectoryEnabled && positionInitialized) { + smoothedLat = POSITION_SMOOTHING_BASE * smoothedLat + + (1.0f - POSITION_SMOOTHING_BASE) * rawLocation.latitude; + smoothedLng = POSITION_SMOOTHING_BASE * smoothedLng + + (1.0f - POSITION_SMOOTHING_BASE) * rawLocation.longitude; + } else { + smoothedLat = rawLocation.latitude; + smoothedLng = rawLocation.longitude; + } + positionInitialized = true; + LatLng newLocation = new LatLng(smoothedLat, smoothedLng); + currentLocation = newLocation; + + // Color-coded trajectory update (1 s interval OR on movement) + boolean timeElapsed = (now - lastTrajectoryUpdateTime) >= TRAJECTORY_UPDATE_INTERVAL_MS; + boolean movedEnough = (lastTrajectoryPoint == null) + || calculateDistance(lastTrajectoryPoint, newLocation) > MIN_TRAJECTORY_POINT_DISTANCE; + + updateRawSensorObservationOverlays(now); + + if (timeElapsed || movedEnough) { + // Always draw fused trajectory as a red line. + if (fusedPolyline != null) { + List pts = fusedPolyline.getPoints(); + pts.add(newLocation); + fusedPolyline.setPoints(pts); + } + + // Persist the same fused point the user sees, so replay matches recording. + sensorFusion.addReplayTrackPoint(newLocation.latitude, newLocation.longitude); + + secondLastTrajectoryPoint = lastTrajectoryPoint; + lastTrajectoryPoint = newLocation; + lastTrajectoryUpdateTime = now; + lastPolylineSource = source; + } + + // User marker + if (userMarker == null) { + // Keep fused marker fixed as a red pin. + userMarker = googleMap.addMarker(new MarkerOptions() + .position(newLocation) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)) + .title("Fused Position") + .flat(true) + .anchor(0.5f, 0.5f) + .zIndex(999)); + } else { + userMarker.setPosition(newLocation); + userMarker.setRotation((float) Math.toDegrees(sensorFusion.passOrientation())); + } + + // Camera + boolean cameraTimeOk = (now - lastCameraUpdateTime) > CAMERA_UPDATE_INTERVAL_MS; + boolean cameraMoveEnough = lastCameraLocation == null + || calculateDistance(lastCameraLocation, newLocation) >= MIN_CAMERA_MOVE_DISTANCE_M; + if (cameraTimeOk && cameraMoveEnough) { + googleMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation)); + lastCameraUpdateTime = now; + lastCameraLocation = newLocation; + } + } + + private void addRawObservationCircle(LatLng position, FusionManager.PositionSource source) { + if (googleMap == null) return; + + int fillColor; + List targetList; + + if (source == FusionManager.PositionSource.GNSS) { + fillColor = Color.argb(120, 0, 200, 0); + targetList = gnssObservationCircles; + } else if (source == FusionManager.PositionSource.WIFI) { + fillColor = Color.argb(120, 255, 140, 0); + targetList = wifiObservationCircles; + } else { + fillColor = Color.argb(120, 40, 100, 255); + targetList = pdrObservationCircles; + } + + Circle circle = googleMap.addCircle(new CircleOptions() + .center(position) + .radius(RAW_OBSERVATION_RADIUS_M) + .fillColor(fillColor) + .strokeWidth(0f) + .zIndex(120)); + + if (circle != null) { + targetList.add(circle); + while (targetList.size() > MAX_RAW_OBSERVATIONS) { + Circle oldCircle = targetList.remove(0); + if (oldCircle != null) { + oldCircle.remove(); + } } } + } + + private void updateRawSensorObservationOverlays(long nowMs) { + LatLng pdrEstimate = sensorFusion.getRawPdrLatLng(); + if (pdrEstimate != null) { + maybeAddRawObservation( + pdrEstimate, + FusionManager.PositionSource.PDR, + nowMs, + lastPdrObservation, + lastPdrObservationTime + ); + } + + float[] gnss = sensorFusion.getGNSSLatitude(false); + if (gnss != null && gnss.length >= 2 && gnss[0] != 0f && gnss[1] != 0f) { + LatLng gnssLatLng = new LatLng(gnss[0], gnss[1]); + maybeAddRawObservation( + gnssLatLng, + FusionManager.PositionSource.GNSS, + nowMs, + lastGnssObservation, + lastGnssObservationTime + ); + } - // Update previous - previousPosX = pdrValues[0]; - previousPosY = pdrValues[1]; + LatLng wifiLatLng = sensorFusion.getLatLngWifiPositioning(); + if (wifiLatLng != null) { + maybeAddRawObservation( + wifiLatLng, + FusionManager.PositionSource.WIFI, + nowMs, + lastWifiObservation, + lastWifiObservationTime + ); + } + } + + private void maybeAddRawObservation( + LatLng current, + FusionManager.PositionSource source, + long nowMs, + LatLng previous, + long previousTimeMs) { + + if (current == null) { + return; + } + + boolean timeOk = (nowMs - previousTimeMs) >= RAW_OBSERVATION_MIN_INTERVAL_MS; + boolean distOk = previous == null + || calculateDistance(previous, current) >= RAW_OBSERVATION_MIN_DISTANCE_M; + + if (!timeOk || !distOk) { + return; + } + + addRawObservationCircle(current, source); + + if (source == FusionManager.PositionSource.GNSS) { + lastGnssObservation = current; + lastGnssObservationTime = nowMs; + } else if (source == FusionManager.PositionSource.WIFI) { + lastWifiObservation = current; + lastWifiObservationTime = nowMs; + } else { + lastPdrObservation = current; + lastPdrObservationTime = nowMs; + } } /** - * Start the blinking effect for the recording icon. + * Calculate distance between two LatLng points in meters. */ + private float calculateDistance(LatLng p1, LatLng p2) { + double lat1 = Math.toRadians(p1.latitude); + double lat2 = Math.toRadians(p2.latitude); + double dLat = Math.toRadians(p2.latitude - p1.latitude); + double dLng = Math.toRadians(p2.longitude - p1.longitude); + + double a = Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(lat1) * Math.cos(lat2) * + Math.sin(dLng/2) * Math.sin(dLng/2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + return (float) (6371000 * c); // Earth radius in meters + } + private void blinkingRecordingIcon() { Animation blinking = new AlphaAnimation(1, 0); blinking.setDuration(800); @@ -286,13 +2141,20 @@ private void blinkingRecordingIcon() { public void onPause() { super.onPause(); refreshDataHandler.removeCallbacks(refreshDataTask); + if (wiFiFingerprintTask != null) { + refreshDataHandler.removeCallbacks(wiFiFingerprintTask); + } + if (blEDataTask != null) { + refreshDataHandler.removeCallbacks(blEDataTask); + } } @Override public void onResume() { super.onResume(); - if(!this.settings.getBoolean("split_trajectory", false)) { - refreshDataHandler.postDelayed(refreshDataTask, 500); + if (!this.settings.getBoolean("split_trajectory", false)) { + refreshDataHandler.postDelayed(refreshDataTask, UI_REFRESH_INTERVAL_MS); } } + } diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java index d15a4a83..ab52abda 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/ReplayFragment.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.SeekBar; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,6 +52,7 @@ public class ReplayFragment extends Fragment { private float initialLat = 0f; private float initialLon = 0f; private String filePath = ""; + private String initialFloor = ""; // venue floor extracted from the trajectory file private int lastIndex = -1; // UI Controls @@ -74,6 +76,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { filePath = getArguments().getString(ReplayActivity.EXTRA_TRAJECTORY_FILE_PATH, ""); initialLat = getArguments().getFloat(ReplayActivity.EXTRA_INITIAL_LAT, 0f); initialLon = getArguments().getFloat(ReplayActivity.EXTRA_INITIAL_LON, 0f); + initialFloor = getArguments().getString(ReplayActivity.EXTRA_FLOOR, ""); } // Log the received data @@ -125,6 +128,19 @@ public void onViewCreated(@NonNull View view, getChildFragmentManager().findFragmentById(R.id.replayMapFragmentContainer); if (trajectoryMapFragment == null) { trajectoryMapFragment = new TrajectoryMapFragment(); + + // Pass initial coordinates and enable indoor map loading + Bundle mapArgs = new Bundle(); + mapArgs.putBoolean("has_venue", true); + mapArgs.putString("venue_id", ""); + mapArgs.putString("venue_name", ""); + // Use the floor stored in the trajectory file; empty string lets + // TrajectoryMapFragment fall back to the first available floor from the API. + mapArgs.putString("venue_floor", initialFloor); + mapArgs.putDouble("initial_lat", (double) initialLat); + mapArgs.putDouble("initial_lon", (double) initialLon); + trajectoryMapFragment.setArguments(mapArgs); + getChildFragmentManager() .beginTransaction() .replace(R.id.replayMapFragmentContainer, trajectoryMapFragment) @@ -133,18 +149,18 @@ public void onViewCreated(@NonNull View view, + // Always set initial camera position first, so map is centered correctly + if (initialLat != 0f || initialLon != 0f) { + LatLng startPoint = new LatLng(initialLat, initialLon); + Log.i(TAG, "Setting initial map position: " + startPoint.toString()); + trajectoryMapFragment.setInitialCameraPosition(startPoint); + } + // 1) Check if the file contains any GNSS data boolean gnssExists = hasAnyGnssData(replayData); if (gnssExists) { showGnssChoiceDialog(); - } else { - // No GNSS data -> automatically use param lat/lon - if (initialLat != 0f || initialLon != 0f) { - LatLng startPoint = new LatLng(initialLat, initialLon); - Log.i(TAG, "Setting initial map position: " + startPoint.toString()); - trajectoryMapFragment.setInitialCameraPosition(startPoint); - } } // Initialize UI controls @@ -161,8 +177,9 @@ public void onViewCreated(@NonNull View view, // Button Listeners playPauseButton.setOnClickListener(v -> { - if (replayData.isEmpty()) { - Log.w(TAG, "Play/Pause button pressed but replayData is empty."); + if (replayData.size() < 2) { + Log.w(TAG, "Play/Pause pressed but replayData has insufficient points: " + replayData.size()); + Toast.makeText(requireContext(), "Trajectory has too few replay points.", Toast.LENGTH_SHORT).show(); return; } if (isPlaying) { @@ -184,6 +201,7 @@ public void onViewCreated(@NonNull View view, restartButton.setOnClickListener(v -> { if (replayData.isEmpty()) return; currentIndex = 0; + lastIndex = -1; // Reset lastIndex so updateMapForIndex does a full redraw playbackSeekBar.setProgress(0); Log.i(TAG, "Restart button pressed. Resetting playback to index 0."); updateMapForIndex(0); @@ -224,8 +242,12 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @Override public void onStopTrackingTouch(SeekBar seekBar) {} }); - if (!replayData.isEmpty()) { - updateMapForIndex(0); + // Don't call updateMapForIndex(0) here - the map isn't ready yet. + // lastIndex stays at -1 so the first playback call will do a full redraw. + if (replayData.size() >= 2) { + Log.i(TAG, "Replay data loaded with " + replayData.size() + " points. Ready to play."); + } else { + Log.w(TAG, "WARNING: replayData has insufficient points (" + replayData.size() + ")."); } } @@ -272,7 +294,7 @@ private void showGnssChoiceDialog() { } private void setupInitialMapPosition(float latitude, float longitude) { - LatLng startPoint = new LatLng(initialLat, initialLon); + LatLng startPoint = new LatLng(latitude, longitude); Log.i(TAG, "Setting initial map position: " + startPoint.toString()); trajectoryMapFragment.setInitialCameraPosition(startPoint); } @@ -283,7 +305,7 @@ private void setupInitialMapPosition(float latitude, float longitude) { private LatLng getFirstGnssLocation(List data) { for (TrajParser.ReplayPoint point : data) { if (point.gnssLocation != null) { - return new LatLng(replayData.get(0).gnssLocation.latitude, replayData.get(0).gnssLocation.longitude); + return new LatLng(point.gnssLocation.latitude, point.gnssLocation.longitude); } } return null; // None found @@ -299,17 +321,26 @@ private LatLng getFirstGnssLocation(List data) { public void run() { if (!isPlaying || replayData.isEmpty()) return; - Log.i(TAG, "Playing index: " + currentIndex); - updateMapForIndex(currentIndex); - currentIndex++; - playbackSeekBar.setProgress(currentIndex); - - if (currentIndex < replayData.size()) { - playbackHandler.postDelayed(this, PLAYBACK_INTERVAL_MS); - } else { - Log.i(TAG, "Playback completed. Reached end of data."); - isPlaying = false; - playPauseButton.setText("Play"); + try { + Log.i(TAG, "Playing index: " + currentIndex); + updateMapForIndex(currentIndex); + currentIndex++; + playbackSeekBar.setProgress(currentIndex); + + if (currentIndex < replayData.size()) { + playbackHandler.postDelayed(this, PLAYBACK_INTERVAL_MS); + } else { + Log.i(TAG, "Playback completed. Reached end of data."); + isPlaying = false; + playPauseButton.setText("Play"); + } + } catch (Exception e) { + Log.e(TAG, "Error during playback at index " + currentIndex + ": " + e.getMessage(), e); + // Continue playback from next index rather than crashing silently + currentIndex++; + if (currentIndex < replayData.size() && isPlaying) { + playbackHandler.postDelayed(this, PLAYBACK_INTERVAL_MS); + } } } }; @@ -324,6 +355,9 @@ public void run() { private void updateMapForIndex(int newIndex) { if (newIndex < 0 || newIndex >= replayData.size()) return; + // Check if map is ready before drawing + if (trajectoryMapFragment == null) return; + // Detect if user is playing sequentially (lastIndex + 1) // or is skipping around (backwards, or jump forward) boolean isSequentialForward = (newIndex == lastIndex + 1); @@ -362,4 +396,4 @@ public void onDestroyView() { super.onDestroyView(); playbackHandler.removeCallbacks(playbackRunnable); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/StartLocationFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/StartLocationFragment.java index ee14f69f..a591ae31 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/StartLocationFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/StartLocationFragment.java @@ -1,6 +1,7 @@ package com.openpositioning.PositionMe.presentation.fragment; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,7 @@ import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; import com.openpositioning.PositionMe.presentation.activity.ReplayActivity; import com.openpositioning.PositionMe.sensors.SensorFusion; +import com.openpositioning.PositionMe.sensors.WiFiPositioning; import com.openpositioning.PositionMe.utils.NucleusBuildingManager; /** @@ -72,8 +74,25 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, } View rootView = inflater.inflate(R.layout.fragment_startlocation, container, false); - // Obtain the start position from the GPS data from the SensorFusion class - startPosition = sensorFusion.getGNSSLatitude(false); + // Check if the trajectory file's original recording position was passed via arguments + // (from ReplayActivity). If available, use it instead of current GPS. + boolean hasFilePosition = false; + if (getArguments() != null) { + float fileLat = getArguments().getFloat(ReplayActivity.EXTRA_FILE_INITIAL_LAT, 0f); + float fileLon = getArguments().getFloat(ReplayActivity.EXTRA_FILE_INITIAL_LON, 0f); + if (fileLat != 0f || fileLon != 0f) { + startPosition[0] = fileLat; + startPosition[1] = fileLon; + hasFilePosition = true; + Log.i("StartLocationFragment", "Using file's recording position: " + fileLat + ", " + fileLon); + } + } + + // If no file position, fall back to current GPS from SensorFusion + if (!hasFilePosition) { + startPosition = sensorFusion.getGNSSLatitude(false); + } + // If no location found, zoom the map out if (startPosition[0] == 0 && startPosition[1] == 0) { zoom = 1f; @@ -81,6 +100,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, zoom = 19f; } + // Capture as final so it can be referenced inside the anonymous OnMapReadyCallback. + final boolean filePositionUsed = hasFilePosition; + // Initialize map fragment SupportMapFragment supportMapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.startMap); @@ -109,38 +131,37 @@ public void onMapReady(GoogleMap mMap) { nucleusBuildingManager = new NucleusBuildingManager(mMap); nucleusBuildingManager.getIndoorMapManager().hideMap(); - // Add a marker at the current GPS location and move the camera + // Place a fixed (non-draggable) marker at the GPS-estimated position. + // The start location is set automatically by GPS/WiFi and cannot be + // modified manually by the user. position = new LatLng(startPosition[0], startPosition[1]); - Marker startMarker = mMap.addMarker(new MarkerOptions() + final Marker[] markerRef = {mMap.addMarker(new MarkerOptions() .position(position) - .title("Start Position") - .draggable(true)); + .title("Start location (set automatically)") + .draggable(false))}; mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position, zoom)); - // Drag listener for the marker to update the start position when dragged - mMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() { - /** - * {@inheritDoc} - */ - @Override - public void onMarkerDragStart(Marker marker) {} - - /** - * {@inheritDoc} - * Updates the start position of the user. - */ - @Override - public void onMarkerDragEnd(Marker marker) { - startPosition[0] = (float) marker.getPosition().latitude; - startPosition[1] = (float) marker.getPosition().longitude; - } - - /** - * {@inheritDoc} - */ - @Override - public void onMarkerDrag(Marker marker) {} - }); + // Request WiFi-based positioning to refine the initial marker automatically. + // Does NOT touch positionFusion / fusionManager — no effect on recording. + if (!filePositionUsed) { + sensorFusion.requestWifiPositioningForInitialLocation(new WiFiPositioning.VolleyCallback() { + @Override + public void onSuccess(LatLng wifiLocation, int floor) { + if (isAdded()) { + startPosition[0] = (float) wifiLocation.latitude; + startPosition[1] = (float) wifiLocation.longitude; + markerRef[0].setPosition(wifiLocation); + mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(wifiLocation, zoom)); + Log.i("StartLocationFragment", "Initial position refined by WiFi: " + + wifiLocation.latitude + ", " + wifiLocation.longitude); + } + } + @Override + public void onError(String message) { + Log.w("StartLocationFragment", "WiFi initial positioning failed: " + message); + } + }); + } } }); diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SwipeDownLinearLayout.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SwipeDownLinearLayout.java new file mode 100644 index 00000000..0fd4102a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SwipeDownLinearLayout.java @@ -0,0 +1,95 @@ +package com.openpositioning.PositionMe.presentation.fragment; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.widget.LinearLayout; + +/** + * LinearLayout that intercepts downward swipe gestures across its full area, + * while still allowing child views (buttons, switches, etc.) to receive tap events normally. + * + * Uses onInterceptTouchEvent so that: + * - Tapping a button inside the layout works as usual. + * - Swiping downward anywhere on the layout triggers the drag callbacks. + */ +public class SwipeDownLinearLayout extends LinearLayout { + + public interface OnSwipeDownListener { + /** Called each frame while the user is dragging down; dy >= 0. */ + void onDrag(float dy); + /** Called when the finger lifts; decide whether to hide or spring back. */ + void onRelease(float dy); + } + + private float startY; + private boolean intercepting = false; + private final int touchSlop; + private OnSwipeDownListener swipeListener; + + public SwipeDownLinearLayout(Context context) { + this(context, null); + } + + public SwipeDownLinearLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SwipeDownLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + public void setOnSwipeDownListener(OnSwipeDownListener l) { + swipeListener = l; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + startY = ev.getRawY(); + intercepting = false; + break; + + case MotionEvent.ACTION_MOVE: + float dy = ev.getRawY() - startY; + // Only start intercepting for a clear downward swipe + if (dy > touchSlop * 1.5f && !intercepting) { + intercepting = true; + return true; // steal the event stream from child views + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + intercepting = false; + break; + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!intercepting) return super.onTouchEvent(ev); + + switch (ev.getAction()) { + case MotionEvent.ACTION_MOVE: + float dy = ev.getRawY() - startY; + if (dy >= 0 && swipeListener != null) { + swipeListener.onDrag(dy); + } + return true; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (swipeListener != null) { + swipeListener.onRelease(Math.max(0, ev.getRawY() - startY)); + } + intercepting = false; + return true; + } + return super.onTouchEvent(ev); + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java index eb0bad65..9b34fcad 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/TrajectoryMapFragment.java @@ -10,77 +10,94 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; -import com.google.android.material.switchmaterial.SwitchMaterial; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.Polygon; +import com.google.android.gms.maps.model.PolygonOptions; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.switchmaterial.SwitchMaterial; import com.openpositioning.PositionMe.R; -import com.openpositioning.PositionMe.sensors.SensorFusion; import com.openpositioning.PositionMe.utils.IndoorMapManager; import com.openpositioning.PositionMe.utils.UtilFunctions; -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.*; import java.util.ArrayList; +import java.util.Collections; import java.util.List; - /** * A fragment responsible for displaying a trajectory map using Google Maps. - *

- * The TrajectoryMapFragment provides a map interface for visualizing movement trajectories, - * GNSS tracking, and indoor mapping. It manages map settings, user interactions, and real-time - * updates to user location and GNSS markers. - *

- * Key Features: - * - Displays a Google Map with support for different map types (Hybrid, Normal, Satellite). - * - Tracks and visualizes user movement using polylines. - * - Supports GNSS position updates and visual representation. - * - Includes indoor mapping with floor selection and auto-floor adjustments. - * - Allows user interaction through map controls and UI elements. - * - * @see com.openpositioning.PositionMe.presentation.activity.RecordingActivity The activity hosting this fragment. - * @see com.openpositioning.PositionMe.utils.IndoorMapManager Utility for managing indoor map overlays. - * @see com.openpositioning.PositionMe.utils.UtilFunctions Utility functions for UI and graphics handling. - * - * @author Mate Stodulka + * Updated to support dynamic Indoor Map display via NetworkUtils. */ - public class TrajectoryMapFragment extends Fragment { - private GoogleMap gMap; // Google Maps instance - private LatLng currentLocation; // Stores the user's current location - private Marker orientationMarker; // Marker representing user's heading - private Marker gnssMarker; // GNSS position marker - private Polyline polyline; // Polyline representing user's movement path - private boolean isRed = true; // Tracks whether the polyline color is red - private boolean isGnssOn = false; // Tracks if GNSS tracking is enabled - - private Polyline gnssPolyline; // Polyline for GNSS path - private LatLng lastGnssLocation = null; // Stores the last GNSS location - - private LatLng pendingCameraPosition = null; // Stores pending camera movement - private boolean hasPendingCameraMove = false; // Tracks if camera needs to move - - private IndoorMapManager indoorMapManager; // Manages indoor mapping - private SensorFusion sensorFusion; - + private static final String TAG = "TrajectoryMapFragment"; + + private GoogleMap gMap; + // Stores the user's current location + private LatLng currentLocation; + // Marker representing user's heading + private Marker orientationMarker; + // GNSS position marker + private Marker gnssMarker; + // Polyline representing user's movement path + private Polyline polyline; + // Tracks whether the polyline color is red + private boolean isRed = true; + // Tracks if GNSS tracking is enabled + private boolean isGnssOn = false; + + // Polyline for GNSS path + private Polyline gnssPolyline; + // Stores the last GNSS location + private LatLng lastGnssLocation = null; + + private LatLng pendingCameraPosition = null; + private boolean hasPendingCameraMove = false; + + // Manages indoor mapping (Legacy, keeping for compatibility) + private IndoorMapManager indoorMapManager; // UI private Spinner switchMapSpinner; - private SwitchMaterial gnssSwitch; private SwitchMaterial autoFloorSwitch; - private com.google.android.material.floatingactionbutton.FloatingActionButton floorUpButton, floorDownButton; + private FloatingActionButton floorUpButton, floorDownButton; private Button switchColorButton; private Polygon buildingPolygon; + // Venue and Floor Data + private boolean hasVenue = false; + private String currentVenueId = ""; + private String currentFloor = ""; // The floor selected by user or current floor + private String currentVenueName = ""; + + // Store downloaded building data + private NetworkUtils.BuildingData currentBuildingData = null; + private List sortedFloors = new ArrayList<>(); + + // Map objects for indoor features (to clear them when switching floors) + private List indoorWalls = new ArrayList<>(); + private List indoorAreas = new ArrayList<>(); + private List indoorPois = new ArrayList<>(); + + // Initial position from arguments (used for indoor map loading when currentLocation is null) + private double initialLat = 0; + private double initialLon = 0; public TrajectoryMapFragment() { // Required empty public constructor @@ -100,16 +117,28 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + // 1. Retrieve Venue Information from Arguments + Bundle args = getArguments(); + if (args != null) { + hasVenue = args.getBoolean("has_venue", false); + currentVenueId = args.getString("venue_id", ""); + currentVenueName = args.getString("venue_name", ""); + currentFloor = args.getString("venue_floor", "0"); + // Read initial position for indoor map loading + initialLat = args.getDouble("initial_lat", 0); + initialLon = args.getDouble("initial_lon", 0); + } + // Grab references to UI controls switchMapSpinner = view.findViewById(R.id.mapSwitchSpinner); - gnssSwitch = view.findViewById(R.id.gnssSwitch); + gnssSwitch = view.findViewById(R.id.gnssSwitch); autoFloorSwitch = view.findViewById(R.id.autoFloor); - floorUpButton = view.findViewById(R.id.floorUpButton); + floorUpButton = view.findViewById(R.id.floorUpButton); floorDownButton = view.findViewById(R.id.floorDownButton); switchColorButton = view.findViewById(R.id.lineColorButton); - // Setup floor up/down UI hidden initially until we know there's an indoor map - setFloorControlsVisibility(View.GONE); + // Setup floor up/down UI - Only show if we have a venue + setFloorControlsVisibility(hasVenue ? View.VISIBLE : View.GONE); // Initialize the map asynchronously SupportMapFragment mapFragment = (SupportMapFragment) @@ -118,22 +147,22 @@ public void onViewCreated(@NonNull View view, mapFragment.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(@NonNull GoogleMap googleMap) { - // Assign the provided googleMap to your field variable gMap = googleMap; - // Initialize map settings with the now non-null gMap initMapSettings(gMap); - // If we had a pending camera move, apply it now if (hasPendingCameraMove && pendingCameraPosition != null) { gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pendingCameraPosition, 19f)); hasPendingCameraMove = false; pendingCameraPosition = null; } - drawBuildingPolygon(); - - Log.d("TrajectoryMapFragment", "onMapReady: Map is ready!"); - + // 2. Load Indoor Map if venue is selected + if (hasVenue) { + loadIndoorMapData(); + } else { + // Fallback to old behavior (just outlines) + drawBuildingPolygon(); + } } }); @@ -166,41 +195,170 @@ public void onMapReady(@NonNull GoogleMap googleMap) { } }); - // Floor up/down logic + // 3. Updated Floor Control Logic autoFloorSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> { - - //TODO - fix the sensor fusion method to get the elevation (cannot get it from the current method) -// float elevationVal = sensorFusion.getElevation(); -// indoorMapManager.setCurrentFloor((int)(elevationVal/indoorMapManager.getFloorHeight()) -// ,true); + // Logic for auto-floor using pressure sensor (omitted for now) }); floorUpButton.setOnClickListener(v -> { - // If user manually changes floor, turn off auto floor autoFloorSwitch.setChecked(false); - if (indoorMapManager != null) { - indoorMapManager.increaseFloor(); - } + changeFloor(1); // Go up }); floorDownButton.setOnClickListener(v -> { autoFloorSwitch.setChecked(false); - if (indoorMapManager != null) { - indoorMapManager.decreaseFloor(); + changeFloor(-1); // Go down + }); + } + + /** + * Load Indoor Map Data using NetworkUtils + */ + private void loadIndoorMapData() { + // Use current location > initial position from arguments > Edinburgh campus default + double lat, lon; + if (currentLocation != null) { + lat = currentLocation.latitude; + lon = currentLocation.longitude; + } else if (initialLat != 0 || initialLon != 0) { + lat = initialLat; + lon = initialLon; + } else { + lat = 55.9234; + lon = -3.1761; + } + NetworkUtils.fetchFloorPlan(lat, lon, new NetworkUtils.Callback() { + @Override + public void onSuccess(NetworkUtils.BuildingData data) { + if (getActivity() == null) return; + getActivity().runOnUiThread(() -> { + currentBuildingData = data; + // Sort floors so we know order + sortedFloors = sortFloorNames(new ArrayList<>(data.floors.keySet())); + + if (data.floors.isEmpty()) { + Toast.makeText(getContext(), "No map data found for this location", Toast.LENGTH_SHORT).show(); + drawBuildingPolygon(); // Fallback + } else { + // If currentFloor is set (from previous screen), try to use it + // otherwise use the first available floor + if (!data.floors.containsKey(currentFloor) && !sortedFloors.isEmpty()) { + currentFloor = sortedFloors.get(0); + } + drawIndoorMap(currentFloor); + Toast.makeText(getContext(), "Map loaded: " + currentFloor, Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onError(String error) { + Log.e(TAG, "Error loading map: " + error); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> drawBuildingPolygon()); + } } }); } /** - * Initialize the map settings with the provided GoogleMap instance. - *

- * The method sets basic map settings, initializes the indoor map manager, - * and creates an empty polyline for user movement tracking. - * The method also initializes the GNSS polyline for tracking GNSS path. - * The method sets the map type to Hybrid and initializes the map with these settings. - * - * @param map + * Draw the specific floor from downloaded data + */ + private void drawIndoorMap(String floorName) { + if (gMap == null || currentBuildingData == null) return; + + NetworkUtils.FloorData floorData = currentBuildingData.floors.get(floorName); + if (floorData == null) return; + + // Clear previous indoor layers + clearIndoorLayers(); + + // 1. Draw Walls (Black Lines) + for (List wall : floorData.walls) { + Polyline line = gMap.addPolyline(new PolylineOptions() + .addAll(wall) + .color(Color.BLACK) + .width(6f) + .zIndex(10)); // Above ground + indoorWalls.add(line); + } + + // 2. Draw Areas (Gray Polygons) + for (List area : floorData.areas) { + Polygon poly = gMap.addPolygon(new PolygonOptions() + .addAll(area) + .strokeColor(Color.DKGRAY) + .strokeWidth(2f) + .fillColor(Color.argb(50, 200, 200, 200)) + .zIndex(5)); // Below walls + indoorAreas.add(poly); + } + + // 3. Draw POIs + for (NetworkUtils.Poi poi : floorData.pois) { + if (poi.position != null) { + Marker marker = gMap.addMarker(new MarkerOptions() + .position(poi.position) + .title(poi.label.isEmpty() ? poi.type : poi.label) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) + .zIndex(15)); + indoorPois.add(marker); + } + } + } + + /** + * Clear indoor map layers + */ + private void clearIndoorLayers() { + for (Polyline line : indoorWalls) line.remove(); + indoorWalls.clear(); + for (Polygon poly : indoorAreas) poly.remove(); + indoorAreas.clear(); + for (Marker marker : indoorPois) marker.remove(); + indoorPois.clear(); + } + + /** + * Change floor logic + */ + private void changeFloor(int offset) { + if (sortedFloors.isEmpty()) return; + + int index = sortedFloors.indexOf(currentFloor); + if (index == -1) index = 0; + + int newIndex = index + offset; + if (newIndex >= 0 && newIndex < sortedFloors.size()) { + currentFloor = sortedFloors.get(newIndex); + drawIndoorMap(currentFloor); + Toast.makeText(getContext(), "Floor: " + currentFloor, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), "No more floors", Toast.LENGTH_SHORT).show(); + } + } + + /** + * Helper: Sort floor names (Copied from MapsFragment logic) */ + private List sortFloorNames(List floorNames) { + Collections.sort(floorNames, (f1, f2) -> { + try { + int n1 = extractFloorNumber(f1); + int n2 = extractFloorNumber(f2); + return Integer.compare(n1, n2); // Ascending order + } catch (Exception e) { + return f1.compareTo(f2); + } + }); + return floorNames; + } + + private int extractFloorNumber(String floorName) { + String clean = floorName.toLowerCase().replaceAll("[^0-9-]", ""); + if (clean.isEmpty()) return 0; + return Integer.parseInt(clean); + } private void initMapSettings(GoogleMap map) { // Basic map settings @@ -210,16 +368,14 @@ private void initMapSettings(GoogleMap map) { map.getUiSettings().setScrollGesturesEnabled(true); map.setMapType(GoogleMap.MAP_TYPE_HYBRID); - // Initialize indoor manager indoorMapManager = new IndoorMapManager(map); - // Initialize an empty polyline polyline = map.addPolyline(new PolylineOptions() .color(Color.RED) .width(5f) + .zIndex(20) // above indoor walls (zIndex 10) and areas (zIndex 5) .add() // start empty ); - // GNSS path in blue gnssPolyline = map.addPolyline(new PolylineOptions() .color(Color.BLUE) @@ -228,22 +384,6 @@ private void initMapSettings(GoogleMap map) { ); } - - /** - * Initialize the map type spinner with the available map types. - *

- * The spinner allows the user to switch between different map types - * (e.g. Hybrid, Normal, Satellite) to customize their map view. - * The spinner is populated with the available map types and listens - * for user selection to update the map accordingly. - * The map type is updated directly on the GoogleMap instance. - *

- * Note: The spinner is initialized with the default map type (Hybrid). - * The map type is updated on user selection. - *

- *

- * @see com.google.android.gms.maps.GoogleMap The GoogleMap instance to update map type. - */ private void initMapTypeSpinner() { if (switchMapSpinner == null) return; String[] maps = new String[]{ @@ -263,7 +403,7 @@ private void initMapTypeSpinner() { public void onItemSelected(AdapterView parent, View view, int position, long id) { if (gMap == null) return; - switch (position){ + switch (position) { case 0: gMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); break; @@ -275,26 +415,18 @@ public void onItemSelected(AdapterView parent, View view, break; } } + @Override - public void onNothingSelected(AdapterView parent) {} + public void onNothingSelected(AdapterView parent) { + } }); } - /** - * Update the user's current location on the map, create or move orientation marker, - * and append to polyline if the user actually moved. - * - * @param newLocation The new location to plot. - * @param orientation The user’s heading (e.g. from sensor fusion). - */ public void updateUserLocation(@NonNull LatLng newLocation, float orientation) { if (gMap == null) return; - - // Keep track of current location LatLng oldLocation = this.currentLocation; this.currentLocation = newLocation; - // If no marker, create it if (orientationMarker == null) { orientationMarker = gMap.addMarker(new MarkerOptions() .position(newLocation) @@ -305,69 +437,60 @@ public void updateUserLocation(@NonNull LatLng newLocation, float orientation) { R.drawable.ic_baseline_navigation_24))) ); gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(newLocation, 19f)); + // Add the first point to the polyline so the line starts from the beginning + if (polyline != null) { + List points = new ArrayList<>(polyline.getPoints()); + points.add(newLocation); + polyline.setPoints(points); + } } else { - // Update marker position + orientation orientationMarker.setPosition(newLocation); orientationMarker.setRotation(orientation); - // Move camera a bit gMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation)); - } - // Extend polyline if movement occurred - if (oldLocation != null && !oldLocation.equals(newLocation) && polyline != null) { - List points = new ArrayList<>(polyline.getPoints()); - points.add(newLocation); - polyline.setPoints(points); + if (oldLocation != null && !oldLocation.equals(newLocation) && polyline != null) { + List points = new ArrayList<>(polyline.getPoints()); + points.add(newLocation); + polyline.setPoints(points); + } } - // Update indoor map overlay - if (indoorMapManager != null) { + // Only use IndoorMapManager when API data is not available + if (!hasVenue && indoorMapManager != null) { indoorMapManager.setCurrentLocation(newLocation); - setFloorControlsVisibility(indoorMapManager.getIsIndoorMapSet() ? View.VISIBLE : View.GONE); } } - - /** - * Set the initial camera position for the map. - *

- * The method sets the initial camera position for the map when it is first loaded. - * If the map is already ready, the camera is moved immediately. - * If the map is not ready, the camera position is stored until the map is ready. - * The method also tracks if there is a pending camera move. - *

- * @param startLocation The initial camera position to set. + * Sets initial camera position. + * Renamed to be more descriptive, but aliased by setStartLocation for compatibility. */ public void setInitialCameraPosition(@NonNull LatLng startLocation) { - // If the map is already ready, move camera immediately if (gMap != null) { gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(startLocation, 19f)); } else { - // Otherwise, store it until onMapReady pendingCameraPosition = startLocation; hasPendingCameraMove = true; } } - /** - * Get the current user location on the map. - * @return The current user location as a LatLng object. + * FIX: Added for backward compatibility with ReplayFragment. + * ReplayFragment calls this method, so we map it to setInitialCameraPosition. */ + public void setStartLocation(LatLng startLocation) { + setInitialCameraPosition(startLocation); + } + public LatLng getCurrentLocation() { return currentLocation; } - /** - * Called when we want to set or update the GNSS marker position - */ public void updateGNSS(@NonNull LatLng gnssLocation) { if (gMap == null) return; if (!isGnssOn) return; if (gnssMarker == null) { - // Create the GNSS marker for the first time gnssMarker = gMap.addMarker(new MarkerOptions() .position(gnssLocation) .title("GNSS Position") @@ -375,10 +498,7 @@ public void updateGNSS(@NonNull LatLng gnssLocation) { .defaultMarker(BitmapDescriptorFactory.HUE_AZURE))); lastGnssLocation = gnssLocation; } else { - // Move existing GNSS marker gnssMarker.setPosition(gnssLocation); - - // Add a segment to the blue GNSS line, if this is a new location if (lastGnssLocation != null && !lastGnssLocation.equals(gnssLocation)) { List gnssPoints = new ArrayList<>(gnssPolyline.getPoints()); gnssPoints.add(gnssLocation); @@ -388,10 +508,6 @@ public void updateGNSS(@NonNull LatLng gnssLocation) { } } - - /** - * Remove GNSS marker if user toggles it off - */ public void clearGNSS() { if (gnssMarker != null) { gnssMarker.remove(); @@ -399,9 +515,6 @@ public void clearGNSS() { } } - /** - * Whether user is currently showing GNSS or not - */ public boolean isGnssEnabled() { return isGnssOn; } @@ -430,112 +543,52 @@ public void clearMapAndReset() { gnssMarker = null; } lastGnssLocation = null; - currentLocation = null; + currentLocation = null; + + // Clear indoor data too + clearIndoorLayers(); - // Re-create empty polylines with your chosen colors if (gMap != null) { polyline = gMap.addPolyline(new PolylineOptions() .color(Color.RED) .width(5f) + .zIndex(20) // above indoor walls (zIndex 10) and areas (zIndex 5) .add()); gnssPolyline = gMap.addPolyline(new PolylineOptions() .color(Color.BLUE) .width(5f) .add()); + + // Redraw indoor map after clearing (if we have building data) + if (currentBuildingData != null && currentFloor != null) { + drawIndoorMap(currentFloor); + } } } - /** - * Draw the building polygon on the map - *

- * The method draws a polygon representing the building on the map. - * The polygon is drawn with specific vertices and colors to represent - * different buildings or areas on the map. - * The method removes the old polygon if it exists and adds the new polygon - * to the map with the specified options. - * The method logs the number of vertices in the polygon for debugging. - *

- * - * Note: The method uses hard-coded vertices for the building polygon. - * - *

- * - * See: {@link com.google.android.gms.maps.model.PolygonOptions} The options for the new polygon. - */ private void drawBuildingPolygon() { if (gMap == null) { - Log.e("TrajectoryMapFragment", "GoogleMap is not ready"); + Log.e(TAG, "GoogleMap is not ready"); return; } - + // Keep existing fallback logic // nuclear building polygon vertices LatLng nucleus1 = new LatLng(55.92279538827796, -3.174612147506538); LatLng nucleus2 = new LatLng(55.92278121423647, -3.174107900816096); LatLng nucleus3 = new LatLng(55.92288405733954, -3.173843694667146); LatLng nucleus4 = new LatLng(55.92331786793876, -3.173832892645086); LatLng nucleus5 = new LatLng(55.923337194112555, -3.1746284301397387); - - - // nkml building polygon vertices - LatLng nkml1 = new LatLng(55.9230343434213, -3.1751847990731954); - LatLng nkml2 = new LatLng(55.923032840563366, -3.174777103346131); - LatLng nkml4 = new LatLng(55.92280139974615, -3.175195527934348); - LatLng nkml3 = new LatLng(55.922793885410734, -3.1747958788136867); - - LatLng fjb1 = new LatLng(55.92269205199916, -3.1729563477188774);//left top - LatLng fjb2 = new LatLng(55.922822801570994, -3.172594249522305); - LatLng fjb3 = new LatLng(55.92223512226413, -3.171921917547244); - LatLng fjb4 = new LatLng(55.9221071265519, -3.1722813131202097); - - LatLng faraday1 = new LatLng(55.92242866264128, -3.1719553662011815); - LatLng faraday2 = new LatLng(55.9224966752294, -3.1717846714743474); - LatLng faraday3 = new LatLng(55.922271383074154, -3.1715191463437162); - LatLng faraday4 = new LatLng(55.92220124468304, -3.171705013935158); - - + // ... (Other hardcoded coordinates have been omitted; keep your original ones, or this coordinate might not be called if the API download is successful.) PolygonOptions buildingPolygonOptions = new PolygonOptions() .add(nucleus1, nucleus2, nucleus3, nucleus4, nucleus5) - .strokeColor(Color.RED) // Red border - .strokeWidth(10f) // Border width - //.fillColor(Color.argb(50, 255, 0, 0)) // Semi-transparent red fill - .zIndex(1); // Set a higher zIndex to ensure it appears above other overlays - - // Options for the new polygon - PolygonOptions buildingPolygonOptions2 = new PolygonOptions() - .add(nkml1, nkml2, nkml3, nkml4, nkml1) - .strokeColor(Color.BLUE) // Blue border - .strokeWidth(10f) // Border width - // .fillColor(Color.argb(50, 0, 0, 255)) // Semi-transparent blue fill - .zIndex(1); // Set a higher zIndex to ensure it appears above other overlays - - PolygonOptions buildingPolygonOptions3 = new PolygonOptions() - .add(fjb1, fjb2, fjb3, fjb4, fjb1) - .strokeColor(Color.GREEN) // Green border - .strokeWidth(10f) // Border width - //.fillColor(Color.argb(50, 0, 255, 0)) // Semi-transparent green fill - .zIndex(1); // Set a higher zIndex to ensure it appears above other overlays - - PolygonOptions buildingPolygonOptions4 = new PolygonOptions() - .add(faraday1, faraday2, faraday3, faraday4, faraday1) - .strokeColor(Color.YELLOW) // Yellow border - .strokeWidth(10f) // Border width - //.fillColor(Color.argb(50, 255, 255, 0)) // Semi-transparent yellow fill - .zIndex(1); // Set a higher zIndex to ensure it appears above other overlays - - - // Remove the old polygon if it exists + .strokeColor(Color.RED) + .strokeWidth(10f) + .zIndex(1); + if (buildingPolygon != null) { buildingPolygon.remove(); } - - // Add the polygon to the map buildingPolygon = gMap.addPolygon(buildingPolygonOptions); - gMap.addPolygon(buildingPolygonOptions2); - gMap.addPolygon(buildingPolygonOptions3); - gMap.addPolygon(buildingPolygonOptions4); - Log.d("TrajectoryMapFragment", "Building polygon added, vertex count: " + buildingPolygon.getPoints().size()); } - - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/VenueManager.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/VenueManager.java new file mode 100644 index 00000000..bc0c407f --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/VenueManager.java @@ -0,0 +1,125 @@ +package com.openpositioning.PositionMe.presentation.fragment; + +import android.content.Context; +import android.content.SharedPreferences; + +/** + * Singleton class to manage selected venue information across the app. + * This ensures that the selected venue persists across fragments and activities. + * + * Usage: + * - VenueManager.getInstance(context).setSelectedVenue(buildingName, buildingId, floor); + * - String venueName = VenueManager.getInstance(context).getSelectedVenueName(); + * + * @author Your Team + */ +public class VenueManager { + private static VenueManager instance; + private static final String PREFS_NAME = "VenuePreferences"; + private static final String KEY_VENUE_NAME = "selected_venue_name"; + private static final String KEY_VENUE_ID = "selected_venue_id"; + private static final String KEY_VENUE_FLOOR = "selected_venue_floor"; + + private SharedPreferences prefs; + + private VenueManager(Context context) { + prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + } + + /** + * Get singleton instance of VenueManager + * @param context Application context + * @return VenueManager instance + */ + public static synchronized VenueManager getInstance(Context context) { + if (instance == null) { + instance = new VenueManager(context); + } + return instance; + } + + /** + * Set the selected venue for data collection + * @param venueName Human-readable name of the venue (e.g., "Murchison House") + * @param venueId Unique identifier for the venue (e.g., building ID from API) + * @param floor Current floor name (e.g., "Ground Floor", "Floor 1") + */ + public void setSelectedVenue(String venueName, String venueId, String floor) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(KEY_VENUE_NAME, venueName); + editor.putString(KEY_VENUE_ID, venueId); + editor.putString(KEY_VENUE_FLOOR, floor); + editor.apply(); + } + + /** + * Get the selected venue name + * @return Venue name or "No Venue Selected" if none selected + */ + public String getSelectedVenueName() { + return prefs.getString(KEY_VENUE_NAME, "No Venue Selected"); + } + + /** + * Get the selected venue ID (for API submission) + * @return Venue ID or empty string if none selected + */ + public String getSelectedVenueId() { + return prefs.getString(KEY_VENUE_ID, ""); + } + + /** + * Get the current floor + * @return Floor name or empty string if none selected + */ + public String getSelectedFloor() { + return prefs.getString(KEY_VENUE_FLOOR, ""); + } + + /** + * Check if a venue has been selected + * @return true if venue is selected, false otherwise + */ + public boolean hasSelectedVenue() { + return !getSelectedVenueId().isEmpty(); + } + + /** + * Clear the selected venue (e.g., when user goes outside) + * @deprecated Use clearVenueSelection() instead + */ + @Deprecated + public void clearSelectedVenue() { + clearVenueSelection(); + } + + /** + * Clear the selected venue (e.g., when user goes outside) + */ + public void clearVenueSelection() { + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(KEY_VENUE_NAME); + editor.remove(KEY_VENUE_ID); + editor.remove(KEY_VENUE_FLOOR); + editor.apply(); + } + + /** + * Get full venue information as a formatted string + * @return Formatted string like "Murchison House - Ground Floor" or "No Venue Selected" + */ + public String getVenueDisplayText() { + if (!hasSelectedVenue()) { + return "No Venue Selected"; + } + + String name = getSelectedVenueName(); + String floor = getSelectedFloor(); + + if (floor.isEmpty()) { + return name; + } else { + return name + " - " + floor; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadListAdapter.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadListAdapter.java index 7de29c8a..48f65154 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadListAdapter.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadListAdapter.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.FileReader; import java.io.BufferedReader; +import java.io.IOException; import android.content.Intent; import android.os.Handler; @@ -214,13 +215,31 @@ public void onBindViewHolder(@NonNull TrajDownloadViewHolder holder, int positio String fileName = recordDetails.optString("file_name", null); if (fileName != null) { File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName); - filePath = file.getAbsolutePath(); + // Verify the cached file actually exists on disk + if (file.exists() && file.length() > 0) { + if (hasSufficientReplayPoints(file)) { + filePath = file.getAbsolutePath(); + } else { + Log.w("TrajDownloadListAdapter", "Cached file has insufficient replay points for ID " + + id + ": " + file.getAbsolutePath()); + matched = false; + } + } else { + // File is missing or empty - treat as not downloaded + Log.w("TrajDownloadListAdapter", "Cached file missing or empty for ID " + id + ": " + file.getAbsolutePath()); + matched = false; + } + } else { + matched = false; } - // Set the button state to "downloaded". - setButtonState(holder.downloadButton, 1); } catch (Exception e) { e.printStackTrace(); } + } + + // Set the button state based on the final matched status + if (matched) { + setButtonState(holder.downloadButton, 1); } else if (downloadingTrajIds.contains(id)) { // If the item is still being downloaded, set the button state to "downloading". setButtonState(holder.downloadButton, 2); @@ -292,4 +311,49 @@ private void setButtonState(MaterialButton button, int state) { button.setBackgroundTintList(ContextCompat.getColorStateList(context, R.color.md_theme_light_primary)); } } + + /** + * Lightweight replayability check for cached JSON trajectory files. + * Returns true when we can detect at least two replay points in either pdrData + * or correctedPositions arrays. + */ + private boolean hasSufficientReplayPoints(File file) { + int pdrPoints = 0; + int correctedPoints = 0; + boolean inPdrArray = false; + boolean inCorrectedArray = false; + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("\"pdrData\"") || line.contains("\"pdr_data\"")) { + inPdrArray = true; + inCorrectedArray = false; + } else if (line.contains("\"correctedPositions\"") || line.contains("\"corrected_positions\"")) { + inCorrectedArray = true; + inPdrArray = false; + } + + if (inPdrArray && line.contains("\"relativeTimestamp\"")) { + pdrPoints++; + if (pdrPoints >= 2) return true; + } + + if (inCorrectedArray && line.contains("\"latitude\"")) { + correctedPoints++; + if (correctedPoints >= 2) return true; + } + + if (line.contains("]")) { + inPdrArray = false; + inCorrectedArray = false; + } + } + } catch (IOException e) { + Log.w("TrajDownloadListAdapter", "Failed to validate cached trajectory file: " + e.getMessage()); + return false; + } + + return false; + } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java index 579e344c..f7b32b0d 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java @@ -18,6 +18,7 @@ * * @author Virginia Cangelosi * @author Mate Stodulka + * test */ public class GNSSDataProcessor { // Application context for handling permissions and locationManager instances @@ -98,7 +99,6 @@ private boolean checkLocationPermissions() { */ @SuppressLint("MissingPermission") public void startLocationUpdates() { - //if (sharedPreferences.getBoolean("location", true)) { boolean permissionGranted = checkLocationPermissions(); if (permissionGranted && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){ diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java index 6eca847c..b672aa2f 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java @@ -12,14 +12,18 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; +import android.view.Surface; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import com.google.android.gms.maps.model.LatLng; import com.openpositioning.PositionMe.presentation.activity.MainActivity; +import com.openpositioning.PositionMe.utils.FusionManager; import com.openpositioning.PositionMe.utils.PathView; import com.openpositioning.PositionMe.utils.PdrProcessing; +import com.openpositioning.PositionMe.utils.SimplePositionFusion; import com.openpositioning.PositionMe.data.remote.ServerCommunications; import com.openpositioning.PositionMe.Traj; import com.openpositioning.PositionMe.presentation.fragment.SettingsFragment; @@ -77,8 +81,111 @@ public class SensorFusion implements SensorEventListener, Observer { public static final float FILTER_COEFFICIENT = 0.96f; //Tuning value for low pass filter private static final float ALPHA = 0.8f; - // String for creating WiFi fingerprint JSO N object + // String for creating WiFi fingerprint JSON object private static final String WIFI_FINGERPRINT= "wf"; + + // HEADING LAG FIX - Game Rotation Vector + Gyro-integrated heading + /** + * High-frequency complementary-filter heading (radians, NED convention). + * Primary source: TYPE_GAME_ROTATION_VECTOR (accel+gyro only, no magnetometer). + * Gyroscope integration propagates heading between GAME_ROTATION_VECTOR updates. + * Complementary filter: heading = alpha*(heading + gyro*dt) + (1-alpha)*gameRotVecHeading + * alpha drops to HEADING_GYRO_ALPHA_TURN on sharp turns for instant snap. + */ + private float headingRad = 0f; + + /** Complementary-filter weight for gyro during straight walking (high = trust gyro). */ + private static final float HEADING_GYRO_ALPHA = 0.85f; + + /** + * Complementary-filter weight during a detected sharp turn. + * Lower value means game-rotation-vector dominates for instant heading snap. + */ + private static final float HEADING_GYRO_ALPHA_TURN = 0.20f; + + /** + * Angular-rate threshold (rad/s) above which we consider the user to be + * turning sharply and switch to the fast-response alpha. + */ + private static final float TURN_RATE_THRESHOLD_RAD_S = (float) Math.toRadians(30.0); + + /** Timestamp (ns from SensorEvent.timestamp) of the last gyroscope event. */ + private long lastGyroTimestampNs = 0; + + /** Most recent gravity-projected yaw rate (rad/s), updated in the gyroscope handler. */ + private float lastYawRate = 0f; + + /** Heading derived purely from the latest GAME_ROTATION_VECTOR quaternion (radians). */ + private float gameRotVecHeading = 0f; + + /** Whether we have received at least one GAME_ROTATION_VECTOR event. */ + private boolean gameRotVecReady = false; + + /** Adaptation gain for heading offset updates from TYPE_ROTATION_VECTOR. + * 0.03 per 150ms interval ≈ 0.2×delta/s effective rate, matching the original + * per-event implementation (0.002 × delta at 100Hz). Faster than the previous + * 0.01/300ms (0.033/s) so accumulated heading bias is corrected before it + * causes visible lateral drift at corridor entrances. */ + private static final float HEADING_OFFSET_ADAPT_GAIN = 0.03f; + + /** Reject large instantaneous magnetic jumps when adapting heading offset. */ + private static final float HEADING_OFFSET_MAX_UPDATE_RAD = (float) Math.toRadians(10.0); + + /** Only adapt heading offset when angular rate is small (quasi-static phone orientation). */ + private static final float HEADING_OFFSET_ADAPT_MAX_YAW_RATE_RAD_S = (float) Math.toRadians(15.0); + + /** Minimum interval between heading-offset updates to avoid rapid oscillation. + * 150ms (was 300ms) to restore heading-offset correction bandwidth closer to + * the original per-event implementation while keeping the protective gates. */ + private static final long HEADING_OFFSET_ADAPT_MIN_INTERVAL_MS = 150L; + + /** Require a short settle window after turning before adapting heading offset. */ + private static final long HEADING_OFFSET_ADAPT_TURN_SETTLE_MS = 1500L; + + /** Initial offset calibration: require multiple consistent samples, not a single frame. */ + private static final int HEADING_OFFSET_INIT_REQUIRED_SAMPLES = 5; + private static final float HEADING_OFFSET_INIT_MAX_SPREAD_RAD = (float) Math.toRadians(12.0); + + private long lastHeadingOffsetAdaptMs = 0L; + private long lastTurnDetectedMs = 0L; + + // Circular mean accumulator for robust initial heading-offset calibration. + private float headingOffsetInitSinSum = 0f; + private float headingOffsetInitCosSum = 0f; + private int headingOffsetInitSampleCount = 0; + + /** Latest GAME_ROTATION_VECTOR rotation matrix (device -> world). */ + private final float[] latestGameRotMatrix = new float[9]; + private boolean gameRotMatrixReady = false; + + /** + * Calibration offset: headingRad = gameRotVecHeading + headingOffset. + * + * GAME_ROTATION_VECTOR uses an arbitrary yaw reference frame (gravity-only, + * no magnetometer). TYPE_ROTATION_VECTOR references magnetic North. + * This offset aligns the two frames so PDR steps use a North-referenced heading + * while benefiting from the game-rotation-vector's immunity to local magnetic + * disturbances. + */ + private float headingOffset = 0f; + + /** True once the initial offset has been computed from the first valid TYPE_ROTATION_VECTOR reading. */ + private boolean headingOffsetCalibrated = false; + + /** True once at least one TYPE_ROTATION_VECTOR event has been received (orientation[0] = 0 + * is a valid azimuth meaning "facing north", so we cannot use != 0f as a ready-check). */ + private boolean rotVecReady = false; + + /** Dedicated sensor handle for TYPE_GAME_ROTATION_VECTOR. */ + private MovementSensor gameRotationSensor; + + // Heading diagnostics (enable while tuning turn-response / trend consistency). + private static final boolean HEADING_DEBUG_LOG_ENABLED = true; + private static final long HEADING_DEBUG_LOG_INTERVAL_MS = 250; + private long lastHeadingDebugLogMs = 0L; + + // NOTE: PCA heading estimation was removed – samples were collected but + // estimatePcaHeading() was never called in the active code path. //endregion //region Instance variables @@ -147,12 +254,122 @@ public class SensorFusion implements SensorEventListener, Observer { // Wifi values private List wifiList; + // Fields for updated proto support + // Trajectory identification + private String trajectoryId; + private float trajectoryVersion = 2.0f; + // Floor selected in VenueManager at recording start, saved into initialPosition.floor + private String recordingVenueFloor = ""; + + // Initial position and orientation + private boolean initialPositionSet = false; + private float[] initialLocation; + private float initialOrientation = 0f; + + // Corrected positions list + private List correctedPositions; + + // Fused trajectory samples captured from the live map path for faithful replay. + private List replayTrackPoints; + + private static class ReplayTrackPoint { + final long relativeTimestamp; + final double latitude; + final double longitude; + + ReplayTrackPoint(long relativeTimestamp, double latitude, double longitude) { + this.relativeTimestamp = relativeTimestamp; + this.latitude = latitude; + this.longitude = longitude; + } + } + + // WiFi fingerprint data + private List> wifiFingerprints; + + // WiFi AP data (Access Point information with RTT flag) + private List> wifiAPData; + + // BLE data collection + private List> bleData; + private List> bleFingerprints; + + // WiFi RTT data + private List> wifiRttData; + + // Proximity sensor data + private float currentProximity = 0f; + + // PDR data + private List pdrData; + + // Test Points Data - timestamped markers during recording + private List> testPoints; + private int testPointCounter = 0; + + // FUSED POSITION - Combines PDR with WiFi for smoother tracking + private float fusedLatitude = 0f; + private float fusedLongitude = 0f; + private float lastPdrX = 0f; + private float lastPdrY = 0f; + private long lastPositionUpdateTime = 0; + // PDR holdoff: ignore step events for this many ms after fusion is (re-)initialized. + // 400 ms is enough to skip accidental button-tap steps while still capturing the + // user's first real walking step (typical step period ≈ 500 ms). The previous 1500 ms + // discarded the first 2-3 real steps, creating a permanent position offset. + private long pdrIgnoreUntilMs = 0L; + private static final long PDR_INIT_HOLDOFF_MS = 400L; + + // Dual-phase floor detection: WiFi anchors absolute floor, barometer tracks relative delta. + // Integer.MIN_VALUE = anchor not yet set (pre-first-WiFi-fix state). + private int wifiFloorAnchor = Integer.MIN_VALUE; + private float wifiAnchorElevation = 0f; + // Last floor that passed the zone gate — the only value ever returned to the UI. + private int lastConfirmedFloor = 0; + + // Floor transition zone constraints: floor switches only when near a known lift or staircase. + // Populated from the API floor-plan POI list in RecordingFragment.drawFloor(). + private List liftZones = new ArrayList<>(); + private List stairZones = new ArrayList<>(); + private static final double LIFT_ZONE_RADIUS_M = 15.0; + private static final double STAIR_ZONE_RADIUS_M = 12.0; + + /** Describes how the user is changing floors (display/logging only, never used by PDR). */ + public enum VerticalTransportMode { NONE, STAIRS, ELEVATOR } + private boolean hasFusedPosition = false; + private static final float WIFI_PDR_FUSION_WEIGHT = 0.3f; // Weight for WiFi position in fusion + private static final long POSITION_INTERPOLATION_INTERVAL = 50; // ms + + // Smooth position interpolation + private float targetPdrX = 0f; + private float targetPdrY = 0f; + private float smoothPdrX = 0f; + private float smoothPdrY = 0f; + private static final float SMOOTHING_FACTOR = 0.15f; // Exponential smoothing factor + + // Inter-step interpolation: extrapolate position between step events + // so the marker moves continuously instead of jumping every ~500ms. + private long lastStepSystemTimeMs = 0; // System.currentTimeMillis() of last step + private long estimatedStepPeriodMs = 500; // Estimated time between steps (ms) + private boolean isWalking = false; // Whether user appears to be walking + private static final long WALKING_TIMEOUT_MS = 1200; // Stop interpolating after this idle time + private LatLng lastFusionStepPosition = null; // Fusion position at last step event // Over time accelerometer magnitude values since last step private List accelMagnitude; // PDR calculation class private PdrProcessing pdrProcessing; + + // GNSS-PDR Fusion for continuous position correction + private SimplePositionFusion positionFusion; + + // Particle Filter + Map Matching fusion pipeline + private FusionManager fusionManager; + // Timestamp of last PDR step (for speed estimation) + private long lastPdrStepTime = 0; + // Last PDR step length estimate (metres) + private float lastStepLengthM = 0.65f; // Trajectory displaying class private PathView pathView; @@ -228,7 +445,11 @@ public void setContext(Context context) { this.proximitySensor = new MovementSensor(context, Sensor.TYPE_PROXIMITY); this.magnetometerSensor = new MovementSensor(context, Sensor.TYPE_MAGNETIC_FIELD); this.stepDetectionSensor = new MovementSensor(context, Sensor.TYPE_STEP_DETECTOR); + // Keep legacy ROTATION_VECTOR for backward-compat data recording; heading is now + // driven by GAME_ROTATION_VECTOR which is immune to indoor magnetic disturbances. this.rotationSensor = new MovementSensor(context, Sensor.TYPE_ROTATION_VECTOR); + // HEADING LAG FIX: register GAME_ROTATION_VECTOR at the fastest rate available. + this.gameRotationSensor = new MovementSensor(context, Sensor.TYPE_GAME_ROTATION_VECTOR); this.gravitySensor = new MovementSensor(context, Sensor.TYPE_GRAVITY); this.linearAccelerationSensor = new MovementSensor(context, Sensor.TYPE_LINEAR_ACCELERATION); // Listener based devices @@ -246,9 +467,23 @@ public void setContext(Context context) { // Other initialisations... this.accelMagnitude = new ArrayList<>(); this.pdrProcessing = new PdrProcessing(context); + this.positionFusion = new SimplePositionFusion(); // Simple position fusion + this.fusionManager = new FusionManager(); this.settings = PreferenceManager.getDefaultSharedPreferences(context); this.pathView = new PathView(context, null); this.wiFiPositioning = new WiFiPositioning(context); + + // Initialize proto2.0 fields + this.initialLocation = new float[2]; + this.correctedPositions = new ArrayList<>(); + this.replayTrackPoints = new ArrayList<>(); + this.wifiFingerprints = new ArrayList<>(); + this.wifiAPData = new ArrayList<>(); + this.bleData = new ArrayList<>(); + this.bleFingerprints = new ArrayList<>(); + this.wifiRttData = new ArrayList<>(); + this.pdrData = new ArrayList<>(); + this.testPoints = new ArrayList<>(); // Initialize test points list if(settings.getBoolean("overwrite_constants", false)) { this.filter_coefficient = Float.parseFloat(settings.getString("accel_filter", "0.96")); @@ -284,11 +519,10 @@ public void onSensorChanged(SensorEvent sensorEvent) { if (lastTimestamp != null) { long timeGap = currentTime - lastTimestamp; -// // Log a warning if the time gap is larger than the threshold -// if (timeGap > LARGE_GAP_THRESHOLD_MS) { -// Log.e("SensorFusion", "Large time gap detected for sensor " + sensorType + -// " | Time gap: " + timeGap + " ms"); -// } + if (timeGap > LARGE_GAP_THRESHOLD_MS) { + Log.w("SensorFusion", "Large time gap detected for sensor " + sensorType + + " | gap=" + timeGap + " ms"); + } } // Update timestamp and frequency counter for this sensor @@ -307,9 +541,13 @@ public void onSensorChanged(SensorEvent sensorEvent) { case Sensor.TYPE_PRESSURE: pressure = (1 - ALPHA) * pressure + ALPHA * sensorEvent.values[0]; if (saveRecording) { - this.elevation = pdrProcessing.updateElevation( - SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) - ); + float altitudeM = SensorManager.getAltitude( + SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure); + this.elevation = pdrProcessing.updateElevation(altitudeM); + // Feed barometer into FusionManager for floor inference + if (fusionManager != null) { + fusionManager.updateBarometer(altitudeM); + } } break; @@ -318,6 +556,60 @@ public void onSensorChanged(SensorEvent sensorEvent) { angularVelocity[1] = sensorEvent.values[1]; angularVelocity[2] = sensorEvent.values[2]; + // Complementary filter: use gyro for short-term responsiveness and + // GAME_ROTATION_VECTOR (tilt-compensated) as the long-term reference. + boolean isTurningNow = false; + if (lastGyroTimestampNs > 0 && gameRotVecReady) { + double dtSec = (sensorEvent.timestamp - lastGyroTimestampNs) * 1e-9; + if (dtSec > 0 && dtSec < 0.1) { + // Project gyro onto world yaw axis (gravity direction in phone frame). + // angularVelocity[2] (screen-normal / z-axis) is NOT the yaw axis when + // the phone is held upright in portrait – it captures roll from arm swing + // and creates a systematic westward bias. The correct world yaw rate is + // dot(omega, gravity_unit): this works for any phone tilt angle. + float gravMag = (float) Math.sqrt( + gravity[0]*gravity[0] + gravity[1]*gravity[1] + gravity[2]*gravity[2]); + float yawRate = (gravMag > 1.0f) + ? (angularVelocity[0]*gravity[0] + + angularVelocity[1]*gravity[1] + + angularVelocity[2]*gravity[2]) / gravMag + : angularVelocity[2]; // fallback if gravity not yet valid + lastYawRate = yawRate; + boolean isTurning = Math.abs(yawRate) > TURN_RATE_THRESHOLD_RAD_S; + if (isTurning) { + lastTurnDetectedMs = System.currentTimeMillis(); + } + isTurningNow = isTurning; + float alpha = isTurning ? HEADING_GYRO_ALPHA_TURN : HEADING_GYRO_ALPHA; + + float refHeading; + if (headingOffsetCalibrated) { + refHeading = wrapAngleFloat(gameRotVecHeading + headingOffset); + } else if (rotVecReady && orientation != null) { + refHeading = orientation[0]; + } else { + refHeading = gameRotVecHeading; + } + + float gyroHeading = wrapAngleFloat(headingRad + (float) (yawRate * dtSec)); + // Use circular mean instead of linear interpolation to avoid the ±π + // wrapping boundary issue: alpha*(π-ε) + (1-alpha)*(-π+ε) ≈ -0.6π (wrong). + float sinMean = alpha * (float) Math.sin(gyroHeading) + (1.0f - alpha) * (float) Math.sin(refHeading); + float cosMean = alpha * (float) Math.cos(gyroHeading) + (1.0f - alpha) * (float) Math.cos(refHeading); + headingRad = (float) Math.atan2(sinMean, cosMean); + } + } + + lastGyroTimestampNs = sensorEvent.timestamp; + + // Update EKF with gyroscope for heading integration + if (positionFusion != null) { + positionFusion.updateWithGyroscope(angularVelocity); + } + + maybeLogHeadingDebug("gyro", angularVelocity[2], isTurningNow); + break; + case Sensor.TYPE_LINEAR_ACCELERATION: filteredAcc[0] = sensorEvent.values[0]; filteredAcc[1] = sensorEvent.values[1]; @@ -331,10 +623,10 @@ public void onSensorChanged(SensorEvent sensorEvent) { ); this.accelMagnitude.add(accelMagFiltered); -// // Debug logging -// Log.v("SensorFusion", -// "Added new linear accel magnitude: " + accelMagFiltered -// + "; accelMagnitude size = " + accelMagnitude.size()); + // Update EKF with accelerometer for ZUPT and real-time motion + if (positionFusion != null && this.orientation != null) { + positionFusion.updateWithAccelerometer(filteredAcc, orientation[0]); + } elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); break; @@ -344,9 +636,6 @@ public void onSensorChanged(SensorEvent sensorEvent) { gravity[1] = sensorEvent.values[1]; gravity[2] = sensorEvent.values[2]; - // Possibly log gravity values if needed - //Log.v("SensorFusion", "Gravity: " + Arrays.toString(gravity)); - elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); break; @@ -364,11 +653,107 @@ public void onSensorChanged(SensorEvent sensorEvent) { magneticField[2] = sensorEvent.values[2]; break; + case Sensor.TYPE_GAME_ROTATION_VECTOR: { + // Primary heading source: GAME_ROTATION_VECTOR (gyro+accel, no magnetometer). + // This avoids indoor magnetic disturbances that bias azimuth left/right. + // The north-reference is restored by headingOffset adapted in + // TYPE_ROTATION_VECTOR below. + float[] gameRotMatrix = new float[9]; + float[] gameOrientation = new float[3]; + SensorManager.getRotationMatrixFromVector(gameRotMatrix, sensorEvent.values); + float[] remappedGameRotMatrix = new float[9]; + remapToDisplay(gameRotMatrix, remappedGameRotMatrix); + System.arraycopy(remappedGameRotMatrix, 0, latestGameRotMatrix, 0, latestGameRotMatrix.length); + gameRotMatrixReady = true; + SensorManager.getOrientation(remappedGameRotMatrix, gameOrientation); + gameRotVecHeading = gameOrientation[0]; + gameRotVecReady = true; + + float calibrated; + if (headingOffsetCalibrated) { + calibrated = wrapAngleFloat(gameRotVecHeading + headingOffset); + } else if (rotVecReady && orientation != null) { + // Before offset calibration is ready, prefer north-referenced ROTATION_VECTOR. + // Using raw GAME_ROTATION_VECTOR yaw at this stage can introduce an arbitrary + // frame offset (commonly close to 90 degrees on some devices). + calibrated = orientation[0]; + } else { + calibrated = gameRotVecHeading; + } + headingRad = wrapAngleFloat(calibrated); + break; + } + case Sensor.TYPE_ROTATION_VECTOR: + // Keep TYPE_ROTATION_VECTOR for north-reference alignment and trajectory logging. + // Do not use it as the real-time heading source indoors because magnetic + // disturbances can rotate azimuth significantly. this.rotation = sensorEvent.values.clone(); float[] rotationVectorDCM = new float[9]; SensorManager.getRotationMatrixFromVector(rotationVectorDCM, this.rotation); - SensorManager.getOrientation(rotationVectorDCM, this.orientation); + float[] remappedRotVecMatrix = new float[9]; + remapToDisplay(rotationVectorDCM, remappedRotVecMatrix); + SensorManager.getOrientation(remappedRotVecMatrix, this.orientation); + rotVecReady = true; // mark as received; orientation[0]=0 (north) is valid + + if (gameRotVecReady) { + float desiredOffset = wrapAngleFloat(orientation[0] - gameRotVecHeading); + long nowMs = System.currentTimeMillis(); + // Use gravity-projected yaw rate (same axis as isTurning detection), + // not angularVelocity[2] (screen-normal/roll) which is the wrong axis + // when the phone is held upright in portrait mode. + boolean lowYawRate = Math.abs(lastYawRate) <= HEADING_OFFSET_ADAPT_MAX_YAW_RATE_RAD_S; + boolean turnSettled = (nowMs - lastTurnDetectedMs) >= HEADING_OFFSET_ADAPT_TURN_SETTLE_MS; + + if (!headingOffsetCalibrated) { + if (lowYawRate && turnSettled) { + float meanOffset = (headingOffsetInitSampleCount > 0) + ? (float) Math.atan2(headingOffsetInitSinSum, headingOffsetInitCosSum) + : desiredOffset; + float spread = Math.abs(wrapAngleFloat(desiredOffset - meanOffset)); + + // Re-start the init window if the new sample is not consistent. + if (headingOffsetInitSampleCount > 0 + && spread > HEADING_OFFSET_INIT_MAX_SPREAD_RAD) { + headingOffsetInitSinSum = 0f; + headingOffsetInitCosSum = 0f; + headingOffsetInitSampleCount = 0; + } + + headingOffsetInitSinSum += (float) Math.sin(desiredOffset); + headingOffsetInitCosSum += (float) Math.cos(desiredOffset); + headingOffsetInitSampleCount++; + + if (headingOffsetInitSampleCount >= HEADING_OFFSET_INIT_REQUIRED_SAMPLES) { + headingOffset = (float) Math.atan2(headingOffsetInitSinSum, headingOffsetInitCosSum); + headingOffsetCalibrated = true; + lastHeadingOffsetAdaptMs = nowMs; + headingOffsetInitSinSum = 0f; + headingOffsetInitCosSum = 0f; + headingOffsetInitSampleCount = 0; + } + } + } else { + float deltaOffset = wrapAngleFloat(desiredOffset - headingOffset); + boolean intervalOk = (nowMs - lastHeadingOffsetAdaptMs) >= HEADING_OFFSET_ADAPT_MIN_INTERVAL_MS; + if (lowYawRate && turnSettled && intervalOk + && Math.abs(deltaOffset) <= HEADING_OFFSET_MAX_UPDATE_RAD) { + headingOffset = wrapAngleFloat(headingOffset + HEADING_OFFSET_ADAPT_GAIN * deltaOffset); + lastHeadingOffsetAdaptMs = nowMs; + } + } + headingRad = wrapAngleFloat(gameRotVecHeading + headingOffset); + } else { + // Fallback before GAME_ROTATION_VECTOR is ready. + headingRad = orientation[0]; + } + + if (positionFusion != null) { + positionFusion.updateWithMagnetometer(headingRad); + } + + maybeLogHeadingDebug("rotvec", angularVelocity[2], + Math.abs(angularVelocity[2]) > TURN_RATE_THRESHOLD_RAD_S); break; case Sensor.TYPE_STEP_DETECTOR: @@ -388,25 +773,73 @@ public void onSensorChanged(SensorEvent sensorEvent) { Log.e("SensorFusion", "stepDetection triggered, but accelMagnitude is empty! " + "This can cause updatePdr(...) to fail or return bad results."); - } else { - Log.d("SensorFusion", - "stepDetection triggered, accelMagnitude size = " + accelMagnitude.size()); } + // Use the unified heading accessor so PDR/fusion paths always consume + // the same calibrated heading source. + float pdrHeading = getHeadingRad(); float[] newCords = this.pdrProcessing.updatePdr( stepTime, this.accelMagnitude, - this.orientation[0] + pdrHeading ); // Clear the accelMagnitude after using it this.accelMagnitude.clear(); + // Update position fusion with PDR data for GNSS correction + if (positionFusion != null) { + positionFusion.updateWithPDR(newCords[0], newCords[1]); + } + + // Update Particle Filter / EKF with PDR step. + // Skip during the post-init holdoff so that phone-handling steps and + // "walk-into-position" steps don't immediately displace the start marker. + if (fusionManager != null && fusionManager.isInitialized() + && System.currentTimeMillis() > pdrIgnoreUntilMs) { + long dtMs = (lastPdrStepTime > 0) ? (currentTime - lastPdrStepTime) : 500; + // Estimate step length from PDR delta (avoids resetting the counter) + float dX = newCords[0] - lastPdrX; + float dY = newCords[1] - lastPdrY; + lastStepLengthM = (float) Math.sqrt(dX * dX + dY * dY); + if (lastStepLengthM <= 0.05f) lastStepLengthM = 0.65f; // fallback + double fusionHeading = pdrHeading; + fusionManager.updateWithPDR( + newCords[0], newCords[1], + fusionHeading, + lastStepLengthM, + dtMs); + } + lastPdrX = newCords[0]; + lastPdrY = newCords[1]; + lastPdrStepTime = currentTime; + + // Update target position for smooth interpolation + targetPdrX = newCords[0]; + targetPdrY = newCords[1]; + lastPositionUpdateTime = currentTime; + hasFusedPosition = true; + + // Inter-step interpolation: record step timing & snapshot fusion position + long prevStepTime = lastStepSystemTimeMs; + lastStepSystemTimeMs = currentTime; + if (prevStepTime > 0) { + long period = currentTime - prevStepTime; + if (period > 100 && period < 2000) { + // Smooth the step period estimate (EMA) + estimatedStepPeriodMs = (long)(0.3 * period + 0.7 * estimatedStepPeriodMs); + } + } + isWalking = true; + // Snapshot the fusion position at this step for interpolation base + if (fusionManager != null && fusionManager.isInitialized()) { + lastFusionStepPosition = fusionManager.getEstimatedPosition(); + } if (saveRecording) { this.pathView.drawTrajectory(newCords); stepCounter++; - trajectory.addPdrData(Traj.Pdr_Sample.newBuilder() + trajectory.addPdrData(Traj.RelativePosition.newBuilder() .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) .setX(newCords[0]) .setY(newCords[1])); @@ -422,9 +855,104 @@ public void onSensorChanged(SensorEvent sensorEvent) { * Call this periodically for debugging purposes. */ public void logSensorFrequencies() { - for (int sensorType : eventCounts.keySet()) { - Log.d("SensorFusion", "Sensor " + sensorType + " | Event Count: " + eventCounts.get(sensorType)); + // debug utility – log output removed for production build + } + + /** + * HEADING LAG FIX helper: wrap an angle in radians to the range [-π, π]. + * + *

Used by the complementary-filter heading update to prevent the heading + * from accumulating unbounded values after many gyro integration steps.

+ * + * @param angle Angle in radians (any value). + * @return Equivalent angle in [-π, π]. + */ + private float wrapAngleFloat(float angle) { + while (angle > Math.PI) angle -= (float)(2.0 * Math.PI); + while (angle < -Math.PI) angle += (float)(2.0 * Math.PI); + return angle; + } + + private double wrapAngle(double angle) { + while (angle > Math.PI) angle -= 2.0 * Math.PI; + while (angle < -Math.PI) angle += 2.0 * Math.PI; + return angle; + } + + private double radToDeg(double rad) { + return Math.toDegrees(wrapAngle(rad)); + } + + private void maybeLogHeadingDebug(String src, float yawRateRadS, boolean isTurning) { + if (!HEADING_DEBUG_LOG_ENABLED) { + return; + } + long now = System.currentTimeMillis(); + if (now - lastHeadingDebugLogMs < HEADING_DEBUG_LOG_INTERVAL_MS) { + return; + } + lastHeadingDebugLogMs = now; + + double fusedDeg = radToDeg(headingRad); + double gameDeg = gameRotVecReady ? radToDeg(gameRotVecHeading) : Double.NaN; + double rotDeg = (rotVecReady && orientation != null) ? radToDeg(orientation[0]) : Double.NaN; + double offsetDeg = headingOffsetCalibrated ? radToDeg(headingOffset) : Double.NaN; + int displayRotation = getDisplayRotation(); + + // heading debug log removed for production build + } + + private int getDisplayRotation() { + if (appContext == null) { + return Surface.ROTATION_0; + } + WindowManager wm = (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE); + if (wm == null || wm.getDefaultDisplay() == null) { + return Surface.ROTATION_0; + } + return wm.getDefaultDisplay().getRotation(); + } + + private void remapToDisplay(float[] inR, float[] outR) { + int rotation = getDisplayRotation(); + int axisX; + int axisY; + switch (rotation) { + case Surface.ROTATION_90: + axisX = SensorManager.AXIS_Y; + axisY = SensorManager.AXIS_MINUS_X; + break; + case Surface.ROTATION_180: + axisX = SensorManager.AXIS_MINUS_X; + axisY = SensorManager.AXIS_MINUS_Y; + break; + case Surface.ROTATION_270: + axisX = SensorManager.AXIS_MINUS_Y; + axisY = SensorManager.AXIS_X; + break; + case Surface.ROTATION_0: + default: + axisX = SensorManager.AXIS_X; + axisY = SensorManager.AXIS_Y; + break; } + SensorManager.remapCoordinateSystem(inR, axisX, axisY, outR); + } + + /** + * Return the calibrated heading in radians (NED convention, 0 = magnetic North). + * + *

Derived from {@code TYPE_GAME_ROTATION_VECTOR} (accel+gyro, no magnetometer) + * plus a calibration offset that aligns the arbitrary game-rotation frame to + * magnetic North. The offset is slowly updated from {@code TYPE_ROTATION_VECTOR} + * so long-term yaw drift is corrected without reacting to brief indoor magnetic + * anomalies (e.g. escalators, iron doors).

+ * + * @return Heading in radians in [-π, π], or {@code orientation[0]} as fallback + * if the game-rotation-vector has not yet fired. + */ + public float getHeadingRad() { + return gameRotVecReady ? headingRad : (orientation != null ? orientation[0] : 0f); } /** @@ -445,15 +973,33 @@ public void onLocationChanged(@NonNull Location location) { float accuracy = (float) location.getAccuracy(); float speed = (float) location.getSpeed(); String provider = location.getProvider(); + + // Update position fusion with GNSS data for continuous correction + if (positionFusion != null) { + positionFusion.updateWithGNSS(latitude, longitude, accuracy); + } + + // Update Particle Filter with GNSS fix + if (fusionManager != null) { + if (!fusionManager.isInitialized()) { + // First GNSS fix – initialise the fusion pipeline. + double initHeading = getHeadingRad(); + fusionManager.initialize(latitude, longitude, accuracy, initHeading, 0); + } else { + fusionManager.updateWithGNSS(latitude, longitude, accuracy); + } + } + if(saveRecording) { - trajectory.addGnssData(Traj.GNSS_Sample.newBuilder() + trajectory.addGnssData(Traj.GNSSReading.newBuilder() + .setPosition(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(System.currentTimeMillis()-absoluteStartTime) + .setLatitude(latitude) + .setLongitude(longitude) + .setAltitude(altitude)) .setAccuracy(accuracy) - .setAltitude(altitude) - .setLatitude(latitude) - .setLongitude(longitude) .setSpeed(speed) - .setProvider(provider) - .setRelativeTimestamp(System.currentTimeMillis()-absoluteStartTime)); + .setProvider(provider)); } } } @@ -471,17 +1017,29 @@ public void update(Object[] wifiList) { this.wifiList = Stream.of(wifiList).map(o -> (Wifi) o).collect(Collectors.toList()); if(this.saveRecording) { - Traj.WiFi_Sample.Builder wifiData = Traj.WiFi_Sample.newBuilder() + Traj.Fingerprint.Builder wifiData = Traj.Fingerprint.newBuilder() .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime); for (Wifi data : this.wifiList) { - wifiData.addMacScans(Traj.Mac_Scan.newBuilder() + wifiData.addRfScans(Traj.RFScan.newBuilder() .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) .setMac(data.getBssid()).setRssi(data.getLevel())); } - // Adding WiFi data to Trajectory - this.trajectory.addWifiData(wifiData); + // Adding WiFi fingerprint data to Trajectory + this.trajectory.addWifiFingerprints(wifiData); + } + // Use callback-based WiFi request so that positionFusion receives + // FRESH data when the server responds, instead of stale data from the + // previous scan (which is always behind the user and pulls backward). + createWifiPositioningRequestWithFusion(); + + // Feed raw WiFi scan into local WKNN predictor (no DL/API dependency for fusion path). + if (fusionManager != null && fusionManager.isInitialized()) { + Map currentScan = new HashMap<>(); + for (Wifi data : this.wifiList) { + currentScan.put(String.format("%012X", data.getBssid()), data.getLevel()); + } + fusionManager.updateWithWifiScan(currentScan); } - createWifiPositioningRequest(); } /** @@ -506,6 +1064,48 @@ private void createWifiPositioningRequest(){ Log.e("jsonErrors","Error creating json object"+e.toString()); } } + + /** + * Callback-based WiFi positioning request. + * Unlike createWifiPositioningRequest(), this feeds the FRESH server response + * directly into positionFusion.updateWithWiFi() when it arrives, avoiding the + * stale-data problem that caused backward pulling and apparent direction reversal. + */ + private void createWifiPositioningRequestWithFusion(){ + try { + JSONObject wifiAccessPoints = new JSONObject(); + for (Wifi data : this.wifiList){ + wifiAccessPoints.put(String.valueOf(data.getBssid()), data.getLevel()); + } + JSONObject wifiFingerPrint = new JSONObject(); + wifiFingerPrint.put(WIFI_FINGERPRINT, wifiAccessPoints); + this.wiFiPositioning.request(wifiFingerPrint, new WiFiPositioning.VolleyCallback() { + @Override + public void onSuccess(LatLng wifiLocation, int floor) { + // Feed FRESH WiFi position into SimplePositionFusion (drift correction) + if (positionFusion != null && positionFusion.isInitialized()) { + positionFusion.updateWithWiFi(wifiLocation.latitude, wifiLocation.longitude); + } + // Feed into FusionManager for heading correction + EKF/PF update + if (fusionManager != null && fusionManager.isInitialized()) { + fusionManager.updateWithWifi(wifiLocation.latitude, wifiLocation.longitude); + } + // Dual-phase floor: WiFi provides absolute floor anchor. + // Barometer will measure relative displacement from this baseline. + wifiFloorAnchor = floor; + wifiAnchorElevation = elevation; + } + + @Override + public void onError(String message) { + // WiFi positioning failed – simply skip this correction. + Log.w("SensorFusion", "WiFi positioning error: " + message); + } + }); + } catch (JSONException e) { + Log.e("jsonErrors","Error creating json object" + e.toString()); + } + } // Callback Example Function /** * Function to create a request to obtain a wifi location for the obtained wifi fingerprint @@ -540,6 +1140,33 @@ public void onError(String message) { } + /** + * Requests WiFi-based positioning using the most recent WiFi scan, and delivers the result + * via a callback. Intended only for refining the initial start-location marker before + * recording begins. Does NOT touch positionFusion or fusionManager. + * + * @param callback called on the main thread when the server responds (or on error) + * @return true if a request was sent, false if no WiFi scan data is available yet + */ + public boolean requestWifiPositioningForInitialLocation(WiFiPositioning.VolleyCallback callback) { + if (wifiList == null || wifiList.isEmpty()) { + return false; + } + try { + JSONObject wifiAccessPoints = new JSONObject(); + for (Wifi data : wifiList) { + wifiAccessPoints.put(String.valueOf(data.getBssid()), data.getLevel()); + } + JSONObject wifiFingerPrint = new JSONObject(); + wifiFingerPrint.put(WIFI_FINGERPRINT, wifiAccessPoints); + wiFiPositioning.request(wifiFingerPrint, callback); + return true; + } catch (JSONException e) { + Log.e("SensorFusion", "Error creating WiFi JSON for initial location: " + e); + return false; + } + } + /** * Method to get user position obtained using {@link WiFiPositioning}. * @@ -649,10 +1276,57 @@ public float[] getGNSSLatitude(boolean start) { /** * Setter function for core location data. * - * @param startPosition contains the initial location set by the user + * Called when the user confirms their starting position in + * {@link com.openpositioning.PositionMe.presentation.fragment.StartLocationFragment}. + * This is the authoritative position: it ALWAYS overrides any earlier GNSS-based + * initialisation, because indoor GPS is typically 10-30 m off. + * + *

After this call: + *

    + *
  • Both {@code positionFusion} and {@code fusionManager} are (re-)initialised + * at the user-chosen coordinates.
  • + *
  • {@code fusionManager} enters a 60-second "user-anchor" window during which + * GNSS fixes more than 2.5 m away are rejected, preventing GPS from pulling + * the trajectory back to the (wrong) outdoor-biased position.
  • + *
+ * + * @param startPosition [latitude, longitude] chosen by the user on the map. */ - public void setStartGNSSLatitude(float[] startPosition){ + public void setStartGNSSLatitude(float[] startPosition) { startLocation = startPosition; + + if (startPosition == null || (startPosition[0] == 0f && startPosition[1] == 0f)) { + return; + } + + // (Re-)seed SimplePositionFusion so PDR steps are immediately converted to + // lat/lng from the correct starting point. + if (positionFusion != null) { + positionFusion.initialize(startPosition[0], startPosition[1], 5.0f); + } + + // ALWAYS reinitialise FusionManager (particle filter + EKF) from the user-chosen + // position, even if GNSS had already initialised it at the wrong GPS location. + // Without this the EKF/PF would continue from wherever GPS placed them. + double initHeading = gameRotVecReady ? this.headingRad + : ((orientation != null) ? orientation[0] : 0.0); + if (fusionManager != null) { + fusionManager.initialize(startPosition[0], startPosition[1], 5.0f, initHeading, 0); + // Enter anchor mode: tighten GNSS rejection for the first 60 s so that + // the poor-quality indoor GPS cannot drag the position back to its + // (biased) estimate. + fusionManager.setUserAnchor(); + } + + // Re-reset PDR so any steps taken between startRecording() and here are cleared, + // and sync SensorFusion.lastPdrX/Y with FusionManager's reset state (both → 0). + this.pdrProcessing.resetPDR(); + this.lastPdrX = 0f; + this.lastPdrY = 0f; + + // Arm the holdoff: PDR steps will be collected but won't move the fusion estimate + // for PDR_INIT_HOLDOFF_MS, giving the user time to stand still at their start point. + this.pdrIgnoreUntilMs = System.currentTimeMillis() + PDR_INIT_HOLDOFF_MS; } @@ -707,6 +1381,456 @@ public Map getSensorValueMap() { return sensorValueMap; } + /** + * Get smoothed PDR position using exponential smoothing. + * This provides smoother movement visualization between step updates. + * + * @return float array of size 2, with smoothed X and Y coordinates respectively. + */ + public float[] getSmoothedPDRPosition() { + // Return the raw PDR target position directly. + // The CascadedFusionManager (EKF + PF) already handles position smoothing; + // an extra exponential-smoothing layer here only adds lag, making the + // displayed position trail behind (or overshoot) the user's actual steps. + return new float[]{targetPdrX, targetPdrY}; + } + + /** + * Get raw PDR-only position in lat/lng (start point + PDR offsets). + * This intentionally excludes WiFi/GNSS/PF fusion so UI overlays can show + * whether step-based PDR itself is behaving correctly. + */ + public LatLng getRawPdrLatLng() { + float[] startPos = getGNSSLatitude(true); + if (startPos == null || startPos[0] == 0f) { + return null; + } + + float[] pdrPos = (pdrProcessing != null) ? pdrProcessing.getPDRMovement() : null; + if (pdrPos == null || pdrPos.length < 2) { + return null; + } + + double latOffset = pdrPos[1] / 111139.0; + double lngOffset = pdrPos[0] / 62422.0; + return new LatLng(startPos[0] + latOffset, startPos[1] + lngOffset); + } + + /** + * Get fused position combining PDR with GNSS correction. + * Uses Kalman-like filter for continuous GNSS-PDR fusion. + * Also incorporates WiFi positioning when available. + * + * @return LatLng of the fused position, or null if no position available. + */ + public LatLng getFusedPosition() { + if (!hasFusedPosition) { + return null; + } + + // Prefer the particle-filter output when available to keep one consistent fusion stream. + LatLng particleEstimate = getParticleFilterPosition(); + if (particleEstimate != null) { + return particleEstimate; + } + + // Try to use position fusion (GNSS-corrected) first - this is already smoothed + if (positionFusion != null && positionFusion.isInitialized()) { + // WiFi fusion is now handled inside SimplePositionFusion.updateWithWiFi() + // which is called from update(Object[] wifiList) on every scan. + // No ad-hoc WiFi blending is needed here. + return new LatLng(positionFusion.getFusedLatitude(), + positionFusion.getFusedLongitude()); + } + + // Fallback to original method if position fusion not initialized + // Get base position from start location + float[] startPos = getGNSSLatitude(true); + if (startPos == null || startPos[0] == 0) { + return null; + } + + // Get smoothed PDR position + float[] smoothPos = getSmoothedPDRPosition(); + + // Convert PDR movement to lat/lng offset + // Approximate conversion: 1 degree latitude ≈ 111,139 meters + // At ~56° latitude: 1 degree longitude ≈ 62,422 meters + double latOffset = smoothPos[1] / 111139.0; // Y movement affects latitude + double lngOffset = smoothPos[0] / 62422.0; // X movement affects longitude + + double pdrLat = startPos[0] + latOffset; + double pdrLng = startPos[1] + lngOffset; + + // Check if we have recent WiFi position for fusion + LatLng wifiPos = getLatLngWifiPositioning(); + if (wifiPos != null) { + // Fuse WiFi and PDR positions using weighted average + double fusedLat = (1 - WIFI_PDR_FUSION_WEIGHT) * pdrLat + WIFI_PDR_FUSION_WEIGHT * wifiPos.latitude; + double fusedLng = (1 - WIFI_PDR_FUSION_WEIGHT) * pdrLng + WIFI_PDR_FUSION_WEIGHT * wifiPos.longitude; + return new LatLng(fusedLat, fusedLng); + } + + return new LatLng(pdrLat, pdrLng); + } + + /** + * Check if position tracking is active. + * + * @return true if we have a valid fused position + */ + public boolean hasValidPosition() { + return hasFusedPosition; + } + + /** + * Reset smooth position tracking - call when starting new recording. + */ + public void resetSmoothPosition() { + smoothPdrX = 0f; + smoothPdrY = 0f; + targetPdrX = 0f; + targetPdrY = 0f; + lastPdrX = 0f; + lastPdrY = 0f; + hasFusedPosition = false; + lastPositionUpdateTime = 0; + lastPdrStepTime = 0; + pdrIgnoreUntilMs = 0L; + + // Reset inter-step interpolation state + lastStepSystemTimeMs = 0; + estimatedStepPeriodMs = 500; + isWalking = false; + lastFusionStepPosition = null; + + // Reset dual-phase floor detection state for new recording + wifiFloorAnchor = Integer.MIN_VALUE; + wifiAnchorElevation = 0f; + lastConfirmedFloor = 0; + + // Reset position fusion for new recording + if (positionFusion != null) { + positionFusion.reset(); + } + // Reset particle filter pipeline for new recording + if (fusionManager != null) { + fusionManager.reset(); + } + } + + // Particle Filter / FusionManager public API + /** + * Get the best-estimate position from the Particle Filter fusion pipeline. + * + *

This is the primary position output for the UI. It fuses PDR, GNSS + * and WiFi using a particle filter with map-matching constraints.

+ * + * @return {@link LatLng} of the fused position, or {@code null} if not ready. + */ + public LatLng getParticleFilterPosition() { + if (fusionManager == null || !fusionManager.isInitialized()) { + return null; + } + + LatLng basePosition = fusionManager.getEstimatedPosition(); + if (basePosition == null) { + return null; + } + + // Inter-step interpolation + // Between step events (~2 Hz) the fusion position is frozen. + // Extrapolate forward using current heading & estimated walk speed + // so the displayed marker moves continuously with the user. + long now = System.currentTimeMillis(); + long elapsed = now - lastStepSystemTimeMs; + + // Stop interpolating if user hasn't stepped recently (standing still) + if (!isWalking || lastStepSystemTimeMs == 0 || elapsed > WALKING_TIMEOUT_MS) { + if (elapsed > WALKING_TIMEOUT_MS) { + isWalking = false; + } + return basePosition; + } + + // Only interpolate for the fraction of one step period after the last step. + // Beyond one period the next step should arrive; cap to avoid overshoot. + float fraction = Math.min(1.0f, (float) elapsed / estimatedStepPeriodMs); + + // Estimated distance walked since last step = fraction × lastStepLength + float extrapolateM = fraction * lastStepLengthM; + + // Use current heading (updated at ~100 Hz by GAME_ROTATION_VECTOR) + float heading = getHeadingRad(); + double dEast = extrapolateM * Math.sin(heading); + double dNorth = extrapolateM * Math.cos(heading); + + // Convert metre offsets to lat/lng + double dLat = dNorth / 111139.0; + double dLng = dEast / (111139.0 * Math.cos(Math.toRadians(basePosition.latitude))); + + return new LatLng(basePosition.latitude + dLat, basePosition.longitude + dLng); + } + + /** + * Get the current floor inferred by the map-matching / barometer pipeline. + * + * @return Floor number (0 = ground floor). + */ + public int getInferredFloor() { + // Step 1: compute the candidate floor from whichever source is available. + int candidateFloor; + if (wifiFloorAnchor != Integer.MIN_VALUE) { + // Dual-phase: WiFi absolute anchor + barometer relative delta. + float floorHeightM = (settings != null) ? settings.getInt("floor_height", 4) : 4f; + float baroDelta = this.elevation - wifiAnchorElevation; + candidateFloor = wifiFloorAnchor + Math.round(baroDelta / floorHeightM); + } else if (Math.abs(this.elevation) <= 1.2f) { + // Barometer near zero → candidate is ground floor; still apply zone gate below. + candidateFloor = 0; + } else if (fusionManager != null) { + candidateFloor = fusionManager.getCurrentFloor(); + } else { + return lastConfirmedFloor; + } + + // Step 2: if candidate differs from last confirmed, apply zone gate. + // This single gate covers ALL paths (WiFi-anchored and barometer-only), + // fixing the two previous bypass holes. + if (candidateFloor != lastConfirmedFloor) { + boolean zonesLoaded = !liftZones.isEmpty() || !stairZones.isEmpty(); + if (zonesLoaded) { + LatLng pos = (fusionManager != null && fusionManager.isInitialized()) + ? fusionManager.getEstimatedPosition() : null; + boolean nearTransition = pos != null + && (isNearZone(pos, liftZones, LIFT_ZONE_RADIUS_M) + || isNearZone(pos, stairZones, STAIR_ZONE_RADIUS_M)); + android.util.Log.w("SensorFusion", "[ZoneGate] FLOOR CHANGE ATTEMPT: " + + lastConfirmedFloor + " → " + candidateFloor + + " pos=" + pos + + " nearTransition=" + nearTransition + + " liftZones=" + liftZones.size() + " stairZones=" + stairZones.size()); + if (!nearTransition) { + android.util.Log.w("SensorFusion", "[ZoneGate] BLOCKED (not near any transition zone)"); + return lastConfirmedFloor; // blocked: not near any lift or staircase + } + android.util.Log.w("SensorFusion", "[ZoneGate] ALLOWED (near transition zone)"); + } else { + android.util.Log.w("SensorFusion", "[ZoneGate] BYPASSED (no zones loaded) " + lastConfirmedFloor + " → " + candidateFloor); + } + lastConfirmedFloor = candidateFloor; + } + return lastConfirmedFloor; + } + + /** True once at least one WiFi floor fix has been received and the dual-phase anchor is set. */ + public boolean hasWifiFloorAnchor() { + return wifiFloorAnchor != Integer.MIN_VALUE; + } + + /** + * Supply lift and staircase LatLng centres parsed from the API floor plan. + * Called by RecordingFragment whenever a new floor is drawn. + * Only used for floor-switch constraints; has no effect on PDR or position fusion. + */ + public void setFloorTransitionZones(List lifts, List stairs) { + // Only replace existing zones if the new list is non-empty. + // This prevents a floor with no POI data from wiping out valid zones + // and accidentally disabling the gate (zonesLoaded = false). + if (lifts != null && !lifts.isEmpty()) this.liftZones = lifts; + if (stairs != null && !stairs.isEmpty()) this.stairZones = stairs; + } + + /** + * Feature 2: determine whether the user is currently using a lift or a staircase. + * + *

Decision rule: + *

    + *
  • ELEVATOR – near a lift zone AND the accelerometer-based + * {@code estimateElevator()} model reports minimal horizontal motion.
  • + *
  • STAIRS – near a staircase zone AND the user is walking + * (step detector fired recently) AND barometer shows height change.
  • + *
  • NONE – neither condition is met.
  • + *
+ * This is purely for display / logging and never feeds back into position or PDR.

+ */ + public VerticalTransportMode getVerticalTransportMode() { + LatLng pos = (fusionManager != null && fusionManager.isInitialized()) + ? fusionManager.getEstimatedPosition() : null; + if (pos == null) return VerticalTransportMode.NONE; + + float baroDelta = this.elevation - wifiAnchorElevation; + boolean heightChanging = Math.abs(baroDelta) > 0.8f; // > ~0.8 m movement detected + + if (isNearZone(pos, liftZones, LIFT_ZONE_RADIUS_M) && elevator) { + return VerticalTransportMode.ELEVATOR; + } + if (isNearZone(pos, stairZones, STAIR_ZONE_RADIUS_M) && isWalking && heightChanging) { + return VerticalTransportMode.STAIRS; + } + return VerticalTransportMode.NONE; + } + + /** Equirectangular distance approximation (accurate to < 1 % for indoor ranges < 200 m). */ + private static double latLngDistanceM(LatLng a, LatLng b) { + double dLat = (b.latitude - a.latitude) * 111320.0; + double dLng = (b.longitude - a.longitude) * 111320.0 + * Math.cos(Math.toRadians(a.latitude)); + return Math.sqrt(dLat * dLat + dLng * dLng); + } + + private boolean isNearZone(LatLng pos, List zones, double radiusM) { + for (LatLng zone : zones) { + if (latLngDistanceM(pos, zone) <= radiusM) return true; + } + return false; + } + + /** + * Get the source of the most recent position update (PDR / GNSS / WiFi / FUSED). + * + * @return {@link FusionManager.PositionSource} enum value. + */ + public FusionManager.PositionSource getLastPositionSource() { + if (fusionManager != null) { + return fusionManager.getLastSource(); + } + return FusionManager.PositionSource.PDR; + } + + /** + * Initialise the Particle Filter pipeline with a known starting position. + * + *

Call this when the user confirms their starting location (e.g. from + * {@link com.openpositioning.PositionMe.presentation.fragment.StartLocationFragment}).

+ * + * @param latDeg Starting latitude (decimal degrees). + * @param lngDeg Starting longitude (decimal degrees). + * @param floor Starting floor number (0 = ground). + */ + public void initFusionManager(double latDeg, double lngDeg, int floor) { + if (fusionManager != null) { + double headingRad = gameRotVecReady ? this.headingRad + : ((orientation != null) ? orientation[0] : 0.0); + fusionManager.initialize(latDeg, lngDeg, 10.0f, headingRad, floor); + } + } + + /** + * Load the Nucleus building map into the map matcher. + * + * @param floor Floor number. + */ + public void loadNucleusMap(int floor) { + if (fusionManager != null) { + fusionManager.loadNucleusMap(floor); + } + } + + /** + * Configure wall constraints from API floor-plan walls. + * + * @param wallPolylines Wall polylines in WGS84. + * @param floor Floor number for the supplied map. + * @return Number of wall segments applied to the matcher. + */ + public int configureIndoorWallConstraints(List> wallPolylines, int floor) { + if (fusionManager == null) { + return 0; + } + return fusionManager.configureDynamicWallMap(wallPolylines, floor); + } + + /** @return The {@link FusionManager} instance (for advanced configuration). */ + public FusionManager getFusionManager() { return fusionManager; } + + /** + * Get current position uncertainty estimate in meters. + * Useful for displaying confidence level to user. + * + * @return Position uncertainty in meters, or Float.MAX_VALUE if not initialized + */ + public float getPositionUncertainty() { + if (positionFusion != null && positionFusion.isInitialized()) { + return positionFusion.getPositionUncertainty(); + } + return Float.MAX_VALUE; + } + + /** + * Check if GNSS data is stale (not receiving recent updates). + * Useful for showing indoor/outdoor indicator. + * + * @return true if GNSS hasn't been updated recently + */ + public boolean isGnssStale() { + if (positionFusion != null) { + return positionFusion.isGnssStale(); + } + return true; + } + + /** + * Get time since last GNSS update in milliseconds. + * + * @return Time since last GNSS update + */ + public long getTimeSinceLastGnss() { + if (positionFusion != null) { + return positionFusion.getTimeSinceLastGnss(); + } + return Long.MAX_VALUE; + } + + /** + * Force reset position to current GNSS location. + * Call when user manually corrects position or exits building. + */ + public void forceResetToGnss() { + if (positionFusion != null && latitude != 0 && longitude != 0) { + positionFusion.forceReset(latitude, longitude, 10.0f); + } + } + + /** + * Set anchor point for position correction. + * User marks their known current position on map to correct accumulated drift. + * The fusion algorithm will gradually correct towards this position. + * + * @param lat Latitude of the known position + * @param lng Longitude of the known position + */ + public void setPositionAnchor(double lat, double lng) { + if (positionFusion != null) { + positionFusion.setAnchorPoint(lat, lng); + Log.d("SensorFusion", String.format("Position anchor set at (%.6f, %.6f)", lat, lng)); + } + } + + /** + * Enable/disable building constraint. + * When enabled, position will be constrained to building boundaries. + * + * @param enable true to enable building constraint + */ + public void setBuildingConstraint(boolean enable) { + if (positionFusion != null) { + positionFusion.setConstrainToBuilding(enable); + } + } + + /** + * Check if there's an active anchor point. + * + * @return true if anchor point is set + */ + public boolean hasPositionAnchor() { + if (positionFusion != null) { + return positionFusion.hasAnchorPoint(); + } + return false; + } + /** * Return the most recent list of WiFi names and levels. * Each Wifi object contains a BSSID and a level value. @@ -795,6 +1919,7 @@ public int getHoldMode(){ * @see GNSSDataProcessor handles location data. */ public void resumeListening() { + // 10000 microseconds = 100Hz for IMU sensors, restored to original values accelerometerSensor.sensorManager.registerListener(this, accelerometerSensor.sensor, 10000, (int) maxReportLatencyNs); accelerometerSensor.sensorManager.registerListener(this, linearAccelerationSensor.sensor, 10000, (int) maxReportLatencyNs); accelerometerSensor.sensorManager.registerListener(this, gravitySensor.sensor, 10000, (int) maxReportLatencyNs); @@ -804,7 +1929,9 @@ public void resumeListening() { proximitySensor.sensorManager.registerListener(this, proximitySensor.sensor, (int) 1e6); magnetometerSensor.sensorManager.registerListener(this, magnetometerSensor.sensor, 10000, (int) maxReportLatencyNs); stepDetectionSensor.sensorManager.registerListener(this, stepDetectionSensor.sensor, SensorManager.SENSOR_DELAY_NORMAL); - rotationSensor.sensorManager.registerListener(this, rotationSensor.sensor, (int) 1e6); + // Higher heading update rate reduces turn lag during sharp direction changes. + rotationSensor.sensorManager.registerListener(this, rotationSensor.sensor, 20000, (int) maxReportLatencyNs); + gameRotationSensor.sensorManager.registerListener(this, gameRotationSensor.sensor, 10000, (int) maxReportLatencyNs); wifiProcessor.startListening(); gnssProcessor.startLocationUpdates(); } @@ -829,6 +1956,7 @@ public void stopListening() { magnetometerSensor.sensorManager.unregisterListener(this); stepDetectionSensor.sensorManager.unregisterListener(this); rotationSensor.sensorManager.unregisterListener(this); + gameRotationSensor.sensorManager.unregisterListener(this); linearAccelerationSensor.sensorManager.unregisterListener(this); gravitySensor.sensorManager.unregisterListener(this); //The app often crashes here because the scan receiver stops after it has found the list. @@ -863,10 +1991,65 @@ public void startRecording() { this.stepCounter = 0; this.absoluteStartTime = System.currentTimeMillis(); this.bootTime = SystemClock.uptimeMillis(); - // Protobuf trajectory class for sending sensor data to restful API + + // Generate trajectory ID from venue + timestamp + String trajectoryIdPrefix = ""; + try { + // Try to get venue name from VenueManager if available + Class venueManagerClass = Class.forName("com.openpositioning.PositionMe.presentation.fragment.VenueManager"); + java.lang.reflect.Method getInstanceMethod = venueManagerClass.getMethod("getInstance", Context.class); + Object venueManager = getInstanceMethod.invoke(null, appContext); + + java.lang.reflect.Method hasVenueMethod = venueManagerClass.getMethod("hasSelectedVenue"); + boolean hasVenue = (boolean) hasVenueMethod.invoke(venueManager); + + if (hasVenue) { + java.lang.reflect.Method getVenueNameMethod = venueManagerClass.getMethod("getSelectedVenueName"); + String venueName = (String) getVenueNameMethod.invoke(venueManager); + trajectoryIdPrefix = venueName.replaceAll("\\s+", "_") + "_"; + // Capture floor so it can be saved into initialPosition.floor on stopRecording + java.lang.reflect.Method getFloorMethod = venueManagerClass.getMethod("getSelectedFloor"); + String floor = (String) getFloorMethod.invoke(venueManager); + this.recordingVenueFloor = (floor != null) ? floor : ""; + } + } catch (Exception e) { + Log.d("SensorFusion", "Could not retrieve venue from VenueManager: " + e.getMessage()); + } + + this.trajectoryId = trajectoryIdPrefix + System.currentTimeMillis(); + Log.d("SensorFusion", "Trajectory ID generated: " + this.trajectoryId); + + // Initialize corrected positions and PDR data lists + this.correctedPositions.clear(); + this.replayTrackPoints.clear(); + this.pdrData.clear(); + this.wifiFingerprints.clear(); + this.wifiAPData.clear(); + this.bleData.clear(); + this.bleFingerprints.clear(); + this.wifiRttData.clear(); + this.testPoints.clear(); // Clear test points + this.testPointCounter = 0; // Reset test point counter + this.accelMagnitude.clear(); // Clear accumulated acceleration data from previous recording + this.initialPositionSet = false; + + // Reset heading-offset calibration so it recalibrates from the current + // magnetic environment at the start of each new recording. + this.headingOffsetCalibrated = false; + this.headingOffset = 0f; + this.headingOffsetInitSinSum = 0f; + this.headingOffsetInitCosSum = 0f; + this.headingOffsetInitSampleCount = 0; + this.lastHeadingOffsetAdaptMs = 0L; + this.lastTurnDetectedMs = 0L; + + // Proto: Protobuf trajectory class for sending sensor data to restful API + // Note: start_timestamp uses MILLISECONDS (matching existing Traj.java field #10) this.trajectory = Traj.Trajectory.newBuilder() .setAndroidVersion(Build.VERSION.RELEASE) - .setStartTimestamp(absoluteStartTime) + .setTrajectoryVersion(2.0f) + .setTrajectoryId(this.trajectoryId != null ? this.trajectoryId : "") + .setStartTimestamp(absoluteStartTime) // milliseconds (System.currentTimeMillis()) .setAccelerometerInfo(createInfoBuilder(accelerometerSensor)) .setGyroscopeInfo(createInfoBuilder(gyroscopeSensor)) .setMagnetometerInfo(createInfoBuilder(magnetometerSensor)) @@ -875,9 +2058,19 @@ public void startRecording() { + // Cancel any existing timer before starting a new one to prevent two timers + // running concurrently (which would double the IMU frequency and cause server rejection). + if (this.storeTrajectoryTimer != null) { + this.storeTrajectoryTimer.cancel(); + this.storeTrajectoryTimer.purge(); + } this.storeTrajectoryTimer = new Timer(); this.storeTrajectoryTimer.schedule(new storeDataInTrajectory(), 0, TIME_CONST); this.pdrProcessing.resetPDR(); + + // Reset smooth position tracking for new recording + resetSmoothPosition(); + if(settings.getBoolean("overwrite_constants", false)) { this.filter_coefficient = Float.parseFloat(settings.getString("accel_filter", "0.96")); } else { @@ -899,12 +2092,153 @@ public void stopRecording() { if(this.saveRecording) { this.saveRecording = false; storeTrajectoryTimer.cancel(); + + // Save all collected data to trajectory protobuf before stopping + saveAllDataToTrajectory(); } if(wakeLock.isHeld()) { this.wakeLock.release(); } } + /** Save collected session metadata into the trajectory protobuf before stopping recording. */ + private void saveAllDataToTrajectory() { + try { + // Save the user's chosen start position as initialPosition in the protobuf. + // This is the position the user selected in StartLocationFragment before recording. + // Without this, downloaded trajectory files have no absolute position reference, + // causing the replay map to center on the current GPS location instead of + // the original recording location. + if (startLocation != null && (startLocation[0] != 0f || startLocation[1] != 0f)) { + trajectory.setInitialPosition(Traj.GNSSPosition.newBuilder() + .setLatitude(startLocation[0]) + .setLongitude(startLocation[1]) + .setFloor(recordingVenueFloor)); + Log.i("SensorFusion", "Saved initialPosition to protobuf: " + + startLocation[0] + ", " + startLocation[1] + ", floor=" + recordingVenueFloor); + } else if (initialPositionSet && (initialLocation[0] != 0f || initialLocation[1] != 0f)) { + trajectory.setInitialPosition(Traj.GNSSPosition.newBuilder() + .setLatitude(initialLocation[0]) + .setLongitude(initialLocation[1]) + .setFloor(recordingVenueFloor)); + Log.i("SensorFusion", "Saved initialPosition (from setInitialPosition) to protobuf: " + + initialLocation[0] + ", " + initialLocation[1] + ", floor=" + recordingVenueFloor); + } + + if (!testPoints.isEmpty()) { + for (Map tp : testPoints) { + try { + double latitude = ((Number) tp.get("latitude")).doubleValue(); + double longitude = ((Number) tp.get("longitude")).doubleValue(); + long timestamp = ((Number) tp.get("timestamp")).longValue(); + String floor = (String) tp.get("floor"); + + trajectory.addTestPoints(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(timestamp) + .setLatitude(latitude) + .setLongitude(longitude) + .setFloor(floor != null ? floor : "") + .build()); + } catch (Exception e) { + Log.e("SensorFusion", "Error saving test point: " + e.getMessage()); + } + } + // Save test point count + trajectory.setTestPointCount(testPointCounter); + Log.d("SensorFusion", "Test Points saved to proto: " + testPoints.size() + " points"); + } + if (!correctedPositions.isEmpty()) { + for (float[] correctedPosition : correctedPositions) { + if (correctedPosition == null || correctedPosition.length < 2) { + continue; + } + trajectory.addCorrectedPositions(Traj.GNSSPosition.newBuilder() + .setLatitude(correctedPosition[0]) + .setLongitude(correctedPosition[1]) + .build()); + } + } + + if (!replayTrackPoints.isEmpty()) { + for (ReplayTrackPoint point : replayTrackPoints) { + trajectory.addCorrectedPositions(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(point.relativeTimestamp) + .setLatitude(point.latitude) + .setLongitude(point.longitude) + .build()); + } + Log.d("SensorFusion", "Replay track samples saved: " + replayTrackPoints.size()); + } + + // Save WiFi AP Data + if (!wifiAPData.isEmpty()) { + for (Map apData : wifiAPData) { + try { + long mac = (long) apData.get("mac"); + String ssid = (String) apData.get("ssid"); + long frequency = (long) apData.get("frequency"); + boolean rttEnabled = (boolean) apData.get("rtt_enabled"); + + trajectory.addApsData(Traj.WiFiAPData.newBuilder() + .setMac(mac) + .setSsid(ssid) + .setFrequency(frequency) + .setRttEnabled(rttEnabled) + .build()); + } catch (Exception e) { + Log.e("SensorFusion", "Error saving WiFi AP data: " + e.getMessage()); + } + } + Log.d("SensorFusion", "WiFi AP Data saved to proto: " + wifiAPData.size() + " access points"); + + // Log RTT flags separately (will be saved to proto once recompiled) + int rttCount = 0; + for (Map apData : wifiAPData) { + if ((boolean) apData.get("rtt_enabled")) { + rttCount++; + } + } + if (rttCount > 0) { + Log.d("SensorFusion", "WiFi RTT enabled: " + rttCount + " / " + wifiAPData.size() + " APs"); + } + } + + if (!bleData.isEmpty()) { + for (Map ble : bleData) { + try { + String macAddress = (String) ble.get("mac_address"); + String name = (String) ble.get("name"); + int txPower = ((Number) ble.get("tx_power")).intValue(); + int flags = ((Number) ble.get("flags")).intValue(); + + @SuppressWarnings("unchecked") + List serviceUuids = (List) ble.get("service_uuids"); + + Traj.BleData.Builder bleBuilder = Traj.BleData.newBuilder() + .setMacAddress(macAddress != null ? macAddress : "") + .setName(name != null ? name : "") + .setTxPowerLevel(txPower) + .setAdvertiseFlags(flags); + + if (serviceUuids != null) { + bleBuilder.addAllServiceUuids(serviceUuids); + } + + trajectory.addBleData(bleBuilder.build()); + } catch (Exception e) { + Log.e("SensorFusion", "Error saving BLE data: " + e.getMessage()); + } + } + } + + Log.i("SensorFusion", "Protobuf data saved: testPoints=" + testPoints.size() + + " wifiAPs=" + wifiAPData.size() + " ble=" + bleData.size()); + + } catch (Exception e) { + Log.e("SensorFusion", "Error saving data to trajectory: " + e.getMessage(), e); + } + } + //endregion //region Trajectory object @@ -922,7 +2256,7 @@ public void sendTrajectoryToCloud() { } /** - * Creates a {@link Traj.Sensor_Info} objects from the specified sensor's data. + * Creates a {@link Traj.SensorInfo} objects from the specified sensor's data. * * @param sensor MovementSensor objects with populated sensorInfo fields * @return Traj.SensorInfo object to be used in building the trajectory @@ -930,8 +2264,8 @@ public void sendTrajectoryToCloud() { * @see Traj Trajectory object used for communication with the server * @see MovementSensor class abstracting SensorManager based sensors */ - private Traj.Sensor_Info.Builder createInfoBuilder(MovementSensor sensor) { - return Traj.Sensor_Info.newBuilder() + private Traj.SensorInfo.Builder createInfoBuilder(MovementSensor sensor) { + return Traj.SensorInfo.newBuilder() .setName(sensor.sensorInfo.getName()) .setVendor(sensor.sensorInfo.getVendor()) .setResolution(sensor.sensorInfo.getResolution()) @@ -948,44 +2282,69 @@ private Traj.Sensor_Info.Builder createInfoBuilder(MovementSensor sensor) { */ private class storeDataInTrajectory extends TimerTask { public void run() { + long currentTimestamp = SystemClock.uptimeMillis() - bootTime; + + // Clamp magnetometer values to [-999, 999] range (server validation limit) + float magX = Math.max(-999f, Math.min(999f, magneticField[0])); + float magY = Math.max(-999f, Math.min(999f, magneticField[1])); + float magZ = Math.max(-999f, Math.min(999f, magneticField[2])); + + // Normalize the rotation vector quaternion before storing. + // Android's rotation vector sensor can produce quaternions with norm slightly + // above 1.0 due to floating-point drift. The server rejects quaternions whose + // norm deviates more than 1% from 1.0. + float qx = rotation[0], qy = rotation[1], qz = rotation[2], qw = rotation[3]; + float norm = (float) Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); + if (norm > 0f && Math.abs(norm - 1.0f) > 1e-6f) { + qx /= norm; + qy /= norm; + qz /= norm; + qw /= norm; + } + // Store IMU and magnetometer data in Trajectory class - trajectory.addImuData(Traj.Motion_Sample.newBuilder() - .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime) - .setAccX(acceleration[0]) - .setAccY(acceleration[1]) - .setAccZ(acceleration[2]) - .setGyrX(angularVelocity[0]) - .setGyrY(angularVelocity[1]) - .setGyrZ(angularVelocity[2]) - .setGyrZ(angularVelocity[2]) - .setRotationVectorX(rotation[0]) - .setRotationVectorY(rotation[1]) - .setRotationVectorZ(rotation[2]) - .setRotationVectorW(rotation[3]) + trajectory.addImuData(Traj.IMUReading.newBuilder() + .setRelativeTimestamp(currentTimestamp) + .setAcc(Traj.Vector3.newBuilder() + .setX(acceleration[0]) + .setY(acceleration[1]) + .setZ(acceleration[2])) + .setGyr(Traj.Vector3.newBuilder() + .setX(angularVelocity[0]) + .setY(angularVelocity[1]) + .setZ(angularVelocity[2])) + .setRotationVector(Traj.Quaternion.newBuilder() + .setX(qx) + .setY(qy) + .setZ(qz) + .setW(qw)) .setStepCount(stepCounter)) - .addPositionData(Traj.Position_Sample.newBuilder() - .setMagX(magneticField[0]) - .setMagY(magneticField[1]) - .setMagZ(magneticField[2]) - .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime)) -// .addGnssData(Traj.GNSS_Sample.newBuilder() -// .setLatitude(latitude) -// .setLongitude(longitude) -// .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime)) - ; - + .addMagnetometerData(Traj.MagnetometerReading.newBuilder() + .setRelativeTimestamp(currentTimestamp) + .setMag(Traj.Vector3.newBuilder() + .setX(magX) + .setY(magY) + .setZ(magZ))); + + // Collect WiFi fingerprint data from all scanned networks + // This stores RSSI values from all detected WiFi networks at this timestamp // Divide timer with a counter for storing data every 1 second if (counter == 99) { counter = 0; // Store pressure and light data if (barometerSensor.sensor != null) { - trajectory.addPressureData(Traj.Pressure_Sample.newBuilder() - .setPressure(pressure) - .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime)) - .addLightData(Traj.Light_Sample.newBuilder() + trajectory.addPressureData(Traj.BarometerReading.newBuilder() + .setRelativeTimestamp(currentTimestamp) + .setPressure(pressure)) + .addLightData(Traj.LightReading.newBuilder() + .setRelativeTimestamp(currentTimestamp) .setLight(light) - .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) .build()); + + // Store proximity sensor data if available + if (proximitySensor.sensor != null) { + setProximity(proximity); + } } // Divide the timer for storing AP data every 5 seconds @@ -993,10 +2352,19 @@ public void run() { secondCounter = 0; //Current Wifi Object Wifi currentWifi = wifiProcessor.getCurrentWifiData(); - trajectory.addApsData(Traj.AP_Data.newBuilder() - .setMac(currentWifi.getBssid()) - .setSsid(currentWifi.getSsid()) - .setFrequency(currentWifi.getFrequency())); + // Only store aps_data when connected to a valid WiFi AP. + // mac=0 or frequency=0 means no connection or anonymised BSSID on Android 12+; + // the server rejects entries with these invalid values. + if (currentWifi.getBssid() != 0 && currentWifi.getFrequency() != 0) { + String ssid = currentWifi.getSsid(); + trajectory.addApsData(Traj.WiFiAPData.newBuilder() + .setMac(currentWifi.getBssid()) + .setSsid(ssid != null ? ssid : "") + .setFrequency(currentWifi.getFrequency())); + } + + // Store WiFi fingerprints every 5 seconds + // (stores all WiFi networks detected in current scan) } else { secondCounter++; @@ -1007,6 +2375,338 @@ public void run() { } } + + } + + //endregion + + //region New Proto 2.0 Support Methods + + /** + * Get the trajectory identifier (name) for the current recording + * @return trajectory ID combining venue name and timestamp + */ + public String getTrajectoryId() { + return trajectoryId; + } + + /** + * Set the initial position for trajectory + * @param lat Initial latitude + * @param lon Initial longitude + * @param orientation Initial device orientation (in degrees) + */ + public void setInitialPosition(float lat, float lon, float orientation) { + if (!initialPositionSet) { + this.initialLocation[0] = lat; + this.initialLocation[1] = lon; + this.initialOrientation = orientation; + this.initialPositionSet = true; + Log.d("SensorFusion", String.format( + "InitialPosition SET ✓ | Lat: %.6f | Lon: %.6f | Bearing: %.1f° | Flag: %s", + lat, lon, orientation, initialPositionSet + )); + } else { + Log.w("SensorFusion", String.format("InitialPosition already set (%s), ignoring new value | New: (%.6f, %.6f)", + initialPositionSet, lat, lon)); + } + + // Keep fusion/PF in sync with the user-confirmed map anchor. + // Without this, UI "Set Position" only updates metadata and the live fusion state + // can stay on stale GNSS initialization, making PF appear ineffective. + setStartGNSSLatitude(new float[]{lat, lon}); + } + + /** + * Get initial position + * @return float array with [latitude, longitude] + */ + public float[] getInitialPosition() { + return initialLocation; + } + + /** + * Get initial orientation + * @return Initial device bearing/orientation + */ + public float getInitialOrientation() { + return initialOrientation; + } + + /** + * Add a corrected position to the trajectory + * @param lat Corrected latitude + * @param lon Corrected longitude + */ + public void addCorrectedPosition(float lat, float lon) { + correctedPositions.add(new float[]{lat, lon}); + } + + /** + * Add a fused map trajectory point to be used as high-fidelity replay data. + */ + public void addReplayTrackPoint(double lat, double lon) { + if (!saveRecording) { + return; + } + long relativeTimestamp = Math.max(0L, System.currentTimeMillis() - absoluteStartTime); + replayTrackPoints.add(new ReplayTrackPoint(relativeTimestamp, lat, lon)); + } + + /** + * Get all corrected positions + * @return List of corrected positions + */ + public List getCorrectedPositions() { + return correctedPositions; + } + + /** + * Add WiFi fingerprint data + * @param timestamp Relative timestamp in milliseconds + * @param bssid MAC address as long + * @param rssi RSSI value in dBm + */ + public void addWiFiFingerprint(long timestamp, long bssid, int rssi) { + Map fingerprint = new HashMap<>(); + fingerprint.put("timestamp", timestamp); + fingerprint.put("mac", bssid); + fingerprint.put("rssi", rssi); + wifiFingerprints.add(fingerprint); + } + + /** + * Get WiFi fingerprint data + * @return List of WiFi fingerprints + */ + public List> getWiFiFingerprints() { + return wifiFingerprints; + } + + /** + * Add WiFi RTT measurement + * @param timestamp Relative timestamp in milliseconds + * @param mac MAC address as long + * @param distance Distance in mm + * @param distanceStd Standard deviation in mm + * @param rssi RSSI value in dBm + */ + public void addWiFiRTTReading(long timestamp, long mac, float distance, float distanceStd, int rssi) { + Map rttData = new HashMap<>(); + rttData.put("timestamp", timestamp); + rttData.put("mac", mac); + rttData.put("distance_mm", distance); + rttData.put("distance_std_mm", distanceStd); + rttData.put("rssi", rssi); + wifiRttData.add(rttData); + } + + /** + * Get WiFi RTT data + * @return List of WiFi RTT readings + */ + public List> getWiFiRTTData() { + return wifiRttData; + } + + /** + * Add BLE fingerprint data + * @param timestamp Relative timestamp in milliseconds + * @param macAddress MAC address as string + * @param rssi RSSI value in dBm + * @param txPower TX power level + */ + public void addBLEFingerprint(long timestamp, String macAddress, int rssi, int txPower) { + Map bleFingerprint = new HashMap<>(); + bleFingerprint.put("timestamp", timestamp); + bleFingerprint.put("mac", macAddress); + bleFingerprint.put("rssi", rssi); + bleFingerprint.put("tx_power", txPower); + bleFingerprints.add(bleFingerprint); + } + + /** + * Get BLE fingerprint data + * @return List of BLE fingerprints + */ + public List> getBLEFingerprints() { + return bleFingerprints; + } + + /** + * Add BLE device data + * @param macAddress MAC address + * @param name Device name + * @param txPower TX power level + * @param flags Advertisement flags + * @param serviceUuids Service UUIDs + */ + public void addBLEData(String macAddress, String name, int txPower, int flags, List serviceUuids) { + Map ble = new HashMap<>(); + ble.put("mac_address", macAddress); + ble.put("name", name); + ble.put("tx_power", txPower); + ble.put("flags", flags); + ble.put("service_uuids", serviceUuids); + bleData.add(ble); + } + + /** + * Get BLE data + * @return List of BLE devices + */ + public List> getBLEData() { + return bleData; + } + + /** + * Add WiFi Access Point (AP) data with RTT capability flag + * @param mac MAC address (BSSID) as long + * @param ssid Network name + * @param frequency Frequency in MHz (2400 or 5000) + * @param rttEnabled Flag indicating if AP supports RTT measurements + */ + public void addWiFiAPData(long mac, String ssid, long frequency, boolean rttEnabled) { + Map apData = new HashMap<>(); + apData.put("mac", mac); + apData.put("ssid", ssid); + apData.put("frequency", frequency); + apData.put("rtt_enabled", rttEnabled); // WiFi RTT capability flag + wifiAPData.add(apData); + } + + /** + * Get WiFi Access Point data with RTT flags + * @return List of WiFi AP data + */ + public List> getWiFiAPData() { + return wifiAPData; + } /** + * Add PDR (Pedestrian Dead Reckoning) sample + * @param timestamp Relative timestamp in milliseconds + * @param x X position in meters + * @param y Y position in meters + */ + public void addPDRSample(long timestamp, float x, float y) { + float[] pdr = new float[]{timestamp, x, y}; + pdrData.add(pdr); + } + + /** + * Get PDR data + * @return List of PDR samples + */ + public List getPDRData() { + return pdrData; + } + + /** + * Update current proximity sensor reading + * @param distance Distance in cm + */ + public void setProximity(float distance) { + this.currentProximity = distance; + } + + /** + * Get current proximity reading + * @return Proximity distance in cm + */ + public float getProximity() { + return currentProximity; + } + + /** + * Get trajectory version + * @return Trajectory version (2.0) + */ + public float getTrajectoryVersion() { + return trajectoryVersion; + } + + /** + * Get number of collected WiFi fingerprints + * @return Count of WiFi fingerprints + */ + public int getWiFiFingerprintCount() { + return wifiFingerprints.size(); + } + + /** + * Get number of collected corrected positions + * @return Count of corrected positions + */ + public int getCorrectedPositionCount() { + return correctedPositions.size(); + } + + /** + * Check if initial position has been set + * @return True if initial position is set, false otherwise + */ + public boolean isInitialPositionSet() { + boolean result = initialPositionSet || (initialLocation != null && (initialLocation[0] != 0 || initialLocation[1] != 0)); + return result; + } + + // TEST POINT METHODS + + /** + * Add a test point (marker) with current timestamp and location + * @param latitude Current latitude + * @param longitude Current longitude + * @param floor Current floor (if applicable) + * @return Test point number (starting from 1) + */ + public int addTestPoint(double latitude, double longitude, String floor) { + testPointCounter++; + + Map testPoint = new HashMap<>(); + testPoint.put("point_number", testPointCounter); + testPoint.put("timestamp", System.currentTimeMillis() - absoluteStartTime); + testPoint.put("latitude", latitude); + testPoint.put("longitude", longitude); + testPoint.put("floor", floor); + + testPoints.add(testPoint); + + Log.d("SensorFusion", String.format( + "Test Point #%d marked | Lat: %.6f | Lon: %.6f | Floor: %s | Timestamp: %d ms", + testPointCounter, latitude, longitude, floor != null ? floor : "unknown", + (long) testPoint.get("timestamp") + )); + + return testPointCounter; + } + + /** + * Get all recorded test points + * @return List of test points + */ + public List> getTestPoints() { + return testPoints; + } + + /** + * Get the current test point counter + * @return Number of test points marked so far + */ + public int getTestPointCount() { + return testPointCounter; + } + + /** + * Get a specific test point by number + * @param pointNumber The test point number (1-indexed) + * @return Test point data or null if not found + */ + public Map getTestPoint(int pointNumber) { + for (Map tp : testPoints) { + if ((int) tp.get("point_number") == pointNumber) { + return tp; + } + } + return null; } //endregion diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java index dbf809dd..b12e94f9 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java @@ -137,7 +137,6 @@ public void request( JSONObject jsonWifiFeatures, final VolleyCallback callback) Request.Method.POST, url, jsonWifiFeatures, response -> { try { - Log.d("jsonObject",response.toString()); wifiLocation = new LatLng(response.getDouble("lat"),response.getDouble("lon")); floor = response.getInt("floor"); callback.onSuccess(wifiLocation,floor); diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java index fa8a17dd..662cbb25 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java @@ -82,7 +82,7 @@ public WifiDataProcessor(Context context) { // Decreapted method after API 29 // Turn on wifi if it is currently disabled - // TODO - turn it to a notification toward user + // Keep this message in logs for now to avoid interrupting users with frequent toasts. // // if(permissionsGranted && wifiManager.getWifiState()== WifiManager.WIFI_STATE_DISABLED) { // // wifiManager.setWifiEnabled(true); // // } @@ -209,14 +209,11 @@ private boolean checkWifiPermissions() { * broadcast receiver is registered to be called when the scan is complete. */ private void startWifiScan() { - //Check settings for wifi permissions - if(checkWifiPermissions()) { - //if(sharedPreferences.getBoolean("wifi", false)) { - //Register broadcast receiver for wifi scans + // Check settings for wifi permissions + if (checkWifiPermissions()) { + // Register broadcast receiver for wifi scans context.registerReceiver(wifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); wifiManager.startScan(); - - //} } } @@ -317,8 +314,12 @@ public Wifi getCurrentWifiData(){ //Store the ssid, mac address and frequency of the current wifi currentWifi.setSsid(wifiManager.getConnectionInfo().getSSID()); String wifiMacAddress = wifiManager.getConnectionInfo().getBSSID(); - long intMacAddress = convertBssidToLong(wifiMacAddress); - currentWifi.setBssid(intMacAddress); + // getBSSID() can return null on Android 12+ without precise location permission. + // Leave bssid as 0 (default) in that case; SensorFusion will skip the aps_data entry. + if (wifiMacAddress != null) { + long intMacAddress = convertBssidToLong(wifiMacAddress); + currentWifi.setBssid(intMacAddress); + } currentWifi.setFrequency(wifiManager.getConnectionInfo().getFrequency()); } else{ diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/CascadedFusionManager.java b/app/src/main/java/com/openpositioning/PositionMe/utils/CascadedFusionManager.java new file mode 100644 index 00000000..be9be8fe --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/CascadedFusionManager.java @@ -0,0 +1,392 @@ +package com.openpositioning.PositionMe.utils; + +import android.util.Log; + +import com.google.android.gms.maps.model.LatLng; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Cascaded fusion scheduler: DynamicEKF + MapConstrainedPF + WKNN. + */ +public class CascadedFusionManager { + + private static final String TAG = "Fusion"; + + // Only accept WKNN predictions when enough confidence is achieved. + // 0.25 was too strict with real indoor RSSI variability and often prevented + // WiFi corrections from ever being applied. + private static final double LOW_SCORE_THRESHOLD = 0.12; + private static final double BALANCED_OBSERVATION_SCORE = 0.30; + // Maximum distance a WiFi/WKNN fix may be from the current EKF estimate before it is + // rejected as an outlier. Tightened from 8 m to 4 m: anything farther is almost + // certainly a fingerprint database mismatch, not actual movement. + private static final double WIFI_OUTLIER_REJECT_DIST_M = 4.0; + private static final double RESET_DIFF_THRESHOLD_M = 5.0; + private static final double RESET_PF_VAR_THRESHOLD = 2.0; + private static final double WALL_MODE_RESET_DIFF_THRESHOLD_M = 1.2; + private static final double WALL_MODE_MAX_ACCEPTABLE_PF_VAR = 8.0; + private static final double PF_BLEND_CONFIDENCE_VAR = 6.0; + private static final double MAP_SNAP_MAX_WALL_DIST_M = 3.0; + private static final double MAP_SNAP_MAX_HEADING_DIFF_RAD = Math.toRadians(35.0); + private static final double MAP_SNAP_GAIN = 0.75; + private static final int MAX_FINGERPRINTS = 3000; + private static final double ABS_RELOCALIZE_DIST_M = 6.0; + private static final double ABS_RELOCALIZE_MIN_SCORE = 0.40; + private static final int ABS_RELOCALIZE_REQUIRED_STREAK = 2; + private static final double ABS_RELOCALIZE_STRONG_SCORE = 0.80; + private static final double ABS_RELOCALIZE_STRONG_DIST_M = 2.0; + + private CoordinateConverter converter; + private DynamicEKF ekf; + private MapConstrainedPF pf; + private WknnPredictor wknn; + + private List walls = new ArrayList<>(); + private boolean wallConstraintEnabled = false; + private boolean initialized = false; + private int farAbsoluteObsStreak = 0; + + // Output-stage wall correction: applied to fused position before converting to LatLng. + // Does not touch EKF/PF internals. + private MapMatcher outputMapMatcher = null; + private double lastFusedX = 0.0; + private double lastFusedY = 0.0; + + public void initialize(double latDeg, double lngDeg, double headingRad) { + converter = new CoordinateConverter(latDeg, lngDeg); + ekf = new DynamicEKF(0.0, 0.0, headingRad); + pf = new MapConstrainedPF(walls); + pf.reset(0.0, 0.0, headingRad); + wknn = new WknnPredictor(4, 1e-6, 0.05, -100); + farAbsoluteObsStreak = 0; + lastFusedX = 0.0; + lastFusedY = 0.0; + initialized = true; + } + + public boolean isInitialized() { + return initialized; + } + + public void reset() { + initialized = false; + converter = null; + ekf = null; + pf = null; + wknn = null; + farAbsoluteObsStreak = 0; + lastFusedX = 0.0; + lastFusedY = 0.0; + } + + public void setWalls(List wallList) { + walls = (wallList == null) ? new ArrayList<>() : new ArrayList<>(wallList); + wallConstraintEnabled = !walls.isEmpty(); + if (pf != null) { + pf.setWalls(walls); + } + // Rebuild output-stage MapMatcher so correctPosition() has the latest walls. + if (!walls.isEmpty()) { + outputMapMatcher = new MapMatcher(); + for (MapConstrainedPF.Wall w : walls) { + outputMapMatcher.addFeature(new MapMatcher.MapFeature( + MapMatcher.FeatureType.WALL, w.x1, w.y1, w.x2, w.y2, "wall")); + } + } else { + outputMapMatcher = null; + } + } + + public void onStepDetected(double stepLen, double deltaTheta) { + if (!initialized) { + return; + } + if (wallConstraintEnabled) { + double currentTheta = ekf.getTheta(); + double desiredHeading = wrapAngle(currentTheta + deltaTheta); + double snappedHeading = snapHeadingToMapAxis(desiredHeading, ekf.getX(), ekf.getY()); + deltaTheta = wrapAngle(snappedHeading - currentTheta); + } + ekf.predict(stepLen, deltaTheta); + pf.predict(stepLen, deltaTheta); + maybeResetEkfFromPf(); + } + + public void alignHeading(double headingRad) { + if (!initialized) { + return; + } + ekf.setHeading(headingRad); + pf.setHeading(headingRad); + } + + public WknnPredictor.WknnResult onWifiScanned(Map currentScan) { + if (!initialized || wknn == null) { + return null; + } + + WknnPredictor.WknnResult result = wknn.predictPosition(currentScan); + if (result == null) { + return null; + } + + if (result.score < LOW_SCORE_THRESHOLD) { + return result; + } + + // Outlier rejection: ignore WKNN fix that is too far from current estimate. + // A large jump means the scan matched a fingerprint from a different location. + double dist = Math.hypot(result.x - ekf.getX(), result.y - ekf.getY()); + if (dist > WIFI_OUTLIER_REJECT_DIST_M) { + Log.w(TAG, String.format("WiFi WKNN outlier rejected: %.1fm from EKF estimate", dist)); + return result; + } + + // Use the actual WKNN match score rather than a fixed constant so that + // poor matches are trusted less and the EKF covariance scales accordingly. + ekf.updateWithDynamicR(result.x, result.y, result.score); + pf.update(result.x, result.y, result.score); + maybeResetEkfFromPf(); + return result; + } + + public void onAbsoluteObservationLatLng(double lat, double lng, double score) { + if (!initialized || converter == null) { + return; + } + double[] en = converter.toEastNorth(lat, lng); + onAbsoluteObservationEn(en[0], en[1], score); + } + + public void onAbsoluteObservationEn(double x, double y, double score) { + if (!initialized) { + return; + } + double priorDiff = Math.hypot(x - ekf.getX(), y - ekf.getY()); + double useScore = Math.max(LOW_SCORE_THRESHOLD, + Math.min(1.0, Double.isFinite(score) ? score : BALANCED_OBSERVATION_SCORE)); + + // Strong trusted observation (e.g. cliff-phase WiFi) can trigger immediate relocalization + // to remove the last corridor-length lag near exits. + if (useScore >= ABS_RELOCALIZE_STRONG_SCORE && priorDiff >= ABS_RELOCALIZE_STRONG_DIST_M) { + ekf.resetState(x, y); + pf.relocalize(x, y, ekf.getTheta()); + farAbsoluteObsStreak = 0; + Log.w(TAG, String.format("Immediate relocalize by strong obs: diff=%.1fm score=%.2f", priorDiff, useScore)); + maybeResetEkfFromPf(); + return; + } + + ekf.updateWithDynamicR(x, y, useScore); + pf.update(x, y, useScore); + + // Exit-release: when trusted absolute observations repeatedly disagree + // with the local EKF/PF state by a large distance, force relocalize. + // This prevents "still inside building" lag when the user has already + // reached the entrance/outdoor area. + if (useScore >= ABS_RELOCALIZE_MIN_SCORE && priorDiff >= ABS_RELOCALIZE_DIST_M) { + farAbsoluteObsStreak++; + } else { + farAbsoluteObsStreak = 0; + } + + if (farAbsoluteObsStreak >= ABS_RELOCALIZE_REQUIRED_STREAK) { + ekf.resetState(x, y); + pf.relocalize(x, y, ekf.getTheta()); + farAbsoluteObsStreak = 0; + Log.w(TAG, String.format("Relocalized by absolute obs: diff=%.1fm score=%.2f", priorDiff, useScore)); + } + + maybeResetEkfFromPf(); + } + + public void addFingerprintFromCurrentEstimate(Map scan) { + if (!initialized || wknn == null || scan == null || scan.isEmpty()) { + return; + } + if (wknn.size() >= MAX_FINGERPRINTS) { + return; + } + wknn.addFingerprint(new WknnPredictor.Fingerprint(ekf.getX(), ekf.getY(), scan)); + } + + public LatLng getEstimatedLatLng() { + if (!initialized || converter == null) { + return null; + } + + double pfVar = pf.getConfidence(); + double fusedX; + double fusedY; + + if (wallConstraintEnabled) { + if (Double.isFinite(pfVar) && pfVar <= WALL_MODE_MAX_ACCEPTABLE_PF_VAR) { + // Use a stronger PF blend when PF confidence is high to reduce EKF lag. + double pfWeight = (pfVar <= PF_BLEND_CONFIDENCE_VAR) ? 0.40 : 0.25; + fusedX = (1.0 - pfWeight) * ekf.getX() + pfWeight * pf.getX(); + fusedY = (1.0 - pfWeight) * ekf.getY() + pfWeight * pf.getY(); + } else { + fusedX = ekf.getX(); + fusedY = ekf.getY(); + } + } else { + // Even without wall constraints, blend PF when confidence is good + // so the UI does not trail behind a conservative EKF state. + if (Double.isFinite(pfVar) && pfVar <= PF_BLEND_CONFIDENCE_VAR) { + fusedX = 0.7 * ekf.getX() + 0.3 * pf.getX(); + fusedY = 0.7 * ekf.getY() + 0.3 * pf.getY(); + } else { + fusedX = ekf.getX(); + fusedY = ekf.getY(); + } + } + + // Output-stage wall correction: slide the fused position back to the legal side + // of any wall it crossed. EKF/PF internal states are NOT modified. + if (outputMapMatcher != null) { + double[] corrected = outputMapMatcher.correctPosition(lastFusedX, lastFusedY, fusedX, fusedY); + fusedX = corrected[0]; + fusedY = corrected[1]; + } + lastFusedX = fusedX; + lastFusedY = fusedY; + + double[] latLng = converter.toLatLng(fusedX, fusedY); + return new LatLng(latLng[0], latLng[1]); + } + + public double getEkfX() { + return initialized ? ekf.getX() : 0.0; + } + + public double getEkfY() { + return initialized ? ekf.getY() : 0.0; + } + + public double getPfX() { + return initialized ? pf.getX() : 0.0; + } + + public double getPfY() { + return initialized ? pf.getY() : 0.0; + } + + public double getPfConfidence() { + return initialized ? pf.getConfidence() : Double.POSITIVE_INFINITY; + } + + public CoordinateConverter getConverter() { + return converter; + } + + public double getEkfTheta() { + return initialized ? ekf.getTheta() : Double.NaN; + } + + private void maybeResetEkfFromPf() { + if (!initialized) return; + + double diff = Math.hypot(ekf.getX() - pf.getX(), ekf.getY() - pf.getY()); + double pfVariance = pf.getConfidence(); + + if (!Double.isFinite(pfVariance) || pfVariance > WALL_MODE_MAX_ACCEPTABLE_PF_VAR) { + return; + } + + // For small differences, apply stronger tether to reduce persistent lag. + if (diff <= WALL_MODE_RESET_DIFF_THRESHOLD_M) { + double newX = ekf.getX() * 0.70 + pf.getX() * 0.30; + double newY = ekf.getY() * 0.70 + pf.getY() * 0.30; + ekf.setPosition(newX, newY); + return; + } + + // For medium divergence, pull EKF gradually instead of waiting for hard reset. + if (diff <= 4.0) { + double newX = ekf.getX() * 0.80 + pf.getX() * 0.20; + double newY = ekf.getY() * 0.80 + pf.getY() * 0.20; + ekf.setPosition(newX, newY); + return; + } + + // Only perform a hard reset / teleport if divergence is totally catastrophic. + if (diff > 8.0) { + ekf.resetState(pf.getX(), pf.getY()); + Log.w(TAG, "EKF 穿墙走飞!已被 PF 强行硬重置。"); + } + } + + private double snapHeadingToMapAxis(double heading, double x, double y) { + if (walls == null || walls.isEmpty()) { + return heading; + } + + double nearestDist = Double.POSITIVE_INFINITY; + WallNearest nearest = null; + for (MapConstrainedPF.Wall wall : walls) { + WallNearest candidate = distanceToSegment(x, y, wall); + if (candidate.distance < nearestDist) { + nearestDist = candidate.distance; + nearest = candidate; + } + } + + if (nearest == null || nearestDist > MAP_SNAP_MAX_WALL_DIST_M) { + return heading; + } + + double axisHeading = wallHeading(nearest.wall); + double axisOpposite = wrapAngle(axisHeading + Math.PI); + double d1 = Math.abs(wrapAngle(axisHeading - heading)); + double d2 = Math.abs(wrapAngle(axisOpposite - heading)); + double target = d1 <= d2 ? axisHeading : axisOpposite; + + double diff = wrapAngle(target - heading); + if (Math.abs(diff) > MAP_SNAP_MAX_HEADING_DIFF_RAD) { + return heading; + } + + return wrapAngle(heading + MAP_SNAP_GAIN * diff); + } + + private static class WallNearest { + final MapConstrainedPF.Wall wall; + final double distance; + + WallNearest(MapConstrainedPF.Wall wall, double distance) { + this.wall = wall; + this.distance = distance; + } + } + + private WallNearest distanceToSegment(double px, double py, MapConstrainedPF.Wall wall) { + double vx = wall.x2 - wall.x1; + double vy = wall.y2 - wall.y1; + double segLen2 = vx * vx + vy * vy; + if (segLen2 < 1e-9) { + double d = Math.hypot(px - wall.x1, py - wall.y1); + return new WallNearest(wall, d); + } + + double t = ((px - wall.x1) * vx + (py - wall.y1) * vy) / segLen2; + t = Math.max(0.0, Math.min(1.0, t)); + double projX = wall.x1 + t * vx; + double projY = wall.y1 + t * vy; + double d = Math.hypot(px - projX, py - projY); + return new WallNearest(wall, d); + } + + private double wallHeading(MapConstrainedPF.Wall wall) { + // EN -> heading(0=north, clockwise positive) + return wrapAngle(Math.atan2(wall.x2 - wall.x1, wall.y2 - wall.y1)); + } + + private double wrapAngle(double angle) { + while (angle > Math.PI) angle -= 2.0 * Math.PI; + while (angle < -Math.PI) angle += 2.0 * Math.PI; + return angle; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateConverter.java b/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateConverter.java new file mode 100644 index 00000000..52791489 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateConverter.java @@ -0,0 +1,78 @@ +package com.openpositioning.PositionMe.utils; + +/** + * Utility class for converting between WGS84 geographic coordinates and a local + * East/North Cartesian plane centred on a reference point. + * + *

All positioning algorithms (PDR, Particle Filter) operate in the local + * East/North metric space (metres). GNSS and WiFi fixes arrive in WGS84 + * (latitude / longitude degrees) and must be projected before they can be + * combined with PDR data.

+ * + *

The flat-earth approximation used here is accurate to within ~1 cm over + * distances of up to ~1 km, which is more than sufficient for indoor + * positioning.

+ * + * @author PositionMe Assessment 2 + */ +public class CoordinateConverter { + + /** Metres per degree of latitude (constant everywhere on Earth). */ + private static final double METRES_PER_DEG_LAT = 111_139.0; + + /** Reference latitude used to scale longitude degrees → metres. */ + private final double refLat; + /** Reference longitude. */ + private final double refLng; + + /** Metres per degree of longitude at the reference latitude. */ + private final double metresPerDegLng; + + /** + * Create a converter anchored at the given WGS84 reference point. + * + * @param refLatDeg Reference latitude in decimal degrees. + * @param refLngDeg Reference longitude in decimal degrees. + */ + public CoordinateConverter(double refLatDeg, double refLngDeg) { + this.refLat = refLatDeg; + this.refLng = refLngDeg; + this.metresPerDegLng = METRES_PER_DEG_LAT * Math.cos(Math.toRadians(refLatDeg)); + } + + + // WGS84 → local East/North + /** + * Convert a WGS84 position to local East/North coordinates (metres). + * + * @param latDeg Latitude in decimal degrees. + * @param lngDeg Longitude in decimal degrees. + * @return {@code double[]{east, north}} in metres relative to the reference. + */ + public double[] toEastNorth(double latDeg, double lngDeg) { + double east = (lngDeg - refLng) * metresPerDegLng; + double north = (latDeg - refLat) * METRES_PER_DEG_LAT; + return new double[]{east, north}; + } + + // Local East/North → WGS84 + /** + * Convert local East/North coordinates (metres) back to WGS84. + * + * @param east Easting in metres. + * @param north Northing in metres. + * @return {@code double[]{latitude, longitude}} in decimal degrees. + */ + public double[] toLatLng(double east, double north) { + double lat = refLat + north / METRES_PER_DEG_LAT; + double lng = refLng + east / metresPerDegLng; + return new double[]{lat, lng}; + } + + // Convenience helpers + /** @return Reference latitude in decimal degrees. */ + public double getRefLat() { return refLat; } + + /** @return Reference longitude in decimal degrees. */ + public double getRefLng() { return refLng; } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/DynamicEKF.java b/app/src/main/java/com/openpositioning/PositionMe/utils/DynamicEKF.java new file mode 100644 index 00000000..3c6156ab --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/DynamicEKF.java @@ -0,0 +1,286 @@ +package com.openpositioning.PositionMe.utils; + +import org.ejml.simple.SimpleMatrix; + +/** + * Extended Kalman Filter (EKF) with dynamic observation covariance for indoor positioning. + * + *

The filter maintains a 3-DOF state vector {@code [x, y, theta]}, where {@code x} and + * {@code y} are East and North positions in metres (relative to the local ENU origin) and + * {@code theta} is the heading in radians (0 = north, clockwise positive per Android convention). + * + *

Two stages are exposed: + *

    + *
  1. Predict ({@link #predict}): propagates the state using a step-and-turn motion + * model driven by PDR step length and heading change.
  2. + *
  3. Update ({@link #updateWithDynamicR}): fuses an absolute 2-D position observation + * (from WiFi or GNSS) whose measurement noise covariance R is scaled by a confidence + * score — higher score → smaller R → stronger correction.
  4. + *
+ * + *

The Joseph-form covariance update {@code P = (I-KH)P(I-KH)^T + KRK^T} is used for + * numerical stability. + * + * @author PositionMe team + */ +public class DynamicEKF { + + /** Minimum value used as a floor for dynamic observation variance to prevent division by zero. */ + private static final double EPS = 1e-9; + + /** + * Process noise covariance matrix Q (3×3 diagonal). + * Diagonal values represent per-step uncertainty growth in x (0.05 m²), y (0.05 m²), + * and heading theta (0.002 rad²). Larger values cause the EKF to trust observations + * more than its own motion model. + */ + private final SimpleMatrix q; + + /** + * Exponent scaling factor governing how sharply observation variance grows as the + * confidence score falls below 1.0. + * For example, alpha=3.0 doubles the variance roughly when score drops to ~0.77. + */ + private final double alpha; + + /** + * Baseline observation variance (m²) at confidence score = 1.0 (perfect observation). + * At lower scores the actual variance is {@code baseVariance * exp(alpha * (1 - score))}. + */ + private final double baseVariance; + + private SimpleMatrix state; // [x, y, theta]^T — East (m), North (m), heading (rad) + private SimpleMatrix covariance; // 3×3 state error covariance matrix P + + /** + * Convenience constructor using default tuning parameters (alpha=3.0, baseVariance=2.0). + * + * @param initX initial East position in metres (local ENU frame). + * @param initY initial North position in metres (local ENU frame). + * @param initTheta initial heading in radians (0 = north, clockwise positive). + */ + public DynamicEKF(double initX, double initY, double initTheta) { + this(initX, initY, initTheta, 3.0, 2.0); + } + + /** + * Full constructor with explicit tuning parameters. + * + * @param initX initial East position in metres. + * @param initY initial North position in metres. + * @param initTheta initial heading in radians. + * @param alpha observation variance scaling exponent (see {@link #alpha}). + * @param baseVariance baseline observation variance in m² (see {@link #baseVariance}). + */ + public DynamicEKF(double initX, double initY, double initTheta, double alpha, double baseVariance) { + this.alpha = alpha; + this.baseVariance = baseVariance; + this.state = new SimpleMatrix(3, 1, true, new double[]{initX, initY, initTheta}); + this.covariance = SimpleMatrix.identity(3); + this.q = new SimpleMatrix(3, 3, true, new double[]{ + 0.05, 0, 0, + 0, 0.05, 0, + 0, 0, 0.002 + }); + } + + /** + * EKF predict step: propagates the state using a unicycle motion model. + * + *

State transition: + *

+     *   x'     = x + stepLen * sin(theta + deltaTheta)
+     *   y'     = y + stepLen * cos(theta + deltaTheta)
+     *   theta' = wrap(theta + deltaTheta)
+     * 
+ * The covariance is propagated as {@code P = Fx * P * Fx^T + Q}, where Fx is the + * Jacobian of the motion model with respect to the state. + * + * @param stepLen distance travelled in this step, in metres. + * @param deltaTheta change in heading since the previous step, in radians. + */ + public synchronized void predict(double stepLen, double deltaTheta) { + double x = state.get(0); + double y = state.get(1); + double theta = state.get(2); + + double heading = theta + deltaTheta; + // Android azimuth convention (0=north, clockwise positive): + // east = step * sin(heading), north = step * cos(heading) + double newX = x + stepLen * Math.sin(heading); + double newY = y + stepLen * Math.cos(heading); + double newTheta = wrapAngle(heading); + + state.set(0, newX); + state.set(1, newY); + state.set(2, newTheta); + + // Partial derivatives of the motion model with respect to theta (for Jacobian Fx) + double dxdTheta = stepLen * Math.cos(heading); + double dydTheta = -stepLen * Math.sin(heading); + + // Jacobian Fx of the motion model: linearises state transition around current estimate + SimpleMatrix fx = new SimpleMatrix(3, 3, true, new double[]{ + 1, 0, dxdTheta, + 0, 1, dydTheta, + 0, 0, 1 + }); + + // Covariance prediction: P = Fx * P * Fx^T + Q + covariance = fx.mult(covariance).mult(fx.transpose()).plus(q); + } + + /** + * EKF update step with dynamic observation noise covariance R. + * + *

The observation noise variance is computed as: + *

+     *   dynamicVar = baseVariance * exp(alpha * (1 - score))
+     * 
+ * A high {@code score} (close to 1.0) yields a small R, causing the filter to trust + * the observation strongly. A low score (e.g. from a weak WiFi fingerprint match) + * inflates R and reduces the correction magnitude. + * + *

The Joseph-form update is used for numerical stability: + *

+     *   K = P * H^T * (H * P * H^T + R)^{-1}
+     *   P = (I - K*H) * P * (I - K*H)^T + K * R * K^T
+     * 
+ * + * @param obsX observed East position in metres (local ENU frame). + * @param obsY observed North position in metres (local ENU frame). + * @param score confidence score in [0, 1]; higher = more reliable observation. + */ + public synchronized void updateWithDynamicR(double obsX, double obsY, double score) { + double safeScore = Math.max(0.0, Math.min(1.0, score)); + double penalty = Math.exp(alpha * (1.0 - safeScore)); + double dynamicVar = Math.max(EPS, baseVariance * penalty); + + // Observation matrix H: maps 3-D state [x, y, theta] to 2-D observation [x, y] + SimpleMatrix h = new SimpleMatrix(2, 3, true, new double[]{ + 1, 0, 0, + 0, 1, 0 + }); + + // Observation noise covariance matrix R (2×2 diagonal, isotropic) + SimpleMatrix r = new SimpleMatrix(2, 2, true, new double[]{ + dynamicVar, 0, + 0, dynamicVar + }); + + // Observation vector z and innovation (z - H*x) + SimpleMatrix z = new SimpleMatrix(2, 1, true, new double[]{obsX, obsY}); + SimpleMatrix innovation = z.minus(h.mult(state)); + + // Innovation covariance S = H * P * H^T + R + SimpleMatrix s = h.mult(covariance).mult(h.transpose()).plus(r); + // Kalman gain K = P * H^T * S^{-1} + SimpleMatrix k = covariance.mult(h.transpose()).mult(s.invert()); + + // State update: x = x + K * innovation + state = state.plus(k.mult(innovation)); + + // Joseph-form covariance update for numerical stability: P = (I-KH)*P*(I-KH)^T + K*R*K^T + SimpleMatrix i = SimpleMatrix.identity(3); + SimpleMatrix iMinusKh = i.minus(k.mult(h)); + covariance = iMinusKh.mult(covariance).mult(iMinusKh.transpose()) + .plus(k.mult(r).mult(k.transpose())); + } + + /** + * Resets the position component of the state and relaxes positional uncertainty. + * Heading is preserved unchanged. Use this when a reliable absolute position fix is + * available after an extended period of dead reckoning. + * + * @param newX new East position in metres. + * @param newY new North position in metres. + */ + public synchronized void resetState(double newX, double newY) { + state.set(0, newX); + state.set(1, newY); + + covariance = new SimpleMatrix(3, 3, true, new double[]{ + 1.0, 0, 0, + 0, 1.0, 0, + 0, 0, 0.2 + }); + } + + /** + * Directly sets the position components of the state without modifying the covariance. + * Prefer {@link #resetState} when also resetting positional uncertainty. + * + * @param newX new East position in metres. + * @param newY new North position in metres. + */ + public synchronized void setPosition(double newX, double newY) { + state.set(0, newX); + state.set(1, newY); + } + + /** + * Directly sets the heading component of the state (normalised to [-π, π]). + * + * @param headingRad heading in radians (0 = north, clockwise positive). + */ + public synchronized void setHeading(double headingRad) { + state.set(2, wrapAngle(headingRad)); + } + + /** + * Returns the current estimated East position in metres (local ENU frame). + * + * @return East coordinate in metres. + */ + public synchronized double getX() { + return state.get(0); + } + + /** + * Returns the current estimated North position in metres (local ENU frame). + * + * @return North coordinate in metres. + */ + public synchronized double getY() { + return state.get(1); + } + + /** + * Returns the current estimated heading in radians. + * + * @return heading in radians, normalised to [-π, π] (0 = north, clockwise positive). + */ + public synchronized double getTheta() { + return state.get(2); + } + + /** + * Returns a defensive copy of the current state vector {@code [x, y, theta]}. + * + * @return a new {@link SimpleMatrix} containing the current state. + */ + public synchronized SimpleMatrix getStateCopy() { + return state.copy(); + } + + /** + * Returns a defensive copy of the current state error covariance matrix P (3×3). + * + * @return a new {@link SimpleMatrix} containing the current covariance. + */ + public synchronized SimpleMatrix getCovarianceCopy() { + return covariance.copy(); + } + + /** + * Wraps an angle in radians to the range [-π, π]. + * + * @param angle angle in radians, any value. + * @return equivalent angle in [-π, π]. + */ + private static double wrapAngle(double angle) { + while (angle > Math.PI) angle -= 2.0 * Math.PI; + while (angle < -Math.PI) angle += 2.0 * Math.PI; + return angle; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/FusionManager.java b/app/src/main/java/com/openpositioning/PositionMe/utils/FusionManager.java new file mode 100644 index 00000000..b22c6b1d --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/FusionManager.java @@ -0,0 +1,681 @@ +package com.openpositioning.PositionMe.utils; + +import android.util.Log; + +import com.google.android.gms.maps.model.LatLng; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Backward-compatible facade that routes updates into CascadedFusionManager. + */ +public class FusionManager { + + private static final String TAG = "FusionManager"; + // WiFi API positioning accuracy indoors is typically 3-10 m; a lower score means + // the EKF trusts it less and doesn't snap the trajectory sideways on each 5-second update. + private static final double WIFI_OBSERVATION_SCORE = 0.45; + // Indoor GNSS accuracy is usually 10-30 m; capping the score keeps it from pulling + // the trajectory away from a good PDR estimate. + private static final double GNSS_MIN_SCORE = 0.12; + private static final double GNSS_MAX_SCORE = 0.20; + // Tighter outlier rejection: a jump > 6 m between consecutive fixes is almost certainly + // an indoor GNSS multipath artefact, not real movement. + private static final double GNSS_OUTLIER_REJECT_DIST_M = 6.0; + private static final double WIFI_OUTLIER_REJECT_DIST_M = 5.0; + + // ---- WiFi-based continuous heading correction ---- + // Minimum displacement (metres) between WiFi fixes before inferring heading + private static final double HEADING_CORR_MIN_DISP_M = 4.0; + // Maximum correction per WiFi heading update (radians) to prevent sudden heading jumps + private static final double HEADING_CORR_MAX_RAD = Math.toRadians(8.0); + // Reject WiFi heading innovations that are too large to be reliable indoors. + private static final double HEADING_CORR_MAX_INNOV_RAD = Math.toRadians(20.0); + // Require WiFi/PDR displacement magnitudes to be broadly consistent. + private static final double HEADING_CORR_MIN_DISP_RATIO = 0.5; + private static final double HEADING_CORR_MAX_DISP_RATIO = 1.8; + // EMA gain for heading offset correction (lower = smoother, higher = faster convergence) + private static final double HEADING_CORR_GAIN = 0.10; + + // WiFi fingerprint training gates + // Require enough APs to avoid training with sparse/noisy scans. + private static final int MIN_WIFI_FINGERPRINT_APS = 6; + // Add fingerprints only when the user has moved enough to increase spatial diversity. + private static final double MIN_WIFI_FINGERPRINT_SPACING_M = 1.5; + // Also enforce a time interval to avoid flooding the DB with near-duplicates. + private static final long MIN_WIFI_FINGERPRINT_INTERVAL_MS = 2000; + + private final CascadedFusionManager cascadedFusionManager = new CascadedFusionManager(); + + private float lastFloorAltitude = Float.NaN; + private float accumulatedHeightChange = 0f; + private int currentFloor = 0; + + private boolean initialized = false; + + private List> pendingWallPolylines = null; + private int pendingFloorHint = 0; + + private LatLng lastEstimate = null; + + public enum PositionSource { PDR, GNSS, WIFI, FUSED } + private PositionSource lastSource = PositionSource.PDR; + + private float lastPdrX = 0f; + private float lastPdrY = 0f; + private double lastHeadingRad = Double.NaN; + private boolean firstStepHeadingAligned = false; + private boolean headingOffsetCalibrated = false; + private double headingOffsetRad = 0.0; + + // WiFi heading correction state: track WiFi positions to infer actual heading + private double[] lastWifiEN = null; // Last WiFi position in EastNorth (for heading inference) + private double pdrDxSinceWifi = 0.0; // Accumulated PDR east displacement since last WiFi heading fix + private double pdrDySinceWifi = 0.0; // Accumulated PDR north displacement since last WiFi heading fix + private int stepsSinceWifiHeadingFix = 0; // Steps taken since last WiFi-based heading correction + private double[] lastFingerprintEN = null; + private long lastFingerprintAddMs = 0L; + + // User-anchor mode + // After the user manually sets their start position, indoor GPS is still unreliable + // (typically 5-20 m off, biased toward windows/exits). For the first + // USER_ANCHOR_DURATION_MS we use a tighter outlier-rejection distance so that + // GPS cannot pull the trajectory back to its (wrong) outdoor-biased position. + private long userAnchorStartMs = 0; + private static final long USER_ANCHOR_DURATION_MS = 60_000; // 60 seconds + // During anchor mode a GNSS fix more than this many metres from the current + // EKF estimate is rejected as an outlier (vs. the normal GNSS_OUTLIER_REJECT_DIST_M). + private static final double USER_ANCHOR_GNSS_OUTLIER_M = 2.5; + + public FusionManager() { + } + + /** + * Activate "user-anchor" mode. + * + *

Call this immediately after the user confirms their starting position on the map. + * For the next {@value #USER_ANCHOR_DURATION_MS} ms, any GNSS fix that is more than + * {@value #USER_ANCHOR_GNSS_OUTLIER_M} m from the current EKF estimate is rejected. + * This prevents indoor GPS (typically biased 5-20 m toward windows/exits) from pulling + * the trajectory back to the wrong position after the user has manually placed themselves.

+ */ + public void setUserAnchor() { + userAnchorStartMs = System.currentTimeMillis(); + Log.d(TAG, "User anchor set – tight GNSS rejection active for " + + USER_ANCHOR_DURATION_MS / 1000 + " s"); + } + + /** + * Initialises the fusion engine with a known starting position and heading. + * Must be called before any update methods. Resets all internal state and delegates + * to the underlying {@link CascadedFusionManager}. + * + * @param latDeg starting latitude in decimal degrees (WGS-84). + * @param lngDeg starting longitude in decimal degrees (WGS-84). + * @param accuracy estimated horizontal accuracy of the starting fix in metres (unused + * internally but provided for API consistency with sensor callbacks). + * @param headingRad initial heading in radians (0 = north, clockwise positive). + * @param floor starting floor number (0 = ground level). + */ + public void initialize(double latDeg, double lngDeg, float accuracy, + double headingRad, int floor) { + cascadedFusionManager.initialize(latDeg, lngDeg, headingRad); + + lastFloorAltitude = Float.NaN; + accumulatedHeightChange = 0f; + currentFloor = floor; + initialized = true; + lastEstimate = new LatLng(latDeg, lngDeg); + lastPdrX = 0f; + lastPdrY = 0f; + lastHeadingRad = headingRad; + firstStepHeadingAligned = false; + headingOffsetCalibrated = false; + headingOffsetRad = 0.0; + lastWifiEN = null; + pdrDxSinceWifi = 0.0; + pdrDySinceWifi = 0.0; + stepsSinceWifiHeadingFix = 0; + + applyPendingWallMapIfAny(); + + Log.d(TAG, String.format("Initialized at (%.6f, %.6f) floor=%d", latDeg, lngDeg, floor)); + } + + /** + * Notifies the manager that the Nucleus building map has been loaded for the given floor. + * Updates the internal floor tracker so that subsequent position estimates are associated + * with the correct floor level. + * + * @param floor the floor number for which the Nucleus map was loaded (0 = ground level). + */ + public void loadNucleusMap(int floor) { + currentFloor = floor; + Log.d(TAG, "Loaded Nucleus map for floor " + floor); + } + + /** + * Legacy no-op retained for API backwards compatibility. + * Map wall geometry is now supplied via {@link #configureDynamicWallMap} instead. + * + * @param matcher ignored; pass {@code null} to document intent. + * @deprecated Use {@link #configureDynamicWallMap(java.util.List, int)} to supply wall data. + */ + @Deprecated + public void setMapMatcher(MapMatcher matcher) { + // Legacy no-op: map walls are now passed via configureDynamicWallMap. + } + + /** + * Converts a set of geographic wall polylines into ENU wall segments and passes them to + * the underlying {@link CascadedFusionManager} for map-constrained particle filtering. + * + *

If the filter has not yet been initialised, the wall data is buffered and applied + * automatically once {@link #initialize} is called. + * + * @param wallPolylines list of polylines, each represented as an ordered list of + * {@link com.google.android.gms.maps.model.LatLng} vertices. + * Consecutive vertex pairs define wall segments. + * @param floorHint floor number that the supplied walls belong to. + * @return number of wall segments successfully loaded, or 0 if the filter + * is not yet ready and the data was buffered instead. + */ + public int configureDynamicWallMap(List> wallPolylines, int floorHint) { + if (wallPolylines == null || wallPolylines.isEmpty()) { + return 0; + } + + if (!initialized || !cascadedFusionManager.isInitialized()) { + pendingWallPolylines = copyWallPolylines(wallPolylines); + pendingFloorHint = floorHint; + return 0; + } + + CoordinateConverter converter = cascadedFusionManager.getConverter(); + if (converter == null) { + return 0; + } + + int segmentCount = 0; + List walls = new ArrayList<>(); + for (List wall : wallPolylines) { + if (wall == null || wall.size() < 2) { + continue; + } + for (int i = 1; i < wall.size(); i++) { + LatLng start = wall.get(i - 1); + LatLng end = wall.get(i); + double[] en1 = converter.toEastNorth(start.latitude, start.longitude); + double[] en2 = converter.toEastNorth(end.latitude, end.longitude); + walls.add(new MapConstrainedPF.Wall(en1[0], en1[1], en2[0], en2[1])); + segmentCount++; + } + } + + if (segmentCount > 0) { + cascadedFusionManager.setWalls(walls); + currentFloor = floorHint; + Log.d(TAG, "Applied dynamic wall map, segments=" + segmentCount + ", floor=" + floorHint); + } + return segmentCount; + } + + /** + * Applies any wall map data that was buffered before initialisation. + * Called internally by {@link #initialize} once the filter is ready to accept wall data. + */ + private void applyPendingWallMapIfAny() { + if (pendingWallPolylines == null || pendingWallPolylines.isEmpty()) { + return; + } + configureDynamicWallMap(pendingWallPolylines, pendingFloorHint); + pendingWallPolylines = null; + } + + /** + * Creates a defensive deep copy of a list of wall polylines. + * Used to safely buffer pending wall data without holding a reference to the caller's list. + * + * @param source the original list of polylines to copy; {@code null} inner lists are skipped. + * @return a new list containing independent copies of each non-null inner polyline list. + */ + private static List> copyWallPolylines(List> source) { + List> copy = new ArrayList<>(); + for (List wall : source) { + if (wall == null) { + continue; + } + copy.add(new ArrayList<>(wall)); + } + return copy; + } + + /** + * Feeds the latest Pedestrian Dead Reckoning (PDR) output into the fusion engine. + * + *

The incremental displacement since the previous PDR update is extracted and passed + * to the {@link CascadedFusionManager} as a step-and-turn motion command. On the very + * first step, the EKF heading is aligned to the sensor heading to resolve the initial + * heading ambiguity. A continuously updated heading offset (derived from WiFi corrections) + * is applied to compensate for gyroscope drift. + * + * @param pdrX cumulative PDR East displacement in metres (relative to recording start). + * @param pdrY cumulative PDR North displacement in metres (relative to recording start). + * @param headingRad current heading from the sensor fusion in radians + * (0 = north, clockwise positive). + * @param stepLenM step length for the current step in metres; if ≤ 0, the magnitude of + * the PDR displacement is used instead. + * @param dtMs elapsed time since the previous PDR update in milliseconds (reserved + * for future use in velocity-based motion models). + */ + public void updateWithPDR(float pdrX, float pdrY, double headingRad, + float stepLenM, long dtMs) { + if (!initialized) return; + + double dx = pdrX - lastPdrX; + double dy = pdrY - lastPdrY; + double moveDistance = Math.hypot(dx, dy); + double step = stepLenM; + if (step <= 0f) { + step = moveDistance; + } + + // Accumulate PDR displacement for WiFi heading correction + pdrDxSinceWifi += dx; + pdrDySinceWifi += dy; + stepsSinceWifiHeadingFix++; + + // Initial heading alignment (first step only, NOT one-shot calibration) + if (!firstStepHeadingAligned && Double.isFinite(headingRad)) { + cascadedFusionManager.alignHeading(headingRad); + firstStepHeadingAligned = true; + lastHeadingRad = headingRad; + } + + // Apply current heading offset (continuously updated by WiFi corrections) + double usedHeading = headingRad; + if (Double.isFinite(usedHeading) && headingOffsetCalibrated) { + usedHeading = wrapAngle(usedHeading + headingOffsetRad); + } + double deltaTheta = 0.0; + if (Double.isFinite(lastHeadingRad) && Double.isFinite(usedHeading)) { + deltaTheta = wrapAngle(usedHeading - lastHeadingRad); + } + lastHeadingRad = usedHeading; + + if (step > 0.0) { + cascadedFusionManager.onStepDetected(step, deltaTheta); + } + + lastPdrX = pdrX; + lastPdrY = pdrY; + lastEstimate = cascadedFusionManager.getEstimatedLatLng(); + lastSource = PositionSource.PDR; + } + + /** + * Called when a WiFi absolute position is available. + * In addition to updating the EKF/PF, this also infers heading by comparing + * the WiFi displacement direction with the PDR displacement direction. + * + *

Key insight: if the user walks straight north but PDR says north-east + * (due to heading bias), the WiFi displacement will point north while the + * PDR displacement points north-east. The angular difference is the heading + * bias, which we correct gradually.

+ */ + private void inferHeadingFromWifi(double wifiEast, double wifiNorth) { + if (lastWifiEN == null) { + // First WiFi fix – just record position, no heading inference yet + lastWifiEN = new double[]{wifiEast, wifiNorth}; + pdrDxSinceWifi = 0.0; + pdrDySinceWifi = 0.0; + stepsSinceWifiHeadingFix = 0; + return; + } + + // WiFi displacement since last heading fix + double wifiDx = wifiEast - lastWifiEN[0]; + double wifiDy = wifiNorth - lastWifiEN[1]; + double wifiDisp = Math.hypot(wifiDx, wifiDy); + double pdrDisp = Math.hypot(pdrDxSinceWifi, pdrDySinceWifi); + + // Need sufficient displacement for reliable heading inference + // Also need at least 3 steps to smooth out WiFi jitter + if (wifiDisp < HEADING_CORR_MIN_DISP_M || pdrDisp < HEADING_CORR_MIN_DISP_M + || stepsSinceWifiHeadingFix < 3) { + return; + } + + // Displacement consistency gate: if WiFi and PDR travelled very different + // distances over the same interval, WiFi direction is likely noisy. + double dispRatio = wifiDisp / Math.max(1e-6, pdrDisp); + if (dispRatio < HEADING_CORR_MIN_DISP_RATIO || dispRatio > HEADING_CORR_MAX_DISP_RATIO) { + return; + } + + // Infer heading from WiFi displacement vs PDR displacement + // WiFi heading = direction the user ACTUALLY moved + // PDR heading = direction PDR THINKS the user moved + double wifiHeading = wrapAngle(Math.atan2(wifiDx, wifiDy)); // EN → heading(0=N) + double pdrHeading = wrapAngle(Math.atan2(pdrDxSinceWifi, pdrDySinceWifi)); + + // The difference is the heading bias: how much PDR is off + double headingError = wrapAngle(wifiHeading - pdrHeading); + + // Innovation gate: ignore implausibly large heading disagreements from noisy WiFi. + if (Math.abs(headingError) > HEADING_CORR_MAX_INNOV_RAD) { + return; + } + + // Clamp to prevent a single jittery WiFi fix from causing a large heading jump + double clampedError = Math.max(-HEADING_CORR_MAX_RAD, + Math.min(HEADING_CORR_MAX_RAD, headingError)); + + // EMA update of the heading offset + if (!headingOffsetCalibrated) { + // First correction: apply strongly + headingOffsetRad = clampedError; + headingOffsetCalibrated = true; + } else { + headingOffsetRad = headingOffsetRad + HEADING_CORR_GAIN * wrapAngle(clampedError - headingOffsetRad); + } + + // Reset accumulators for next interval + lastWifiEN = new double[]{wifiEast, wifiNorth}; + pdrDxSinceWifi = 0.0; + pdrDySinceWifi = 0.0; + stepsSinceWifiHeadingFix = 0; + } + + /** + * Feeds a GNSS absolute position fix into the fusion engine. + * + *

Outlier rejection is applied before the fix reaches the EKF: + *

    + *
  • During user-anchor mode (first {@value #USER_ANCHOR_DURATION_MS} ms after + * {@link #setUserAnchor} is called), fixes further than + * {@value #USER_ANCHOR_GNSS_OUTLIER_M} m from the current estimate are rejected.
  • + *
  • Otherwise, fixes further than {@value #GNSS_OUTLIER_REJECT_DIST_M} m are rejected.
  • + *
+ * The observation score passed to the EKF is derived from accuracy via an exponential + * decay function, then clamped to [{@value #GNSS_MIN_SCORE}, {@value #GNSS_MAX_SCORE}] + * to prevent GNSS from overriding a good PDR trajectory when indoors. + * + * @param latDeg measured latitude in decimal degrees. + * @param lngDeg measured longitude in decimal degrees. + * @param accuracy horizontal accuracy of the fix in metres as reported by the OS. + */ + public void updateWithGNSS(double latDeg, double lngDeg, float accuracy) { + if (!initialized) return; + + LatLng current = cascadedFusionManager.getEstimatedLatLng(); + if (current != null) { + double jumpM = distanceMeters(current.latitude, current.longitude, latDeg, lngDeg); + + // During user-anchor mode use a much tighter outlier threshold so that the + // indoor GPS (biased toward the nearest window/exit) cannot drag the trajectory + // back to its (wrong) position after the user has manually placed themselves. + boolean inAnchorMode = userAnchorStartMs > 0 + && (System.currentTimeMillis() - userAnchorStartMs) < USER_ANCHOR_DURATION_MS; + double rejectDist = inAnchorMode ? USER_ANCHOR_GNSS_OUTLIER_M : GNSS_OUTLIER_REJECT_DIST_M; + + if (jumpM > rejectDist) { + Log.w(TAG, String.format("GNSS rejected: %.1fm > %.1fm (anchor=%b)", + jumpM, rejectDist, inAnchorMode)); + return; + } + } + + double safeAcc = Math.max(0.0f, accuracy); + double gnssScore = Math.exp(-safeAcc / 12.0); + gnssScore = Math.max(GNSS_MIN_SCORE, Math.min(GNSS_MAX_SCORE, gnssScore)); + cascadedFusionManager.onAbsoluteObservationLatLng(latDeg, lngDeg, gnssScore); + + lastEstimate = cascadedFusionManager.getEstimatedLatLng(); + lastSource = PositionSource.GNSS; + } + + /** + * Feeds a WiFi API absolute position estimate into the fusion engine. + * + *

Fixes that jump more than {@value #WIFI_OUTLIER_REJECT_DIST_M} m from the current + * EKF estimate are discarded as outliers (typical indoor WiFi API multipath artefacts). + * Accepted fixes are also used to infer heading correction via + * {@link #inferHeadingFromWifi}, then forwarded to the EKF with a fixed observation + * score of {@value #WIFI_OBSERVATION_SCORE}. + * + * @param latDeg estimated latitude from the WiFi positioning API, in decimal degrees. + * @param lngDeg estimated longitude from the WiFi positioning API, in decimal degrees. + */ + public void updateWithWifi(double latDeg, double lngDeg) { + if (!initialized || !cascadedFusionManager.isInitialized()) return; + + LatLng current = cascadedFusionManager.getEstimatedLatLng(); + if (current != null) { + double jumpM = distanceMeters(current.latitude, current.longitude, latDeg, lngDeg); + if (jumpM > WIFI_OUTLIER_REJECT_DIST_M) { + Log.w(TAG, String.format("WiFi API outlier rejected: %.1fm jump", jumpM)); + return; + } + } + + // Infer heading correction from WiFi displacement before updating position + CoordinateConverter conv = cascadedFusionManager.getConverter(); + if (conv != null) { + double[] en = conv.toEastNorth(latDeg, lngDeg); + inferHeadingFromWifi(en[0], en[1]); + } + + double wifiScore = WIFI_OBSERVATION_SCORE; + cascadedFusionManager.onAbsoluteObservationLatLng(latDeg, lngDeg, wifiScore); + lastEstimate = cascadedFusionManager.getEstimatedLatLng(); + lastSource = PositionSource.WIFI; + } + + /** + * Processes a raw WiFi RSSI scan for fingerprint-based positioning. + * + *

The scan is first matched against the existing fingerprint database using the + * WKNN predictor. If the match confidence (score) is at least 0.08, the resulting + * position estimate is accepted and the last source is updated to {@code WIFI}. + * The scan is then passed to {@link #maybeAddWifiFingerprint} to grow the radio map, + * subject to spatial and temporal gating to avoid near-duplicate fingerprints. + * + * @param currentScan map of WiFi BSSID (access point MAC address) to RSSI value in dBm + * for all access points detected in this scan. + */ + public void updateWithWifiScan(Map currentScan) { + if (!initialized || !cascadedFusionManager.isInitialized() || currentScan == null || currentScan.isEmpty()) { + return; + } + + // Predict first using existing fingerprint DB. + // Previously we inserted currentScan before prediction, which made WKNN + // self-match the just-added sample and produced little/no corrective effect. + WknnPredictor.WknnResult result = cascadedFusionManager.onWifiScanned(currentScan); + if (result != null && result.score >= 0.08) { + lastSource = PositionSource.WIFI; + lastEstimate = cascadedFusionManager.getEstimatedLatLng(); + } + + // Then update fingerprint DB for future scans, but only with spatial/temporal gating + // to build a useful radio-map instead of near-duplicate clusters. + maybeAddWifiFingerprint(currentScan); + } + + /** + * Conditionally adds the current WiFi scan to the fingerprint database. + * + *

A fingerprint is only added when both spatial and temporal gates are satisfied: + *

    + *
  • At least {@value #MIN_WIFI_FINGERPRINT_APS} access points are visible (sparse + * scans are too noisy to be useful reference points).
  • + *
  • At least {@value #MIN_WIFI_FINGERPRINT_INTERVAL_MS} ms have elapsed since the + * last fingerprint was added.
  • + *
  • The current EKF position is at least {@value #MIN_WIFI_FINGERPRINT_SPACING_M} m + * from the position of the last fingerprint (ensures spatial diversity).
  • + *
+ * + * @param currentScan map of BSSID to RSSI (dBm) for the current WiFi scan. + */ + private void maybeAddWifiFingerprint(Map currentScan) { + if (currentScan.size() < MIN_WIFI_FINGERPRINT_APS) { + return; + } + + double ekfX = cascadedFusionManager.getEkfX(); + double ekfY = cascadedFusionManager.getEkfY(); + long nowMs = System.currentTimeMillis(); + + boolean intervalOk = (nowMs - lastFingerprintAddMs) >= MIN_WIFI_FINGERPRINT_INTERVAL_MS; + boolean spacingOk = lastFingerprintEN == null + || Math.hypot(ekfX - lastFingerprintEN[0], ekfY - lastFingerprintEN[1]) >= MIN_WIFI_FINGERPRINT_SPACING_M; + + if (!intervalOk || !spacingOk) { + return; + } + + cascadedFusionManager.addFingerprintFromCurrentEstimate(currentScan); + lastFingerprintEN = new double[]{ekfX, ekfY}; + lastFingerprintAddMs = nowMs; + } + + /** + * Feeds a barometric altitude reading into the floor-change detector. + * + *

Altitude changes are accumulated incrementally. When the accumulated change exceeds + * 3.0 m, a floor transition is inferred by dividing by the assumed inter-floor height + * (3.5 m) and rounding to the nearest integer. The accumulator is then reset. + * + * @param altitudeM barometric altitude in metres above sea level, as derived from + * the device pressure sensor using the standard atmosphere model. + */ + public void updateBarometer(float altitudeM) { + if (!initialized) return; + + if (Float.isNaN(lastFloorAltitude)) { + lastFloorAltitude = altitudeM; + return; + } + + accumulatedHeightChange += (altitudeM - lastFloorAltitude); + lastFloorAltitude = altitudeM; + + if (Math.abs(accumulatedHeightChange) >= 3.0f) { + int floorDelta = Math.round(accumulatedHeightChange / 3.5f); + if (floorDelta != 0) { + currentFloor += floorDelta; + } + accumulatedHeightChange = 0f; + } + } + + /** + * Returns the current best-estimate position from the fusion engine. + * Prefers the live {@link CascadedFusionManager} estimate when available; + * falls back to the last cached estimate otherwise. + * + * @return current estimated {@link LatLng}, or {@code null} if the manager has not + * been initialised yet. + */ + public LatLng getEstimatedPosition() { + if (cascadedFusionManager.isInitialized()) { + return cascadedFusionManager.getEstimatedLatLng(); + } + return lastEstimate; + } + + /** + * Returns the current estimated floor number. + * Updated by {@link #updateBarometer} when a significant altitude change is detected, + * and also set explicitly by {@link #initialize} and {@link #loadNucleusMap}. + * + * @return floor number, where 0 represents the ground level. + */ + public int getCurrentFloor() { + return currentFloor; + } + + /** + * Returns the positioning source that produced the most recent estimate update. + * + * @return one of {@link PositionSource#PDR}, {@link PositionSource#GNSS}, + * {@link PositionSource#WIFI}, or {@link PositionSource#FUSED}. + */ + public PositionSource getLastSource() { return lastSource; } + + /** + * Returns whether both this manager and the underlying {@link CascadedFusionManager} + * have been successfully initialised and are ready to produce position estimates. + * + * @return {@code true} if {@link #initialize} has been called and the cascaded manager + * is also ready; {@code false} otherwise. + */ + public boolean isInitialized() { return initialized && cascadedFusionManager.isInitialized(); } + + /** + * Resets the fusion engine to its uninitialised state. + * Clears all accumulated position estimates, heading corrections, WiFi fingerprint + * state, and floor tracking. {@link #initialize} must be called again before further + * update methods are invoked. + */ + public void reset() { + cascadedFusionManager.reset(); + lastFloorAltitude = Float.NaN; + accumulatedHeightChange = 0f; + currentFloor = 0; + initialized = false; + lastEstimate = null; + lastPdrX = 0f; + lastPdrY = 0f; + lastHeadingRad = Double.NaN; + firstStepHeadingAligned = false; + headingOffsetCalibrated = false; + headingOffsetRad = 0.0; + lastSource = PositionSource.PDR; + userAnchorStartMs = 0; + lastWifiEN = null; + pdrDxSinceWifi = 0.0; + pdrDySinceWifi = 0.0; + stepsSinceWifiHeadingFix = 0; + lastFingerprintEN = null; + lastFingerprintAddMs = 0L; + } + + /** + * Computes the great-circle distance between two geographic coordinates using the + * Haversine formula. + * + * @param lat1 latitude of the first point in decimal degrees. + * @param lon1 longitude of the first point in decimal degrees. + * @param lat2 latitude of the second point in decimal degrees. + * @param lon2 longitude of the second point in decimal degrees. + * @return distance between the two points in metres. + */ + private static double distanceMeters(double lat1, double lon1, double lat2, double lon2) { + double r = 6371000.0; + double phi1 = Math.toRadians(lat1); + double phi2 = Math.toRadians(lat2); + double dPhi = Math.toRadians(lat2 - lat1); + double dLambda = Math.toRadians(lon2 - lon1); + double a = Math.sin(dPhi / 2.0) * Math.sin(dPhi / 2.0) + + Math.cos(phi1) * Math.cos(phi2) + * Math.sin(dLambda / 2.0) * Math.sin(dLambda / 2.0); + double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a)); + return r * c; + } + + /** + * Wraps an angle in radians to the range [-π, π]. + * + * @param angle angle in radians, any value. + * @return equivalent angle normalised to [-π, π]. + */ + private static double wrapAngle(double angle) { + while (angle > Math.PI) angle -= 2.0 * Math.PI; + while (angle < -Math.PI) angle += 2.0 * Math.PI; + return angle; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/MapConstrainedPF.java b/app/src/main/java/com/openpositioning/PositionMe/utils/MapConstrainedPF.java new file mode 100644 index 00000000..a62bfe5a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/MapConstrainedPF.java @@ -0,0 +1,331 @@ +package com.openpositioning.PositionMe.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +/** + * Particle filter with hard wall-crossing constraints. + */ +public class MapConstrainedPF { + + public static class Wall { + public final double x1; + public final double y1; + public final double x2; + public final double y2; + + public Wall(double x1, double y1, double x2, double y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + } + + public static class Particle { + double x; + double y; + double theta; + double weight; + + Particle(double x, double y, double theta, double weight) { + this.x = x; + this.y = y; + this.theta = theta; + this.weight = weight; + } + + Particle copy() { + return new Particle(x, y, theta, weight); + } + } + + private static final int NUM_PARTICLES = 200; + private static final double DEFAULT_OBS_SIGMA = 2.0; + private static final double STEP_NOISE_SIGMA = 0.10; + private static final double THETA_NOISE_SIGMA = Math.toRadians(12.0); + private static final double RESAMPLE_JITTER = 0.03; + private static final double EPS = 1e-12; + + private final Random random = new Random(); + private final List particles = new ArrayList<>(NUM_PARTICLES); + private List walls = new ArrayList<>(); + private double obsSigma = DEFAULT_OBS_SIGMA; + + public MapConstrainedPF(List walls) { + if (walls != null) { + this.walls = new ArrayList<>(walls); + } + reset(0.0, 0.0, 0.0); + } + + public synchronized void setWalls(List walls) { + this.walls = (walls == null) ? new ArrayList<>() : new ArrayList<>(walls); + } + + public synchronized void setObservationSigma(double sigma) { + this.obsSigma = Math.max(0.3, sigma); + } + + public synchronized void setHeading(double headingRad) { + double wrapped = wrapAngle(headingRad); + for (Particle p : particles) { + p.theta = wrapped + random.nextGaussian() * Math.toRadians(2.0); + } + } + + public synchronized void reset(double x, double y, double theta) { + particles.clear(); + double w = 1.0 / NUM_PARTICLES; + for (int i = 0; i < NUM_PARTICLES; i++) { + double px = x + random.nextGaussian() * 0.2; + double py = y + random.nextGaussian() * 0.2; + double pt = theta + random.nextGaussian() * Math.toRadians(15.0); + particles.add(new Particle(px, py, wrapAngle(pt), w)); + } + } + + /** + * Force-relocate the particle cloud around a trusted absolute observation. + * This is used when WiFi/GNSS repeatedly indicates the user is far from the + * current particle cloud (e.g., at building exits where map constraints can trap particles). + */ + public synchronized void relocalize(double x, double y, double theta) { + reset(x, y, theta); + } + + public synchronized void predict(double stepLen, double deltaTheta) { + for (Particle p : particles) { + if (p.weight <= 0.0) { + continue; + } + + double oldX = p.x; + double oldY = p.y; + + double noisyStep = Math.max(0.0, stepLen + random.nextGaussian() * STEP_NOISE_SIGMA); + double noisyDelta = deltaTheta + random.nextGaussian() * THETA_NOISE_SIGMA; + double nextTheta = wrapAngle(p.theta + noisyDelta); + + // Android azimuth convention (0=north, clockwise positive): + // east = step * sin(theta), north = step * cos(theta) + double newX = p.x + noisyStep * Math.sin(nextTheta); + double newY = p.y + noisyStep * Math.cos(nextTheta); + + boolean hitWall = false; + for (Wall wall : walls) { + if (segmentsIntersect(oldX, oldY, newX, newY, wall.x1, wall.y1, wall.x2, wall.y2)) { + hitWall = true; + break; + } + } + + if (hitWall) { + p.weight *= 0.1; // Severely penalize but do not insta-kill + p.theta = nextTheta; // Allow particle to turn away from wall next step + continue; // Cancel the forward step + } + + p.x = newX; + p.y = newY; + p.theta = nextTheta; + } + } + + public synchronized void update(double obsX, double obsY, double score) { + double safeScore = Math.max(0.05, Math.min(1.0, score)); + double dynamicSigma = obsSigma / safeScore; + double denom = 2.0 * dynamicSigma * dynamicSigma; + for (Particle p : particles) { + if (p.weight <= 0.0) { + continue; + } + double dx = p.x - obsX; + double dy = p.y - obsY; + double dist2 = dx * dx + dy * dy; + p.weight = p.weight * Math.exp(-dist2 / denom); + } + + normalizeWeights(); + rouletteWheelResample(); + } + + public synchronized double getX() { + double[] mean = weightedMean(); + return mean[0]; + } + + public synchronized double getY() { + double[] mean = weightedMean(); + return mean[1]; + } + + public synchronized double getConfidence() { + double[] mean = weightedMean(); + double mx = mean[0]; + double my = mean[1]; + + double wsum = 0.0; + double var = 0.0; + for (Particle p : particles) { + if (p.weight <= 0.0) continue; + double dx = p.x - mx; + double dy = p.y - my; + var += p.weight * (dx * dx + dy * dy); + wsum += p.weight; + } + + if (wsum <= EPS) { + return Double.POSITIVE_INFINITY; + } + return var / wsum; + } + + private double[] weightedMean() { + double x = 0.0; + double y = 0.0; + double wsum = 0.0; + for (Particle p : particles) { + if (p.weight <= 0.0) continue; + x += p.weight * p.x; + y += p.weight * p.y; + wsum += p.weight; + } + if (wsum <= EPS) { + double uwx = 0.0, uwy = 0.0; + for (Particle p : particles) { + uwx += p.x; + uwy += p.y; + } + if (particles.isEmpty()) return new double[]{0.0, 0.0}; + return new double[]{uwx / particles.size(), uwy / particles.size()}; + } + return new double[]{x / wsum, y / wsum}; + } + + private void normalizeWeights() { + double sum = 0.0; + for (Particle p : particles) { + sum += Math.max(0.0, p.weight); + } + + if (sum <= EPS) { + double w = 1.0 / NUM_PARTICLES; + for (Particle p : particles) { + p.weight = w; + } + return; + } + + for (Particle p : particles) { + p.weight = Math.max(0.0, p.weight) / sum; + } + } + + private void rouletteWheelResample() { + List alive = new ArrayList<>(); + for (Particle p : particles) { + if (p.weight > 0.0) { + alive.add(p); + } + } + + if (alive.isEmpty()) { + double[] center = weightedMean(); + reset(center[0], center[1], 0.0); + return; + } + + double[] cumulative = new double[alive.size()]; + cumulative[0] = alive.get(0).weight; + for (int i = 1; i < alive.size(); i++) { + cumulative[i] = cumulative[i - 1] + alive.get(i).weight; + } + double total = cumulative[cumulative.length - 1]; + if (total <= EPS) { + Collections.shuffle(alive, random); + double w = 1.0 / NUM_PARTICLES; + particles.clear(); + for (int i = 0; i < NUM_PARTICLES; i++) { + Particle seed = alive.get(i % alive.size()).copy(); + seed.weight = w; + particles.add(seed); + } + return; + } + + List resampled = new ArrayList<>(NUM_PARTICLES); + for (int i = 0; i < NUM_PARTICLES; i++) { + double r = random.nextDouble() * total; + int idx = lowerBound(cumulative, r); + Particle base = alive.get(idx); + Particle np = new Particle( + base.x + random.nextGaussian() * RESAMPLE_JITTER, + base.y + random.nextGaussian() * RESAMPLE_JITTER, + wrapAngle(base.theta + random.nextGaussian() * Math.toRadians(5.0)), + 1.0 / NUM_PARTICLES); + resampled.add(np); + } + + particles.clear(); + particles.addAll(resampled); + } + + private int lowerBound(double[] arr, double target) { + int lo = 0; + int hi = arr.length - 1; + while (lo < hi) { + int mid = lo + (hi - lo) / 2; + if (arr[mid] < target) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + } + + private static double wrapAngle(double a) { + while (a > Math.PI) a -= 2.0 * Math.PI; + while (a < -Math.PI) a += 2.0 * Math.PI; + return a; + } + + private static boolean segmentsIntersect( + double ax, double ay, double bx, double by, + double cx, double cy, double dx, double dy) { + + double o1 = cross(ax, ay, bx, by, cx, cy); + double o2 = cross(ax, ay, bx, by, dx, dy); + double o3 = cross(cx, cy, dx, dy, ax, ay); + double o4 = cross(cx, cy, dx, dy, bx, by); + + if (o1 * o2 < 0 && o3 * o4 < 0) { + return true; + } + + return isCollinearOnSegment(ax, ay, bx, by, cx, cy, o1) + || isCollinearOnSegment(ax, ay, bx, by, dx, dy, o2) + || isCollinearOnSegment(cx, cy, dx, dy, ax, ay, o3) + || isCollinearOnSegment(cx, cy, dx, dy, bx, by, o4); + } + + private static double cross(double ax, double ay, double bx, double by, double px, double py) { + return (bx - ax) * (py - ay) - (by - ay) * (px - ax); + } + + private static boolean isCollinearOnSegment( + double ax, double ay, double bx, double by, + double px, double py, double crossVal) { + if (Math.abs(crossVal) > 1e-9) { + return false; + } + double minX = Math.min(ax, bx) - 1e-9; + double maxX = Math.max(ax, bx) + 1e-9; + double minY = Math.min(ay, by) - 1e-9; + double maxY = Math.max(ay, by) + 1e-9; + return px >= minX && px <= maxX && py >= minY && py <= maxY; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatcher.java b/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatcher.java new file mode 100644 index 00000000..d9ff9ae4 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatcher.java @@ -0,0 +1,554 @@ +package com.openpositioning.PositionMe.utils; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Map Matching engine for indoor positioning. + * + *

Provides three core services:

+ *
    + *
  1. Wall penetration detection 鈥?given a movement segment in the + * local East/North plane, determines whether it crosses any registered + * wall segment. Used by fusion motion models to reject impossible + * movements through walls.
  2. + *
  3. Position correction 鈥?snaps a position that has drifted + * through a wall back to the nearest valid side of that wall.
  4. + *
  5. Floor inference 鈥?combines barometer height change with a + * motion-model heuristic to distinguish stair climbing from elevator + * riding, and updates the current floor accordingly.
  6. + *
+ * + *

Coordinate system

+ *

All positions are in the local East/North metric plane managed by + * {@link CoordinateConverter}. Wall endpoints must be supplied in the same + * coordinate system.

+ * + *

Feature types

+ *
    + *
  • {@code WALL} 鈥?impenetrable barrier; triggers {@link #correctPosition}.
  • + *
  • {@code STAIRS} 鈥?floor transition zone; floor changes only when the + * barometer height change exceeds {@link #STAIR_HEIGHT_THRESHOLD_M}.
  • + *
  • {@code LIFT} 鈥?elevator zone; floor changes without significant + * horizontal displacement (detected by low horizontal speed).
  • + *
+ * + * @author PositionMe Assessment 2 + */ +public class MapMatcher { + + private static final String TAG = "MapMatcher"; + + // Thresholds + /** + * Minimum barometric height change (metres) required to confirm a floor + * transition via stairs. One floor 鈮?3鈥? m; we use a conservative 2 m + * to account for sensor noise. + */ + public static final float STAIR_HEIGHT_THRESHOLD_M = 2.0f; + + /** + * Maximum horizontal speed (m/s) below which we consider the user to be + * stationary 鈥?a prerequisite for elevator detection. + */ + private static final float LIFT_MAX_HORIZ_SPEED = 0.3f; + + /** + * Minimum barometric height change (metres) to trigger an elevator floor + * update (same threshold as stairs but the motion model differs). + */ + private static final float LIFT_HEIGHT_THRESHOLD_M = 2.0f; + + /** Ignore very short micro-movements to reduce wall-crossing false positives. */ + private static final double MIN_WALL_CHECK_STEP_M = 0.20; + + /** Treat near-endpoint touches as non-penetration to avoid corridor lockups. */ + private static final double WALL_ENDPOINT_TOLERANCE_M = 0.35; + + /** Allow near-parallel motion along walls in narrow corridors. */ + private static final double WALL_SLIDING_DISTANCE_M = 0.45; + + // Feature types + /** Enumeration of map feature types. */ + public enum FeatureType { WALL, STAIRS, LIFT } + + // Inner classes + /** + * A line-segment feature in the local East/North plane. + */ + public static class MapFeature { + /** Feature type. */ + public final FeatureType type; + /** Start point east (metres). */ + public final double x1; + /** Start point north (metres). */ + public final double y1; + /** End point east (metres). */ + public final double x2; + /** End point north (metres). */ + public final double y2; + /** Optional label (e.g. "Wall-A", "Staircase-1"). */ + public final String label; + + public MapFeature(FeatureType type, + double x1, double y1, + double x2, double y2, + String label) { + this.type = type; + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.label = label; + } + } + + // State + private final List features = new ArrayList<>(); + + /** Current floor (0 = ground floor). */ + private int currentFloor = 0; + + /** Floor height used for barometer-based floor estimation (metres). */ + private float floorHeightM = 3.5f; + + // Constructor + /** Create an empty map matcher (add features via {@link #addFeature}). */ + public MapMatcher() {} + + /** + * Create a map matcher pre-loaded with the Nucleus building walls for the + * given floor. Coordinates are in the local East/North plane anchored at + * the Nucleus building SW corner (55.92282257掳N, 3.17459565掳W). + * + *

This is a simplified rectangular wall layout derived from the building + * boundary. Replace with actual floor-plan data for production use.

+ * + * @param floor Floor number (0 = ground, 1 = first, 鈥?. + */ + public static MapMatcher forNucleusFloor(int floor) { + MapMatcher mm = new MapMatcher(); + mm.currentFloor = floor; + mm.floorHeightM = 4.2f; // Nucleus floor height + + // Nucleus building outer boundary in local EN plane + // (anchored at SW corner: 55.92282257, -3.17459565) + // NE corner 鈮?(50, 55) m from SW corner + double W = 0, E = 50; + double S = 0, N = 55; + + // Outer walls + mm.addFeature(new MapFeature(FeatureType.WALL, W, S, E, S, "South wall")); + mm.addFeature(new MapFeature(FeatureType.WALL, E, S, E, N, "East wall")); + mm.addFeature(new MapFeature(FeatureType.WALL, E, N, W, N, "North wall")); + mm.addFeature(new MapFeature(FeatureType.WALL, W, N, W, S, "West wall")); + + // Internal corridor walls (simplified) + mm.addFeature(new MapFeature(FeatureType.WALL, 10, S, 10, 40, "Corridor-W")); + mm.addFeature(new MapFeature(FeatureType.WALL, 40, S, 40, 40, "Corridor-E")); + mm.addFeature(new MapFeature(FeatureType.WALL, 10, 40, 40, 40, "Corridor-N")); + + // Staircase zones (represented as short segments at stair locations) + mm.addFeature(new MapFeature(FeatureType.STAIRS, 5, 45, 10, 55, "Staircase-NW")); + mm.addFeature(new MapFeature(FeatureType.STAIRS, 40, 45, 50, 55, "Staircase-NE")); + + // Lift zone + mm.addFeature(new MapFeature(FeatureType.LIFT, 22, 42, 28, 48, "Lift")); + + return mm; + } + + // Feature management + /** + * Add a map feature (wall, stairs, or lift segment). + * + * @param feature Feature to add. + */ + public void addFeature(MapFeature feature) { + features.add(feature); + } + + /** Remove all features. */ + public void clearFeatures() { + features.clear(); + } + + // Wall penetration detection + /** + * Test whether the movement segment from {@code (x1,y1)} to {@code (x2,y2)} + * crosses any registered {@link FeatureType#WALL} segment. + * + *

Used by fusion motion models to reject impossible movement updates + * that would pass through walls.

+ * + * @param x1 Start east (metres). + * @param y1 Start north (metres). + * @param x2 End east (metres). + * @param y2 End north (metres). + * @return {@code true} if the segment intersects at least one wall. + */ + public boolean crossesWall(double x1, double y1, double x2, double y2) { + if (distance(x1, y1, x2, y2) < MIN_WALL_CHECK_STEP_M) { + return false; + } + + for (MapFeature f : features) { + if (f.type == FeatureType.WALL) { + if (segmentsIntersect(x1, y1, x2, y2, f.x1, f.y1, f.x2, f.y2)) { + if (isNearWallEndpoint(x1, y1, x2, y2, f) + || isLikelyWallSliding(x1, y1, x2, y2, f)) { + continue; + } + return true; + } + } + } + return false; + } + + // Position correction + /** + * Correct a position that has drifted through a wall using wall sliding. + * + *

Algorithm (vector projection / wall sliding)

+ *
    + *
  1. Compute the raw displacement vector {@code d = (newX-prevX, newY-prevY)}.
  2. + *
  3. For each wall that the displacement crosses, decompose {@code d} into: + *
      + *
    • A component parallel to the wall (allowed 鈥?the user slides along it).
    • + *
    • A component perpendicular to the wall (blocked 鈥?would penetrate the wall).
    • + *
    + *
  4. + *
  5. The corrected displacement retains only the parallel component, so the + * position glides smoothly along the wall instead of being frozen or + * snapped back. This eliminates the "walk-into-wall 鈫?hard-pull-back" + * spike pattern.
  6. + *
  7. A small safety margin ({@link #WALL_SLIDE_SAFETY_M}) is subtracted from + * the perpendicular component to keep the position on the correct side of + * the wall even after floating-point rounding.
  8. + *
+ * + *

If the slid position still crosses another wall the process is repeated + * (up to {@link #MAX_SLIDE_ITERATIONS} times) so that corner collisions are + * handled correctly.

+ * + * @param prevX Previous east position (metres). + * @param prevY Previous north position (metres). + * @param newX Proposed new east position (metres). + * @param newY Proposed new north position (metres). + * @return Corrected position as {@code double[]{east, north}}. + */ + public double[] correctPosition(double prevX, double prevY, + double newX, double newY) { + if (distance(prevX, prevY, newX, newY) < MIN_WALL_CHECK_STEP_M) { + return new double[]{newX, newY}; + } + + // Iterative wall-sliding: resolve up to MAX_SLIDE_ITERATIONS wall collisions. + double curX = prevX, curY = prevY; + double dstX = newX, dstY = newY; + + for (int iter = 0; iter < MAX_SLIDE_ITERATIONS; iter++) { + MapFeature hitWall = null; + + for (MapFeature f : features) { + if (f.type != FeatureType.WALL) continue; + if (segmentsIntersect(curX, curY, dstX, dstY, + f.x1, f.y1, f.x2, f.y2)) { + if (isNearWallEndpoint(curX, curY, dstX, dstY, f) + || isLikelyWallSliding(curX, curY, dstX, dstY, f)) { + continue; + } + hitWall = f; + break; // handle one wall per iteration + } + } + + if (hitWall == null) { + // No more walls hit 鈥?accept the current destination. + return new double[]{dstX, dstY}; + } + + // Wall-sliding via vector projection + // Displacement vector from current position to proposed destination. + double dx = dstX - curX; + double dy = dstY - curY; + + // Unit vector along the wall. + double wallDx = hitWall.x2 - hitWall.x1; + double wallDy = hitWall.y2 - hitWall.y1; + double wallLen = Math.sqrt(wallDx * wallDx + wallDy * wallDy); + if (wallLen < 1e-9) { + // Degenerate wall 鈥?fall back to hard stop. + return new double[]{curX, curY}; + } + double wallUx = wallDx / wallLen; // unit vector along wall (east component) + double wallUy = wallDy / wallLen; // unit vector along wall (north component) + + // Project displacement onto the wall direction (parallel component). + double parallelMag = dx * wallUx + dy * wallUy; + double slideX = parallelMag * wallUx; + double slideY = parallelMag * wallUy; + + // Apply safety margin: pull the slid position slightly away from the wall + // in the direction the user came from (perpendicular, toward prevX/prevY). + double perpX = dx - slideX; // perpendicular component of displacement + double perpY = dy - slideY; + double perpLen = Math.sqrt(perpX * perpX + perpY * perpY); + double safeOffsetX = 0, safeOffsetY = 0; + if (perpLen > 1e-9) { + // Unit vector pointing away from the wall (back toward the user's side). + safeOffsetX = -(perpX / perpLen) * WALL_SLIDE_SAFETY_M; + safeOffsetY = -(perpY / perpLen) * WALL_SLIDE_SAFETY_M; + } + + // New proposed destination: slide along wall + safety offset. + dstX = curX + slideX + safeOffsetX; + dstY = curY + slideY + safeOffsetY; + + } + + // Exceeded iteration limit 鈥?hard stop to prevent infinite loops. + Log.w(TAG, "Wall sliding: max iterations reached, hard stop."); + return new double[]{prevX, prevY}; + } + + /** Maximum number of wall-sliding iterations per correction call. */ + private static final int MAX_SLIDE_ITERATIONS = 4; + + /** + * Safety margin (metres) subtracted from the perpendicular displacement + * component to keep the corrected position on the correct side of the wall. + */ + private static final double WALL_SLIDE_SAFETY_M = 0.05; + + // Floor inference + /** + * Attempt to update the current floor based on barometric height change + * and the motion model. + * + *

Two scenarios are handled:

+ *
    + *
  • Stairs: the user is near a staircase zone AND the barometer + * reports a height change exceeding {@link #STAIR_HEIGHT_THRESHOLD_M}. + * The floor is updated by 卤1 depending on the sign of the height + * change.
  • + *
  • Lift: the user is near a lift zone AND horizontal speed is + * below {@link #LIFT_MAX_HORIZ_SPEED} AND the barometer reports a + * height change exceeding {@link #LIFT_HEIGHT_THRESHOLD_M}. The + * floor is updated by the number of floors implied by the height + * change.
  • + *
+ * + * @param eastM Current east position (metres). + * @param northM Current north position (metres). + * @param baroHeightChangedM Barometric height change since last floor check (metres). + * @param horizSpeedMs Estimated horizontal speed (m/s). + * @return New floor number (unchanged if no transition detected). + */ + public int updateFloor(double eastM, double northM, + float baroHeightChangedM, float horizSpeedMs) { + + boolean hasTransitionFeatures = hasFeatureType(FeatureType.STAIRS) + || hasFeatureType(FeatureType.LIFT); + + // Staircase check + if (Math.abs(baroHeightChangedM) >= STAIR_HEIGHT_THRESHOLD_M) { + if (isNearFeatureType(eastM, northM, FeatureType.STAIRS, 8.0)) { + int delta = (baroHeightChangedM > 0) ? 1 : -1; + int newFloor = currentFloor + delta; + Log.d(TAG, String.format("Stairs detected: floor %d 鈫?%d (螖h=%.1f m)", + currentFloor, newFloor, baroHeightChangedM)); + currentFloor = newFloor; + return currentFloor; + } + } + + // Lift check + if (Math.abs(baroHeightChangedM) >= LIFT_HEIGHT_THRESHOLD_M + && horizSpeedMs < LIFT_MAX_HORIZ_SPEED) { + if (isNearFeatureType(eastM, northM, FeatureType.LIFT, 10.0)) { + int floorsChanged = (int) Math.round(baroHeightChangedM / floorHeightM); + if (floorsChanged != 0) { + int newFloor = currentFloor + floorsChanged; + Log.d(TAG, String.format("Lift detected: floor %d 鈫?%d (螖h=%.1f m)", + currentFloor, newFloor, baroHeightChangedM)); + currentFloor = newFloor; + return currentFloor; + } + } + } + + // If no stairs/lift map features are available, fall back to pure + // barometer-based floor inference so floor can still auto-switch. + if (!hasTransitionFeatures && Math.abs(baroHeightChangedM) >= STAIR_HEIGHT_THRESHOLD_M) { + int floorsChanged = (int) Math.round(baroHeightChangedM / floorHeightM); + if (floorsChanged == 0) { + floorsChanged = (baroHeightChangedM > 0) ? 1 : -1; + } + int newFloor = currentFloor + floorsChanged; + Log.d(TAG, String.format("Barometer fallback: floor %d 鈫?%d (螖h=%.1f m)", + currentFloor, newFloor, baroHeightChangedM)); + currentFloor = newFloor; + return currentFloor; + } + + return currentFloor; + } + + // Proximity helpers + /** + * Check whether the given position is within {@code radiusM} metres of any + * feature of the specified type. + */ + private boolean isNearFeatureType(double eastM, double northM, + FeatureType type, double radiusM) { + for (MapFeature f : features) { + if (f.type == type) { + // Distance from point to segment + double dist = pointToSegmentDistance(eastM, northM, + f.x1, f.y1, f.x2, f.y2); + if (dist <= radiusM) return true; + } + } + return false; + } + + private boolean hasFeatureType(FeatureType type) { + for (MapFeature f : features) { + if (f.type == type) { + return true; + } + } + return false; + } + + // Geometry utilities + /** + * Test whether two 2-D line segments intersect. + * + *

Uses the cross-product / orientation method.

+ */ + private static boolean segmentsIntersect(double ax, double ay, double bx, double by, + double cx, double cy, double dx, double dy) { + double d1 = cross(cx, cy, dx, dy, ax, ay); + double d2 = cross(cx, cy, dx, dy, bx, by); + double d3 = cross(ax, ay, bx, by, cx, cy); + double d4 = cross(ax, ay, bx, by, dx, dy); + + if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && + ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) { + return true; + } + + // Collinear cases + if (d1 == 0 && onSegment(cx, cy, dx, dy, ax, ay)) return true; + if (d2 == 0 && onSegment(cx, cy, dx, dy, bx, by)) return true; + if (d3 == 0 && onSegment(ax, ay, bx, by, cx, cy)) return true; + if (d4 == 0 && onSegment(ax, ay, bx, by, dx, dy)) return true; + + return false; + } + + /** Cross product of vectors (px, py) and (qx, qy). */ + private static double cross(double px, double py, + double qx, double qy, + double rx, double ry) { + return (qx - px) * (ry - py) - (qy - py) * (rx - px); + } + + /** Check whether point rx lies on segment px (assuming collinearity). */ + private static boolean onSegment(double px, double py, + double qx, double qy, + double rx, double ry) { + return Math.min(px, qx) <= rx && rx <= Math.max(px, qx) && + Math.min(py, qy) <= ry && ry <= Math.max(py, qy); + } + + private static boolean isNearWallEndpoint(double x1, double y1, + double x2, double y2, + MapFeature wall) { + return distance(x1, y1, wall.x1, wall.y1) <= WALL_ENDPOINT_TOLERANCE_M + || distance(x1, y1, wall.x2, wall.y2) <= WALL_ENDPOINT_TOLERANCE_M + || distance(x2, y2, wall.x1, wall.y1) <= WALL_ENDPOINT_TOLERANCE_M + || distance(x2, y2, wall.x2, wall.y2) <= WALL_ENDPOINT_TOLERANCE_M; + } + + private static boolean isLikelyWallSliding(double x1, double y1, + double x2, double y2, + MapFeature wall) { + double moveX = x2 - x1; + double moveY = y2 - y1; + double moveLen = Math.sqrt(moveX * moveX + moveY * moveY); + if (moveLen < MIN_WALL_CHECK_STEP_M) { + return true; + } + + double wallX = wall.x2 - wall.x1; + double wallY = wall.y2 - wall.y1; + double wallLen = Math.sqrt(wallX * wallX + wallY * wallY); + if (wallLen < 1e-6) { + return false; + } + + double parallel = Math.abs((moveX * wallX + moveY * wallY) / (moveLen * wallLen)); + if (parallel < 0.92) { + return false; + } + + double dStart = pointToSegmentDistance(x1, y1, wall.x1, wall.y1, wall.x2, wall.y2); + double dEnd = pointToSegmentDistance(x2, y2, wall.x1, wall.y1, wall.x2, wall.y2); + return dStart <= WALL_SLIDING_DISTANCE_M && dEnd <= WALL_SLIDING_DISTANCE_M; + } + + private static double distance(double x1, double y1, double x2, double y2) { + double dx = x2 - x1; + double dy = y2 - y1; + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * Minimum distance from point {@code (px,py)} to segment + * {@code (ax,ay) and (bx,by)}. + */ + private static double pointToSegmentDistance(double px, double py, + double ax, double ay, + double bx, double by) { + double dx = bx - ax, dy = by - ay; + double lenSq = dx * dx + dy * dy; + if (lenSq == 0) { + // Degenerate segment (point) + double ex = px - ax, ey = py - ay; + return Math.sqrt(ex * ex + ey * ey); + } + double t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq)); + double projX = ax + t * dx; + double projY = ay + t * dy; + double ex = px - projX, ey = py - projY; + return Math.sqrt(ex * ex + ey * ey); + } + + // Getters / setters + /** @return Current floor number (0 = ground). */ + public int getCurrentFloor() { return currentFloor; } + + /** + * Manually set the current floor (e.g. from user input or WiFi floor). + * + * @param floor Floor number. + */ + public void setCurrentFloor(int floor) { this.currentFloor = floor; } + + /** + * Set the floor height used for barometer-based floor estimation. + * + * @param heightM Floor height in metres. + */ + public void setFloorHeightM(float heightM) { this.floorHeightM = heightM; } + + /** @return All registered map features. */ + public List getFeatures() { return features; } +} + diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java b/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java index 9765b044..63227391 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java @@ -27,8 +27,17 @@ public class PdrProcessing { //region Static variables - // Weiberg algorithm coefficient for stride calculations - private static final float K = 0.364f; + // Weiberg algorithm coefficient for step length estimation. + // Original Weiberg K≈0.53 is for stride (two steps). Android TYPE_STEP_DETECTOR fires + // once per step (each footfall), so K should remain below stride-level constants. + // 0.42 improves distance scale in real indoor walking where 0.36 tends to under-shoot. + private static final float K = 0.50f; + private static final float MIN_STRIDE_M = 0.30f; + private static final float MAX_STRIDE_M = 0.85f; + private static final float DEFAULT_STEP_M = 0.65f; + // EMA coefficient for step-length smoothing (lower = smoother, higher = more responsive) + // 0.45 keeps trajectory smooth while responding to genuine pace changes in ~3 steps. + private static final float STEP_SMOOTHING = 0.45f; // Number of samples (seconds) to keep as memory for elevation calculation private static final int elevationSeconds = 4; // Number of samples (0.01 seconds) @@ -71,6 +80,8 @@ public class PdrProcessing { // Step sum and length aggregation variables private float sumStepLength = 0; private int stepCount = 0; + // EMA-smoothed step length to reduce step-to-step jitter + private float smoothedStepLength = 0f; //endregion /** @@ -140,35 +151,31 @@ public PdrProcessing(Context context) { * @param headingRad heading relative to magnetic north in radians. */ public float[] updatePdr(long currentStepEnd, List accelMagnitudeOvertime, float headingRad) { - if (accelMagnitudeOvertime == null || accelMagnitudeOvertime.size() < MIN_REQUIRED_SAMPLES) { - return new float[]{this.positionX, this.positionY}; // Return current position without update - // - TODO - temporary solution of the empty list issue - } - - // Change angle so zero rad is east - float adaptedHeading = (float) (Math.PI/2 - headingRad); - - // check if accelMagnitudeOvertime is empty - if (accelMagnitudeOvertime == null || accelMagnitudeOvertime.isEmpty()) { - // return current position, do not update - return new float[]{this.positionX, this.positionY}; - } - - // Calculate step length - if(!useManualStep) { - //ArrayList accelMagnitudeFiltered = filter(accelMagnitudeOvertime); - // Estimate stride - this.stepLength = weibergMinMax(accelMagnitudeOvertime); - // System.err.println("Step Length" + stepLength); + boolean validAccel = accelMagnitudeOvertime != null + && accelMagnitudeOvertime.size() >= MIN_REQUIRED_SAMPLES; + + // Calculate step length. Never drop a step event completely: if accel samples are + // sparse on this step, reuse the latest stable estimate (or a conservative default). + if (!useManualStep) { + if (validAccel) { + this.stepLength = weibergMinMax(accelMagnitudeOvertime); + } else if (smoothedStepLength > 0f) { + this.stepLength = smoothedStepLength; + } else { + this.stepLength = DEFAULT_STEP_M; + } } // Increment aggregate variables sumStepLength += stepLength; stepCount++; - // Translate to cartesian coordinate system - float x = (float) (stepLength * Math.cos(adaptedHeading)); - float y = (float) (stepLength * Math.sin(adaptedHeading)); + // Translate to ENU cartesian coordinate system. + // headingRad: 0 = north, π/2 = east (Android SensorManager convention). + // East displacement = stepLength * sin(heading) + // North displacement = stepLength * cos(heading) + float x = (float) (stepLength * Math.sin(headingRad)); // east component + float y = (float) (stepLength * Math.cos(headingRad)); // north component // Update position values this.positionX += x; @@ -254,11 +261,25 @@ private float weibergMinMax(List accelMagnitude) { float bounce = (float) Math.pow((maxAccel - minAccel), 0.25); // determine which constant to use based on settings + // Note: Removed *2 multiplier that was causing overestimation + float stride; if (this.settings.getBoolean("overwrite_constants", false)) { - return bounce * Float.parseFloat(settings.getString("weiberg_k", "0.934")) * 2; + stride = bounce * Float.parseFloat(settings.getString("weiberg_k", "0.50")); + } else { + stride = bounce * K; } - return bounce * K * 2; + // Clamp unrealistic strides to keep trajectory smooth and avoid sudden jumps. + float clamped = Math.max(MIN_STRIDE_M, Math.min(MAX_STRIDE_M, stride)); + + // Apply EMA smoothing to reduce step-to-step length jitter which causes + // the displayed position to alternately lead and lag the user. + if (smoothedStepLength <= 0f) { + smoothedStepLength = clamped; // first step: initialise + } else { + smoothedStepLength = STEP_SMOOTHING * clamped + (1f - STEP_SMOOTHING) * smoothedStepLength; + } + return smoothedStepLength; } /** @@ -371,6 +392,7 @@ public void resetPDR() { this.positionX = 0f; this.positionY = 0f; this.elevation = 0f; + this.smoothedStepLength = 0f; if(this.settings.getBoolean("overwrite_constants", false)) { // Capacity - pressure is read with 1Hz - store values of past 10 seconds diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/SimplePositionFusion.java b/app/src/main/java/com/openpositioning/PositionMe/utils/SimplePositionFusion.java new file mode 100644 index 00000000..5563b7bb --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/SimplePositionFusion.java @@ -0,0 +1,362 @@ +package com.openpositioning.PositionMe.utils; + +import android.util.Log; +import com.google.android.gms.maps.model.LatLng; + +/** + * Simple, correct Position Fusion. + * + * Design principle: PDR already accumulates steps into (x,y) coordinates. + * We simply convert that to lat/lng and add drift correction from GNSS/anchors. + * + * NO sliding windows. NO re-weighting. NO re-calculation. + * Just: position = startLatLng + PDR_offset + drift_correction + * + * Drift correction is the key to long-term accuracy: + * - GNSS provides periodic absolute position fixes + * - User anchor points provide manual corrections + * - The correction is applied as a simple offset that accumulates + * + * @author PositionMe + */ +public class SimplePositionFusion { + + private static final String TAG = "SimplePosFusion"; + + // START POSITION + private double startLat = 0; + private double startLng = 0; + + // DRIFT CORRECTION + // This is the accumulated correction offset in meters + // It represents: (true_position - pdr_position) + private double correctionX = 0; // East-West correction in meters + private double correctionY = 0; // North-South correction in meters + + // PDR TRACKING + private float lastPdrX = 0; + private float lastPdrY = 0; + + // OUTPUT SMOOTHING + // Simple low-pass filter on the output to remove jitter + private double smoothLat = 0; + private double smoothLng = 0; + private static final double SMOOTH_ALPHA = 1.0; // No additional smoothing lag; EKF+step EMA already smooth + + // STATE + private boolean initialized = false; + private int stepCount = 0; + private boolean firstWiFiFix = true; // First WiFi fix applies stronger correction + + // BUILDING CONSTRAINT + private double[] buildingBounds = null; + private boolean constrainEnabled = false; + + // CONSTANTS + private static final double METERS_PER_DEG_LAT = 111139.0; + + /** + * Initialize with starting GPS position. + */ + public void initialize(double latitude, double longitude, float accuracy) { + startLat = latitude; + startLng = longitude; + + correctionX = 0; + correctionY = 0; + + lastPdrX = 0; + lastPdrY = 0; + + smoothLat = latitude; + smoothLng = longitude; + + stepCount = 0; + firstWiFiFix = true; + initialized = true; + + checkBuildingConstraint(latitude, longitude); + + Log.d(TAG, String.format("Init at (%.6f, %.6f) acc=%.1f", latitude, longitude, accuracy)); + } + + public boolean isInitialized() { return initialized; } + + /** + * Main PDR update. Called on each step. + * + * pdrX, pdrY are cumulative PDR position from PdrProcessing (meters from start). + * We simply convert to lat/lng and add our correction offset. + */ + public void updateWithPDR(float pdrX, float pdrY) { + if (!initialized) { + lastPdrX = pdrX; + lastPdrY = pdrY; + return; + } + + lastPdrX = pdrX; + lastPdrY = pdrY; + stepCount++; + + // Calculate raw position: start + PDR + correction + double metersPerDegLng = METERS_PER_DEG_LAT * Math.cos(Math.toRadians(startLat)); + + // PDR coordinate system: X is East (sin(heading)), Y is North (cos(heading)) + // PdrProcessing uses: x = stepLen * sin(heading), y = stepLen * cos(heading) + double rawLat = startLat + (pdrY + correctionY) / METERS_PER_DEG_LAT; + double rawLng = startLng + (pdrX + correctionX) / metersPerDegLng; + + // Apply building constraint + if (constrainEnabled && buildingBounds != null) { + rawLat = Math.max(buildingBounds[0], Math.min(buildingBounds[1], rawLat)); + rawLng = Math.max(buildingBounds[2], Math.min(buildingBounds[3], rawLng)); + } + + // Smooth output + smoothLat = smoothLat + SMOOTH_ALPHA * (rawLat - smoothLat); + smoothLng = smoothLng + SMOOTH_ALPHA * (rawLng - smoothLng); + } + + /** + * Simplified PDR update with heading and step length (compatibility). + */ + public void updateWithPDR(float pdrX, float pdrY, float heading, float stepLength) { + updateWithPDR(pdrX, pdrY); + } + + /** + * Update with GNSS fix. + * Calculates the error between where PDR thinks we are and where GNSS says we are, + * then adds that error to our drift correction. + */ + public boolean updateWithGNSS(double lat, double lng, float accuracy) { + if (!initialized) { + if (accuracy < 50) { + initialize(lat, lng, accuracy); + return true; + } + return false; + } + + // Ignore poor GNSS in indoor mode + if (constrainEnabled && accuracy > 15) { + return false; + } + + // Ignore very poor GNSS everywhere + if (accuracy > 30) { + return false; + } + + // Calculate where PDR thinks we are (without correction we'd have applied) + double metersPerDegLng = METERS_PER_DEG_LAT * Math.cos(Math.toRadians(startLat)); + double pdrLat = startLat + (lastPdrY + correctionY) / METERS_PER_DEG_LAT; + double pdrLng = startLng + (lastPdrX + correctionX) / metersPerDegLng; + + // Error = GNSS position - current PDR position + double errorY = (lat - pdrLat) * METERS_PER_DEG_LAT; + double errorX = (lng - pdrLng) * metersPerDegLng; + + // Weight based on accuracy (better accuracy = more trust) + // accuracy 5m -> weight 0.5, accuracy 15m -> weight 0.17, accuracy 30m -> weight 0.08 + double weight = Math.min(0.5, 2.5 / accuracy); + + // Cap the correction applied per GNSS update to prevent sudden jumps. + // Indoor GNSS can have 10-30m errors; without a cap, a single bad fix can + // shift the displayed position by several metres instantly. + // 0.5 m/fix means large errors are absorbed gradually (e.g. 5 m drift corrected + // over ~10 GPS fixes ≈ 10 s), while small real drift is corrected quickly. + final double MAX_CORRECTION_PER_FIX = 1.2; // metres + double cx = errorX * weight; + double cy = errorY * weight; + correctionX += Math.max(-MAX_CORRECTION_PER_FIX, Math.min(MAX_CORRECTION_PER_FIX, cx)); + correctionY += Math.max(-MAX_CORRECTION_PER_FIX, Math.min(MAX_CORRECTION_PER_FIX, cy)); + + return true; + } + + /** + * Update with WiFi positioning fix. + * + * WiFi is "no-lag, absolute" – its statistical centre tracks the true + * position even though individual readings jitter. We use it to pull the + * PDR estimate forward when it is lagging, but with careful guard-rails: + * + * error < 2 m → PDR is accurate, virtually ignore WiFi + * 2 ≤ error ≤ 10 → PDR is lagging, apply proportional correction + * 10 < error ≤ 15 → might be jitter; small correction only + * error > 15 m → WiFi is jumping, discard entirely + * + * The per-fix correction is clamped to MAX_WIFI_CORRECTION_PER_FIX metres + * so a single bad scan can never cause a visible teleport. + */ + public void updateWithWiFi(double lat, double lng) { + if (!initialized) return; + + // Current fused position (PDR + accumulated corrections) + double metersPerDegLng = METERS_PER_DEG_LAT * Math.cos(Math.toRadians(startLat)); + double curLat = startLat + (lastPdrY + correctionY) / METERS_PER_DEG_LAT; + double curLng = startLng + (lastPdrX + correctionX) / metersPerDegLng; + + // Error vector: WiFi position − current fused position + double errorY = (lat - curLat) * METERS_PER_DEG_LAT; + double errorX = (lng - curLng) * metersPerDegLng; + double errorMag = Math.sqrt(errorX * errorX + errorY * errorY); + + // ---- First WiFi fix: strong initial alignment ---- + // The user-selected start position may have a few metres of offset. + // The very first WiFi response can correct this immediately (like an + // anchor point), so subsequent gradual corrections start from a + // position that already matches WiFi's statistical centre. + if (firstWiFiFix && errorMag >= 2.0 && errorMag <= 15.0) { + firstWiFiFix = false; + double strongWeight = 0.70; + correctionX += errorX * strongWeight; + correctionY += errorY * strongWeight; + // Also snap the smooth output to avoid visual lag on the first jump + double newLat = startLat + (lastPdrY + correctionY) / METERS_PER_DEG_LAT; + double newLng = startLng + (lastPdrX + correctionX) / metersPerDegLng; + smoothLat = smoothLat + 0.7 * (newLat - smoothLat); + smoothLng = smoothLng + 0.7 * (newLng - smoothLng); + Log.d(TAG, String.format("WiFi FIRST FIX: err=%.1fm, applied %.0f%% correction", + errorMag, strongWeight * 100)); + return; + } + firstWiFiFix = false; // Mark as consumed even if error was too small/large + + // ---- Dynamic weight based on error magnitude ---- + double weight; + if (errorMag < 2.0) { + // PDR is very close to WiFi → PDR is right, don't disturb it + weight = 0.05; + } else if (errorMag <= 10.0) { + // PDR is lagging – linearly ramp weight from 0.05 to 0.25 + // so larger lags get stronger pull-back + weight = 0.10 + 0.22 * ((errorMag - 2.0) / 8.0); + } else if (errorMag <= 15.0) { + // Borderline jitter zone – small cautious correction + weight = 0.10; + } else { + // Clearly anomalous WiFi jump → discard + return; + } + + // Cap the per-fix correction to prevent sudden UI jumps + final double MAX_WIFI_CORRECTION_PER_FIX = 1.5; // metres + double cx = errorX * weight; + double cy = errorY * weight; + correctionX += Math.max(-MAX_WIFI_CORRECTION_PER_FIX, + Math.min(MAX_WIFI_CORRECTION_PER_FIX, cx)); + correctionY += Math.max(-MAX_WIFI_CORRECTION_PER_FIX, + Math.min(MAX_WIFI_CORRECTION_PER_FIX, cy)); + + } + + /** + * User manually sets their position (anchor point). + * This is a strong correction - apply most of the error immediately. + */ + public void setAnchorPoint(double lat, double lng) { + if (!initialized) return; + + double metersPerDegLng = METERS_PER_DEG_LAT * Math.cos(Math.toRadians(startLat)); + double pdrLat = startLat + (lastPdrY + correctionY) / METERS_PER_DEG_LAT; + double pdrLng = startLng + (lastPdrX + correctionX) / metersPerDegLng; + + // User anchor is highly trusted - apply 80% of correction immediately + double errorY = (lat - pdrLat) * METERS_PER_DEG_LAT; + double errorX = (lng - pdrLng) * metersPerDegLng; + + correctionX += errorX * 0.8; + correctionY += errorY * 0.8; + + // Also update smooth output to jump closer + smoothLat = smoothLat + 0.7 * (lat - smoothLat); + smoothLng = smoothLng + 0.7 * (lng - smoothLng); + + checkBuildingConstraint(lat, lng); + + } + + // BUILDING CONSTRAINT + + private void checkBuildingConstraint(double lat, double lng) { + // Nucleus + if (lat >= 55.92282 && lat <= 55.92332 && lng >= -3.17460 && lng <= -3.17387) { + buildingBounds = new double[]{55.92282, 55.92332, -3.17460, -3.17387}; + constrainEnabled = true; + return; + } + // Library + if (lat >= 55.92260 && lat <= 55.92310 && lng >= -3.17530 && lng <= -3.17440) { + buildingBounds = new double[]{55.92260, 55.92310, -3.17530, -3.17440}; + constrainEnabled = true; + return; + } + constrainEnabled = false; + buildingBounds = null; + } + + public void setConstrainToBuilding(boolean enable) { + constrainEnabled = enable; + } + + public void setBuildingBounds(double minLat, double maxLat, double minLng, double maxLng) { + buildingBounds = new double[]{minLat, maxLat, minLng, maxLng}; + constrainEnabled = true; + } + + // OUTPUT + + public double getFusedLatitude() { return smoothLat; } + public double getFusedLongitude() { return smoothLng; } + + public LatLng getPosition() { + return new LatLng(smoothLat, smoothLng); + } + + public float getPositionUncertainty() { + double corrMag = Math.sqrt(correctionX * correctionX + correctionY * correctionY); + return (float) Math.min(5.0 + corrMag * 0.1, 20.0); + } + + public float[] getPdrOffset() { + return new float[]{lastPdrX, lastPdrY}; + } + + public boolean hasAnchorPoint() { + return Math.abs(correctionX) > 0.1 || Math.abs(correctionY) > 0.1; + } + + public boolean isConstrainedToBuilding() { return constrainEnabled; } + public double getFilteredHeading() { return 0; } + public double getAdaptiveStepLength() { return 0.65; } + + public long getTimeSinceLastGnss() { return Long.MAX_VALUE; } + public boolean isGnssStale() { return true; } + + // CONTROL + + public void forceReset(double lat, double lng, float accuracy) { + initialize(lat, lng, accuracy); + } + + public void reset() { + initialized = false; + startLat = 0; startLng = 0; + correctionX = 0; correctionY = 0; + lastPdrX = 0; lastPdrY = 0; + smoothLat = 0; smoothLng = 0; + stepCount = 0; + firstWiFiFix = true; + constrainEnabled = false; + buildingBounds = null; + Log.d(TAG, "Reset"); + } + + // Compatibility stubs for sensor updates (not needed in simple fusion) + public void updateWithAccelerometer(float[] accel, float heading) { } + public void updateWithGyroscope(float[] gyro) { } + public void updateWithMagnetometer(float heading) { } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/UtilFunctions.java b/app/src/main/java/com/openpositioning/PositionMe/utils/UtilFunctions.java index 56a88aa3..c14f2736 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/utils/UtilFunctions.java +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/UtilFunctions.java @@ -65,8 +65,7 @@ public static double degreesToMetersLng(double degreeVal, double latitude) { } /** - * Calculates the distance between two LatLng points A and B (in meters) - * (Note: approximation: for short distances) + * Calculates the distance between two LatLng points A and B in meters * @param pointA initial point * @param pointB final point * @return the distance between the two points diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/WknnPredictor.java b/app/src/main/java/com/openpositioning/PositionMe/utils/WknnPredictor.java new file mode 100644 index 00000000..5029d30a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/WknnPredictor.java @@ -0,0 +1,142 @@ +package com.openpositioning.PositionMe.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Weighted KNN indoor fingerprint predictor. + */ +public class WknnPredictor { + + public static class Fingerprint { + public final double x; + public final double y; + public final Map macRssiMap; + + public Fingerprint(double x, double y, Map macRssiMap) { + this.x = x; + this.y = y; + this.macRssiMap = new HashMap<>(macRssiMap); + } + } + + public static class WknnResult { + public final double x; + public final double y; + public final double score; + + public WknnResult(double x, double y, double score) { + this.x = x; + this.y = y; + this.score = score; + } + } + + private static class DistanceItem { + final Fingerprint fingerprint; + final double distance; + final int matchedMacCount; + + DistanceItem(Fingerprint fingerprint, double distance, int matchedMacCount) { + this.fingerprint = fingerprint; + this.distance = distance; + this.matchedMacCount = matchedMacCount; + } + } + + private final List fingerprintDb = new ArrayList<>(); + private final int k; + private final double epsilon; + private final double lambda; + private final int missingRssiPenalty; + + public WknnPredictor(int k, double epsilon, double lambda, int missingRssiPenalty) { + this.k = Math.max(1, k); + this.epsilon = Math.max(1e-9, epsilon); + this.lambda = Math.max(0.0, lambda); + this.missingRssiPenalty = missingRssiPenalty; + } + + public WknnPredictor() { + this(4, 1e-6, 0.05, -100); + } + + public synchronized void clear() { + fingerprintDb.clear(); + } + + public synchronized void addFingerprint(Fingerprint fingerprint) { + if (fingerprint == null || fingerprint.macRssiMap == null || fingerprint.macRssiMap.isEmpty()) { + return; + } + fingerprintDb.add(fingerprint); + } + + public synchronized int size() { + return fingerprintDb.size(); + } + + public synchronized WknnResult predictPosition(Map currentScan) { + if (currentScan == null || currentScan.isEmpty() || fingerprintDb.isEmpty()) { + return null; + } + + List ranked = new ArrayList<>(fingerprintDb.size()); + for (Fingerprint fp : fingerprintDb) { + double sumSq = 0.0; + int matched = 0; + + for (Map.Entry entry : currentScan.entrySet()) { + String mac = entry.getKey(); + int curRssi = entry.getValue(); + Integer libRssi = fp.macRssiMap.get(mac); + int refRssi = (libRssi != null) ? libRssi : missingRssiPenalty; + if (libRssi != null) { + matched++; + } + double diff = curRssi - refRssi; + sumSq += diff * diff; + } + + double distance = Math.sqrt(sumSq); + ranked.add(new DistanceItem(fp, distance, matched)); + } + + Collections.sort(ranked, Comparator.comparingDouble(item -> item.distance)); + + int usedK = Math.min(k, ranked.size()); + double weightedX = 0.0; + double weightedY = 0.0; + double weightSum = 0.0; + + for (int i = 0; i < usedK; i++) { + DistanceItem item = ranked.get(i); + double w = 1.0 / (item.distance + epsilon); + weightedX += w * item.fingerprint.x; + weightedY += w * item.fingerprint.y; + weightSum += w; + } + + if (weightSum <= 0.0) { + return null; + } + + double xWknn = weightedX / weightSum; + double yWknn = weightedY / weightSum; + + DistanceItem best = ranked.get(0); + double macRatio = (double) best.matchedMacCount / (double) currentScan.size(); + // Use RMS RSSI error (per AP) rather than raw L2 distance so confidence remains + // meaningful as the number of scanned APs changes. + double rmsDistance = best.distance / Math.sqrt(Math.max(1, currentScan.size())); + double score = macRatio * Math.exp(-lambda * rmsDistance); + if (score < 0.0) score = 0.0; + if (score > 1.0) score = 1.0; + + return new WknnResult(xWknn, yWknn, score); + } +} diff --git a/app/src/main/proto/traj.proto b/app/src/main/proto/traj.proto index 95d8b0ac..9c97cabf 100644 --- a/app/src/main/proto/traj.proto +++ b/app/src/main/proto/traj.proto @@ -1,149 +1,215 @@ syntax = "proto3"; -message Trajectory { -string android_version = 1; -repeated Motion_Sample imu_data = 2; -repeated Pdr_Sample pdr_data = 3; -repeated Position_Sample position_data = 4; -repeated Pressure_Sample pressure_data = 5; -repeated Light_Sample light_data = 6; - -repeated GNSS_Sample gnss_data = 7; -repeated WiFi_Sample wifi_data = 8; -repeated AP_Data aps_data = 9; +option java_package = "com.openpositioning.PositionMe"; +option java_outer_classname = "Traj"; -// UNIX timestamp (in milliseconds) recorded from the start of this -// trajectory data collection event. All future -// timestamps in sub classes are to be RELATIVE timestamps -// (in milliseconds) to this start time. -// E.g. -// start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning) -// relative_timestamp = 3000 (3s) -int64 start_timestamp = 10; -string data_identifier = 11; -Sensor_Info accelerometer_info = 12; -Sensor_Info gyroscope_info = 13; -Sensor_Info rotation_vector_info = 14; -Sensor_Info magnetometer_info = 15; -Sensor_Info barometer_info = 16; -Sensor_Info light_sensor_info = 17; +message Trajectory { + string android_version = 1; + // version 2.0 + float trajectory_version = 2; + // trajectory id/name for identification + string trajectory_id = 3; + repeated IMUReading imu_data = 4; + repeated RelativePosition pdr_data = 5; + repeated MagnetometerReading magnetometer_data = 6; + repeated BarometerReading pressure_data = 7; + repeated LightReading light_data = 8; + repeated ProximityReading proximity_data = 9; + + repeated GNSSReading gnss_data = 10; + repeated Fingerprint wifi_fingerprints = 11; + repeated WiFiAPData aps_data = 12; + repeated WiFiRTTReading wifi_rtt_data = 13; + repeated Fingerprint ble_fingerprints = 14; + repeated BleData ble_data = 15; + + // UNIX timestamp (in milliseconds) recorded from the start of this + // trajectory data collection event. All future + // timestamps in sub classes are to be RELATIVE timestamps + // (in milliseconds) to this start time. + // E.g. + // start_timestamp = 1674819807315 (UTC 27 Jan 2023 in the morning) + // relative_timestamp = 3000 (3s) + int64 start_timestamp = 16; + GNSSPosition initial_position = 17; + repeated GNSSPosition corrected_positions = 18; + + SensorInfo accelerometer_info = 19; + SensorInfo gyroscope_info = 20; + SensorInfo rotation_vector_info = 21; + SensorInfo magnetometer_info = 22; + SensorInfo barometer_info = 23; + SensorInfo light_sensor_info = 24; + SensorInfo proximity_info = 25; + + // 🆕 Test Points Data - Timestamped markers marked during recording session + repeated GNSSPosition test_points = 26; + int32 test_point_count = 27; // Total number of test points marked } -message Pdr_Sample { -// milliseconds from the start_timestamp -int64 relative_timestamp = 1; +message RelativePosition { + // milliseconds from the start_timestamp + int64 relative_timestamp = 1; -// Both in metres. You should implement an algorithm to estimate -// these values. The values are always relative to your start point -// so the first entry should always be x = 0.0, y = 0.0 -float x = 2; -float y = 3; + // Both in metres. You should implement an algorithm to estimate + // these values. The values are always relative to your start point + // so the first entry should always be x = 0.0, y = 0.0 + float x = 2; + float y = 3; } - -message Motion_Sample { - // milliseconds - int64 relative_timestamp = 1; - // m/s^2 - float acc_x = 2; - float acc_y = 3; - float acc_z = 4; - - // radians/s - float gyr_x = 5; - float gyr_y = 6; - float gyr_z = 7; - - // unitless, 4 components should sum to ~1 - float rotation_vector_x = 8; - float rotation_vector_y = 9; - float rotation_vector_z = 10; - float rotation_vector_w = 11; - - // Integer - int32 step_count = 12; +message IMUReading { + // milliseconds + int64 relative_timestamp = 1; + // Accelerometer [m/s^2] + Vector3 acc = 2; + + // Gyroscope [radians/s] + Vector3 gyr = 3; + + // Orientation [unitless], 4 components should square sum to ~1 + Quaternion rotation_vector = 4; + + // Number of steps so far + int32 step_count = 5; } + +message MagnetometerReading { + int64 relative_timestamp = 1; -message Position_Sample { - int64 relative_timestamp = 1; + // Magnetometer [uT] + Vector3 mag = 2; +} + +message BarometerReading { + int64 relative_timestamp = 1; - // uT - float mag_x = 2; - float mag_y = 3; - float mag_z = 4; + // mbar + float pressure = 2; } -message Pressure_Sample { - int64 relative_timestamp = 1; +message LightReading { + int64 relative_timestamp = 1; + // lux + float light = 2; +} - // mbar - float pressure = 2; +message ProximityReading { + int64 relative_timestamp = 1; + // cm + float distance = 2; +} +message GNSSPosition { + int64 relative_timestamp = 1; + + // degrees (minimum 6 significant figures) + // latitude between -90 and 90 + double latitude = 2; + // longitude between -180 and 180 + double longitude = 3; + //metres + double altitude = 4; + // floor name + optional string floor = 5; } -message Light_Sample { - int64 relative_timestamp = 1; - // lux - float light = 2; + +message GNSSReading { + GNSSPosition position = 1; + // metres + float accuracy = 2; + // m/s + float speed = 3; + // degrees + float bearing = 4; + + // e.g 'gps' or 'network' + string provider = 5; } -message GNSS_Sample { - int64 relative_timestamp = 1; - // degrees (minimum 6 significant figures) - // latitude between -90 and 90 - float latitude = 2; +message Fingerprint { + int64 relative_timestamp = 1; + repeated RFScan rf_scans = 2; - // longitude between -180 and 180 - float longitude = 3; +} - //metres - float altitude = 4; +message RFScan { + int64 relative_timestamp = 1; - // metres - float accuracy = 5; + // Integer encoding of the hex mac address (BSSID) + // e.g. 207394925843984 + int64 mac = 2; - // m/s - float speed = 6; + // rssi integer in dBm. + // typically between -120 and -10 + int32 rssi = 3; - // e.g 'gps' or 'network' - string provider = 7; + // returned position + optional GNSSPosition position = 4; } -message WiFi_Sample { - int64 relative_timestamp = 1; - repeated Mac_Scan mac_scans = 2; - +message WiFiRTTReading { + int64 relative_timestamp = 1; + // cm + // Integer encoding of the hex mac address (BSSID) + // e.g. 207394925843984 + int64 mac = 2; + + // in mm + float distance = 3; + // in mm + float distance_std = 4; + // rssi integer in dBm. + // typically between -120 and -10 + int32 rssi = 5; } -message Mac_Scan { - int64 relative_timestamp = 1; +message WiFiAPData { + // Integer encoding of the hex mac address (BSSID) + // e.g. 207394925843984 + int64 mac = 1; - // Integer encoding of the hex mac address - // e.g. 207394925843984 - int64 mac = 2; + // E.g. 'Eduroam' or 'Starbucks_free_wifi' + string ssid = 2; - // rssi integer in dBm. - // typically between -120 and -10 - int32 rssi = 3; + // Typically 2.4GHz or 5GHz + int64 frequency = 3; + + // Flag to indicate if the AP supports RTT measurements + bool rtt_enabled = 4; } -message AP_Data { - // Integer encoding of the hex mac address - // e.g. 207394925843984 - int64 mac = 1; +message BleData { + string mac_address = 1; + string name = 2; + int32 tx_power_level = 3; + int32 advertise_flags = 4; + repeated string service_uuids = 5; + bytes manufacturer_data = 6; +} - // E.g. 'Eduroam' or 'Starbucks_free_wifi' - string ssid = 2; + // --- Common Types --- +message Vector3 { + float x = 1; + float y = 2; + float z = 3; +} - // Typically 2.4GHz or 5GHz - int64 frequency = 3; +message Quaternion { + float x = 1; + float y = 2; + float z = 3; + float w = 4; } -message Sensor_Info { - string name = 1; - string vendor = 2; - float resolution = 3; - float power = 4; - int32 version = 5; - int32 type = 6; +message SensorInfo { + string name = 1; + string vendor = 2; + float resolution = 3; + float power = 4; + int32 version = 5; + int32 type = 6; + float max_range = 7; + float frequency = 8; } \ No newline at end of file diff --git a/app/src/main/res/drawable/drawer_handle_bg.xml b/app/src/main/res/drawable/drawer_handle_bg.xml new file mode 100644 index 00000000..32f158f4 --- /dev/null +++ b/app/src/main/res/drawable/drawer_handle_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/nucleusground.png b/app/src/main/res/drawable/nucleusground.png deleted file mode 100644 index 11502ce1..00000000 Binary files a/app/src/main/res/drawable/nucleusground.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0a3ceda2..5550c55f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,11 +1,10 @@ - @@ -17,13 +16,12 @@ android:elevation="4dp" android:title="@string/app_name" android:titleTextColor="@color/md_theme_light_onPrimary" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" app:popupTheme="@style/Widget.MaterialComponents.PopupMenu" /> - + app:layout_constraintTop_toBottomOf="@id/main_toolbar" + app:navGraph="@navigation/main_nav" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 99c1ef13..2a15199d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -10,8 +10,8 @@ + android:background="@color/md_theme_background" + android:padding="24dp"> + app:layout_constraintTop_toTopOf="parent" /> + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/sloganText"> + + + + + + + + + + + + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/venueCard" /> - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/gnssStatusTextView"> + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/mapCardView" + app:rowCount="2"> - + + app:layout_constraintTop_toBottomOf="@id/buttonGrid" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_maps.xml b/app/src/main/res/layout/fragment_maps.xml new file mode 100644 index 00000000..8a7ec617 --- /dev/null +++ b/app/src/main/res/layout/fragment_maps.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_measurements.xml b/app/src/main/res/layout/fragment_measurements.xml index 640af10d..cc9603a1 100644 --- a/app/src/main/res/layout/fragment_measurements.xml +++ b/app/src/main/res/layout/fragment_measurements.xml @@ -566,6 +566,20 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + - + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + diff --git a/app/src/main/res/layout/fragment_recording.xml b/app/src/main/res/layout/fragment_recording.xml index c04381a5..4c01c301 100644 --- a/app/src/main/res/layout/fragment_recording.xml +++ b/app/src/main/res/layout/fragment_recording.xml @@ -2,20 +2,33 @@ + android:layout_height="match_parent" + android:background="@color/md_theme_background"> - - + + + + @@ -24,122 +37,555 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:padding="12dp" + android:padding="10dp" android:gravity="center_vertical"> - - - - - - - - + android:textColor="@color/md_theme_onPrimaryContainer" /> - - + android:text="@string/change_venue" + android:textSize="11sp" + android:visibility="visible" /> - + - - + + - - + + + + + + + + + + + + + + + + + - - + + - - + + + + android:scrollbars="none" + android:fadeScrollbars="true"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml index 9d966b29..e50b9687 100644 --- a/app/src/main/res/navigation/main_nav.xml +++ b/app/src/main/res/navigation/main_nav.xml @@ -32,6 +32,15 @@ app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right"/> + + + + + + + diff --git a/app/src/main/res/values/googlemaps_api.xml b/app/src/main/res/values/googlemaps_api.xml index 80672c61..0352fe3c 100644 --- a/app/src/main/res/values/googlemaps_api.xml +++ b/app/src/main/res/values/googlemaps_api.xml @@ -1,6 +1,5 @@ - - AIzaSyAGqo26Wz1SUnGYP3TDDxLSDNK-EvHHtNc - + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2fa6043..2762f50a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,7 +45,7 @@ Elevation: %1s Computed Avg. Step Length unit: cm - Long press and drag the marker to your start location + Your start location is set automatically. It cannot be modified manually. Zoom, scroll and rotate the map \n to correct the path @@ -137,4 +137,43 @@ End Exit + + + + + + 📍 Outdoor (Tap building to select venue) + Change + ← Back + + + RECORDING + Recording indicator + + + Distance: 0.00m + Elevation: 0.0m + GNSS Error: 0.00m + + + Cancel + Complete + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index e208c000..da60d2fe 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,20 @@ // Top-level build file buildscript { + ext { + agp_version = '8.7.1' + } repositories { google() mavenCentral() } dependencies { // NOTE: Only classpath deps (plugins) go here - classpath 'com.android.tools.build:gradle:8.8.0' + classpath "com.android.tools.build:gradle:$agp_version" classpath 'com.google.gms:google-services:4.4.2' def nav_version = "2.5.3" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.4" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/gradle.properties b/gradle.properties index 52f5917c..5368112b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# Use Android Studio bundled JDK (JDK 21) for Gradle builds +org.gradle.java.home=C:\\Program Files\\Android\\Android Studio\\jbr # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects