diff --git a/.gitignore b/.gitignore index d4c3a57e..3a605d87 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,13 @@ .cxx local.properties /.idea/ +secrets.properties +app/google-services.json +*.jks +*.keystore +keystore.properties +/.android-local/ +/.gradle-local/ +/app/build/ +/tmp_pdf/ +/_zip_ref/ diff --git a/app/.gitignore b/app/.gitignore index 42afabfd..2f67ba3e 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,12 @@ -/build \ No newline at end of file +/build +# NEW: do not commit secrets +secrets.properties + +# NEW: do not commit generated/build outputs +**/build/ + +# NEW: do not commit generated protobuf Java +**/Traj.java + +# NEW: old proto should not be committed (we use new_traj.proto) +app/src/main/proto/traj.proto diff --git a/app/build.gradle b/app/build.gradle index 3e29b13f..be078607 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'com.google.gms.google-services' id 'androidx.navigation.safeargs' + id 'com.google.protobuf' id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' } @@ -34,12 +35,6 @@ android { "\"${localProperties['OPENPOSITIONING_MASTER_KEY'] ?: ''}\"" } - buildFeatures { - // For example: - // compose true // if you want Jetpack Compose - // viewBinding true - } - buildFeatures { buildConfig true } @@ -55,8 +50,35 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } + configurations { + all*.exclude group: 'com.google.protobuf', module: 'protobuf-javalite' + } + + sourceSets { + main { + proto { + + srcDir 'src/main/proto' + } + } + } } +protobuf { + protoc { + + artifact = "com.google.protobuf:protoc:3.21.7" + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + + } + } + } + } +} dependencies { // Core AndroidX implementation 'androidx.appcompat:appcompat:1.7.0-alpha03' // or stable: 1.6.1 @@ -68,14 +90,11 @@ dependencies { implementation 'androidx.gridlayout:gridlayout:1.0.0' // Material Components (Material 3 support is in 1.12.0+) - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.2.1' - 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.squareup.okhttp3:okhttp:4.10.0' - implementation "com.google.protobuf:protobuf-java-util:3.0.0" + implementation "com.google.protobuf:protobuf-java:3.21.7" + implementation "com.google.protobuf:protobuf-java-util:3.21.7" implementation "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" implementation 'com.google.android.gms:play-services-maps:19.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 678711fd..eae4aae3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,7 +43,7 @@ If you plan to adopt scoped storage fully and store only within your app-specific directories (or MediaStore for shared media), consider removing READ/WRITE_EXTERNAL_STORAGE eventually. If you still need broad file access (for example, to migrate existing user data), keep them - short-term but note that starting with Android 11 (API 30), they donโ€™t allow the same broad file access. + short-term but note that starting with Android 11 (API 30), they don't allow the same broad file access. --> @@ -84,7 +84,7 @@ + android:value="AIzaSyAncwR5gNfJ-zJf1gtlVVRZ1SQyXfvr9pk"/> 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/remote/Building.java b/app/src/main/java/com/openpositioning/PositionMe/data/remote/Building.java new file mode 100644 index 00000000..0415395a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/data/remote/Building.java @@ -0,0 +1,27 @@ +package com.openpositioning.PositionMe.data.remote; + +import java.util.List; + +/** + * Data model representing a Building. + * Contains the building's metadata, outline (polygon), and a list of floor plans. + */ +public class Building { + private String id; + private String name; + // Outline points: List of [Latitude, Longitude] + private List> outline; + private List floors; + + public Building(String id, String name, List> outline, List floors) { + this.id = id; + this.name = name; + this.outline = outline; + this.floors = floors; + } + + public String getId() { return id; } + public String getName() { return name; } + public List> getOutline() { return outline; } + public List getFloors() { return floors; } +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/remote/FloorPlan.java b/app/src/main/java/com/openpositioning/PositionMe/data/remote/FloorPlan.java new file mode 100644 index 00000000..6d28ac03 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/data/remote/FloorPlan.java @@ -0,0 +1,45 @@ +package com.openpositioning.PositionMe.data.remote; + +import java.util.List; + +/** + * Data model representing a single floor plan. + */ +public class FloorPlan { + private String floorCode; // e.g., "1", "G", "B1" + private int order; + private String imageUrl; // Kept for compatibility with older APIs. + private double[] bounds; + + private List>> walls; + // Indoor stair geometry. + private List>> stairs; + // Indoor lift geometry. + private List>> lifts; + + public FloorPlan(String floorCode, + int order, + String imageUrl, + double[] bounds, + List>> walls, + List>> stairs, + List>> lifts) { + this.floorCode = floorCode; + this.order = order; + this.imageUrl = imageUrl; + this.bounds = bounds; + this.walls = walls; + this.stairs = stairs; + this.lifts = lifts; + } + + public String getFloorCode() { return floorCode; } + public int getOrder() { return order; } + public String getImageUrl() { return imageUrl; } + public double[] getBounds() { return bounds; } + public List>> getWalls() { return walls; } + // Returns stair geometry for the floor. + public List>> getStairs() { return stairs; } + // Returns lift geometry for the floor. + public List>> getLifts() { return lifts; } +} 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..b5dcb615 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 @@ -1,365 +1,210 @@ package com.openpositioning.PositionMe.data.remote; -import android.util.Log; -import java.util.Map; -import java.util.HashMap; -import java.util.Iterator; -import java.io.BufferedReader; -import java.io.FileReader; -import org.json.JSONObject; - -import android.os.Environment; - -import java.io.FileInputStream; -import java.io.OutputStream; import android.content.Context; import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.util.Log; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; -import com.google.protobuf.util.JsonFormat; -import com.openpositioning.PositionMe.BuildConfig; import com.openpositioning.PositionMe.Traj; -import com.openpositioning.PositionMe.presentation.fragment.FilesFragment; +import com.google.protobuf.util.JsonFormat; import com.openpositioning.PositionMe.presentation.activity.MainActivity; +import com.openpositioning.PositionMe.presentation.fragment.FilesFragment; import com.openpositioning.PositionMe.sensors.Observable; import com.openpositioning.PositionMe.sensors.Observer; +import com.openpositioning.PositionMe.utils.VenueSelectionHelper; + +import org.json.JSONObject; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; -import java.util.zip.ZipEntry; +import java.util.Map; +import java.util.Locale; +import java.util.regex.Pattern; import java.util.zip.ZipInputStream; +import java.util.zip.ZipEntry; import okhttp3.Call; import okhttp3.Callback; -import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.MultipartBody; -import okhttp3.OkHttp; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import org.json.JSONArray; +import org.json.JSONException; +import java.util.Collections; + /** - * This class handles communications with the server through HTTPs. The class uses an - * {@link OkHttpClient} for making requests to the server. The class includes methods for sending - * a recorded trajectory, uploading locally-stored trajectories, downloading trajectories from the - * server and requesting information about the uploaded trajectories. - * - * Keys and URLs are hardcoded strings, given the simple and academic nature of the project. + * Handles communications with the server through HTTPs. * - * @author Michal Dvorak - * @author Mate Stodulka + * New code guide: + * 1. Campaign-aware upload entry points for live and local trajectories. + * 2. Compatibility upload fallback strategy for server payload formats. + * 3. Download helpers for ZIP trajectory exports and local record tracking. + * 4. Building and floor-plan requests for indoor map rendering. */ public class ServerCommunications implements Observable { + + private static final String TAG = "ServerCommunications"; + private static final String LEGACY_UPLOAD_CAMPAIGN = "murchison_house"; + private static final MediaType PROTO_MEDIA_TYPE = MediaType.parse("application/octet-stream"); + private static final double MIN_SERVER_UPLOAD_DURATION_SEC = 30.0; + + public static Map downloadRecords = new HashMap<>(); - // Application context for handling permissions and devices private final Context context; - - // Network status checking - private ConnectivityManager connMgr; - private boolean isWifiConn; - private boolean isMobileConn; - private SharedPreferences settings; + private Traj.Trajectory trajectory; private String infoResponse; private boolean success; private List observers; - // 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 userKey = "LY31NlnGAe9vN-HvQJWTZg"; + private static final String masterKey = "ewireless"; + + private static final String uploadFallbackURL = "https://openpositioning.org/api/live/trajectory/upload/" + userKey + "/?key=" + masterKey; - private static final String downloadURL = - "https://openpositioning.org/api/live/trajectory/download/" + userKey - + "?skip=0&limit=30&key=" + masterKey; + private static final String downloadBaseURL = + "https://openpositioning.org/api/live/trajectory/download/" + userKey; 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 static final String floorPlanRequestURL = + "https://openpositioning.org/api/live/floorplan/request/" + userKey + + "?key=" + masterKey; + private static final String PROTOCOL_ACCEPT_TYPE = "application/json"; - /** - * Public default constructor of {@link ServerCommunications}. The constructor saves context, - * initialises a {@link ConnectivityManager}, {@link Observer} and gets the user preferences. - * Boolean variables storing WiFi and Mobile Data connection status are initialised to false. - * - * @param context application context for handling permissions and devices. - */ public ServerCommunications(Context context) { this.context = context; - this.connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - this.settings = PreferenceManager.getDefaultSharedPreferences(context); - this.isWifiConn = false; - this.isMobileConn = false; - checkNetworkStatus(); - this.observers = new ArrayList<>(); } + public void sendInfo(Traj.Trajectory trajectory) { + this.trajectory = trajectory; + } + /** - * Outgoing communication request with a {@link Traj trajectory} object. The recorded - * trajectory is passed to the method. It is processed into the right format for sending - * to the API server. - * - * @param trajectory Traj object matching all the timing and formal restrictions. + * Uploads a freshly recorded trajectory using the selected venue campaign. */ - public void sendTrajectory(Traj.Trajectory trajectory){ - logDataSize(trajectory); - - // Convert the trajectory to byte array - byte[] binaryTrajectory = trajectory.toByteArray(); - - File path = null; - // for android 13 or higher use dedicated external storage - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - path = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); - if (path == null) { - path = context.getFilesDir(); - } - } else { // for android 12 or lower use internal storage - path = context.getFilesDir(); + public void sendTrajectory(Traj.Trajectory sentTrajectory, String campaign) { + String validationMessage = validateTrajectoryForServerUpload(sentTrajectory); + if (validationMessage != null) { + success = false; + infoResponse = validationMessage; + Log.w(TAG, infoResponse); + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, infoResponse, Toast.LENGTH_LONG).show()); + return; } - System.out.println(path.toString()); - - // Format the file name according to date - SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yy-HH-mm-ss"); - Date date = new Date(); - File file = new File(path, "trajectory_" + dateFormat.format(date) + ".txt"); + String resolvedCampaign = resolveCampaign(campaign); + Log.i(TAG, "Uploading trajectory to campaign=" + resolvedCampaign + + " (test_points=" + sentTrajectory.getTestPointsCount() + ")"); + String fileName = "upload_" + System.currentTimeMillis() + ".proto"; + byte[] rawBytes = sentTrajectory.toByteArray(); + byte[] compatBytes = null; try { - // Write the binary data to the file - FileOutputStream stream = new FileOutputStream(file); - stream.write(binaryTrajectory); - stream.close(); - System.out.println("Recorded binary trajectory for debugging stored in: " + path); - } catch (IOException ee) { - // Catch and print if writing to the file fails - System.err.println("Storing of recorded binary trajectory failed: " + ee.getMessage()); + compatBytes = TrajectoryUploadCompat.toServerBytes(sentTrajectory); + } catch (Exception e) { + Log.w(TAG, "Compatibility payload generation failed for live upload", e); } - // 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 - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", file.getName(), - RequestBody.create(MediaType.parse("text/plain"), file)) - .build(); - - // Create a POST request with the required headers - 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"); - // Delete the local file and set success to false - //file.delete(); - success = false; - notifyObservers(1); - } - - private void copyFile(File src, File dst) throws IOException { - try (InputStream in = new FileInputStream(src); - OutputStream out = new FileOutputStream(dst)) { - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - } - } - - // Process the server's response - @Override public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - // If the response is unsuccessful, delete the local file and throw an - // exception - if (!response.isSuccessful()) { - //file.delete(); -// System.err.println("POST error response: " + responseBody.string()); - - String errorBody = responseBody.string(); - infoResponse = "Upload failed: " + errorBody; - new Handler(Looper.getMainLooper()).post(() -> - Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); // show error message to users - - System.err.println("POST error response: " + errorBody); - success = false; - notifyObservers(1); - throw new IOException("Unexpected code " + response); - } - - // 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)); - } - // Print a confirmation of a successful POST to API - System.out.println("Successful post response: " + responseBody.string()); - - System.out.println("Get file: " + file.getName()); - String originalPath = file.getAbsolutePath(); - System.out.println("Original trajectory file saved at: " + originalPath); - - // Copy the file to the Downloads folder - File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - File downloadFile = new File(downloadsDir, file.getName()); - try { - copyFile(file, downloadFile); - System.out.println("Trajectory file copied to Downloads: " + downloadFile.getAbsolutePath()); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Failed to copy file to Downloads: " + e.getMessage()); - } + List attempts = buildUploadAttempts(resolvedCampaign, rawBytes, compatBytes); + executeUploadAttempt(attempts, 0, fileName, null); + } - // Delete local file and set success to true - success = file.delete(); - notifyObservers(1); - } - } - }); - } - else { - // If the device is not connected to network or allowed to send, do not send trajectory - // and notify observers and user - System.err.println("No uploading allowed right now!"); - success = false; - notifyObservers(1); - } + /** Uploads a local trajectory file; calls onSuccess on main thread if upload succeeds. */ + public void uploadLocalTrajectory(File localTrajectory) { + uploadLocalTrajectory(localTrajectory, null); } /** - * Uploads a local trajectory file to the API server in the specified format. - * {@link OkHttp} library is used for the asynchronous POST request. - * - * @param localTrajectory the File object of the local trajectory to be uploaded + * Uploads a local trajectory file to the API server. + * @param onSuccess Optional Runnable invoked on the main thread after a successful upload. */ - public void uploadLocalTrajectory(File localTrajectory) { - - // Instantiate client for HTTP requests - OkHttpClient client = new OkHttpClient(); - - // robustness improvement - RequestBody fileRequestBody; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - try { - byte[] fileBytes = Files.readAllBytes(localTrajectory.toPath()); - fileRequestBody = RequestBody.create(MediaType.parse("text/plain"), fileBytes); - } catch (IOException e) { - e.printStackTrace(); - // if failed, use File object to construct RequestBody - fileRequestBody = RequestBody.create(MediaType.parse("text/plain"), localTrajectory); - } - } else { - fileRequestBody = RequestBody.create(MediaType.parse("text/plain"), localTrajectory); + public void uploadLocalTrajectory(File localTrajectory, Runnable onSuccess) { + if (localTrajectory == null) { + success = false; + infoResponse = "Upload failed: localTrajectory is null"; + Log.e(TAG, infoResponse); + notifyObservers(1); + return; } - // Create request body with a file to upload in multipart/form-data format - RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", localTrajectory.getName(), fileRequestBody) - .build(); + String resolvedCampaign = resolveCampaign(null); + Log.i(TAG, "Uploading local trajectory " + localTrajectory.getName() + + " to campaign=" + resolvedCampaign); - // Create a POST request with the required headers - okhttp3.Request request = new okhttp3.Request.Builder().url(uploadURL).post(requestBody) - .addHeader("accept", PROTOCOL_ACCEPT_TYPE) - .addHeader("Content-Type", PROTOCOL_CONTENT_TYPE).build(); + byte[] rawBytes; + byte[] compatBytes = null; + try { + rawBytes = readFileBytes(localTrajectory); + } catch (IOException e) { + Log.e(TAG, "Failed to read local trajectory for upload", e); + success = false; + infoResponse = "Upload failed: could not prepare trajectory payload"; + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); + return; + } - // Enqueue the request to be executed asynchronously and handle the response - client.newCall(request).enqueue(new okhttp3.Callback() { - @Override - public void onFailure(Call call, IOException e) { - // Print error message, set success to false and notify observers - e.printStackTrace(); -// localTrajectory.delete(); + try { + Traj.Trajectory localTrajectoryProto = Traj.Trajectory.parseFrom(rawBytes); + String validationMessage = validateTrajectoryForServerUpload(localTrajectoryProto); + if (validationMessage != null) { success = false; - System.err.println("UPLOAD: Failure to get response"); + infoResponse = validationMessage; + Log.w(TAG, infoResponse); notifyObservers(1); - infoResponse = "Upload failed: " + e.getMessage(); // Store error message new Handler(Looper.getMainLooper()).post(() -> - Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); // show error message to users + Toast.makeText(context, infoResponse, Toast.LENGTH_LONG).show()); + return; } + } catch (IOException e) { + Log.w(TAG, "Failed to parse local trajectory for validation; continuing upload attempt", e); + } - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) { - // Print error message, set success to false and throw an exception - success = false; -// System.err.println("UPLOAD unsuccessful: " + responseBody.string()); - notifyObservers(1); -// localTrajectory.delete(); - assert responseBody != null; - String errorBody = responseBody.string(); - System.err.println("UPLOAD unsuccessful: " + errorBody); - infoResponse = "Upload failed: " + errorBody; - new Handler(Looper.getMainLooper()).post(() -> - Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); - throw new IOException("UPLOAD failed with code " + response); - } - - // 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)); - } - - // Print a confirmation of a successful POST to API - assert responseBody != null; - System.out.println("UPLOAD SUCCESSFUL: " + responseBody.string()); + try { + compatBytes = TrajectoryUploadCompat.localFileToServerBytes(localTrajectory); + } catch (Exception e) { + Log.w(TAG, "Compatibility payload generation failed for local upload", e); + } - // Delete local file, set success to true and notify observers - success = localTrajectory.delete(); - notifyObservers(1); - } - } - }); + List attempts = buildUploadAttempts(resolvedCampaign, rawBytes, compatBytes); + executeUploadAttempt(attempts, 0, localTrajectory.getName(), onSuccess); } - /** - * Loads download records from a JSON file and updates the downloadRecords map. - * If the file exists, it reads the JSON content and populates the map. + * Loads download records from a JSON file. */ private void loadDownloadRecords() { - // Point to the app-specific Downloads folder File recordsDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); File recordsFile = new File(recordsDir, "download_records.json"); @@ -379,29 +224,17 @@ private void loadDownloadRecords() { String id = record.getString("id"); downloadRecords.put(id, record); } catch (Exception e) { - System.err.println("Error loading record with key: " + key); - e.printStackTrace(); + Log.w(TAG, "Skipping malformed download record", e); } } - - System.out.println("Loaded downloadRecords: " + downloadRecords); - } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "Failed to load download records", e); } - } else { - System.out.println("Download_records.json not found in app-specific directory."); } } /** - * Saves a download record to a JSON file. - * The method creates or updates the JSON file with the provided details. - * - * @param startTimestamp the start timestamp of the trajectory - * @param fileName the name of the file - * @param id the ID of the trajectory - * @param dateSubmitted the date the trajectory was submitted + * Saves a download record to the local JSON file. */ private void saveDownloadRecord(long startTimestamp, String fileName, String id, String dateSubmitted) { File recordsDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); @@ -409,21 +242,17 @@ private void saveDownloadRecord(long startTimestamp, String fileName, String id, JSONObject jsonObject; try { - // Ensure the directory exists if (recordsDir != null && !recordsDir.exists()) { recordsDir.mkdirs(); } - // If the file does not exist, create it if (!recordsFile.exists()) { if (recordsFile.createNewFile()) { jsonObject = new JSONObject(); } else { - System.err.println("Failed to create file: " + recordsFile.getAbsolutePath()); return; } } else { - // Read the existing contents StringBuilder jsonBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new FileReader(recordsFile))) { String line; @@ -431,108 +260,84 @@ private void saveDownloadRecord(long startTimestamp, String fileName, String id, jsonBuilder.append(line); } } - // If file is empty or invalid JSON, use a fresh JSONObject - jsonObject = jsonBuilder.length() > 0 - ? new JSONObject(jsonBuilder.toString()) - : new JSONObject(); + jsonObject = jsonBuilder.length() > 0 ? new JSONObject(jsonBuilder.toString()) : new JSONObject(); } - // Create the new record details JSONObject recordDetails = new JSONObject(); recordDetails.put("file_name", fileName); recordDetails.put("startTimeStamp", startTimestamp); recordDetails.put("date_submitted", dateSubmitted); recordDetails.put("id", id); - // Insert or update in the main JSON jsonObject.put(id, recordDetails); - // Write updated JSON to file try (FileWriter writer = new FileWriter(recordsFile)) { writer.write(jsonObject.toString(4)); writer.flush(); } - - System.out.println("Download record saved successfully at: " + recordsFile.getAbsolutePath()); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Error saving download record: " + e.getMessage()); + Log.e(TAG, "Failed to save download record", e); } } /** - * 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. - * - * @param position the position of the trajectory in the zip file to retrieve - * @param id the ID of the trajectory - * @param dateSubmitted the date the trajectory was submitted + * Downloads a specific trajectory from the server and saves it as a JSON text file. */ public void downloadTrajectory(int position, String id, String dateSubmitted) { - loadDownloadRecords(); // Load existing records from app-specific directory - - // Initialise OkHttp client + loadDownloadRecords(); OkHttpClient client = new OkHttpClient(); + String downloadUrl = buildDownloadUrl(position); - // Create GET request with required header - okhttp3.Request request = new okhttp3.Request.Builder() - .url(downloadURL) + Request request = new Request.Builder() + .url(downloadUrl) .addHeader("accept", PROTOCOL_ACCEPT_TYPE) .get() .build(); - // Enqueue the GET request for asynchronous execution - client.newCall(request).enqueue(new okhttp3.Callback() { + client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { - e.printStackTrace(); + Log.e(TAG, "downloadTrajectory failed", e); } @Override 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; - } - zipCount++; + if (!response.isSuccessful()) { + Log.e(TAG, "downloadTrajectory HTTP error: " + response.code()); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Download failed: HTTP " + response.code(), Toast.LENGTH_SHORT).show()); + if (response.body() != null) response.body().close(); + return; + } + InputStream inputStream = null; + ZipInputStream zipInputStream = null; + ByteArrayOutputStream byteArrayOutputStream = null; + try { + inputStream = response.body().byteStream(); + zipInputStream = new ZipInputStream(inputStream); + ZipEntry zipEntry = findMatchingZipEntry(zipInputStream, id, position); + if (zipEntry == null) { + Log.e(TAG, "downloadTrajectory: position " + position + + " / id " + id + " not found in ZIP from " + downloadUrl); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Download failed: trajectory not found", Toast.LENGTH_SHORT).show()); + return; } - // Initialise a byte array output stream - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - // Read the zipped data and write it to the byte array output stream + byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = zipInputStream.read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } - - // Convert the byte array to protobuf byte[] byteArray = byteArrayOutputStream.toByteArray(); - Traj.Trajectory receivedTrajectory = Traj.Trajectory.parseFrom(byteArray); - - // Inspect the size of the received trajectory + Traj.Trajectory receivedTrajectory = TrajectoryUploadCompat.parseServerBytes(byteArray); logDataSize(receivedTrajectory); - // Print a message in the console long startTimestamp = receivedTrajectory.getStartTimestamp(); - String fileName = "trajectory_" + dateSubmitted + ".txt"; + String fileName = buildDownloadedTrajectoryFileName(id, dateSubmitted); - // Place the file in your app-specific "Downloads" folder File appSpecificDownloads = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (appSpecificDownloads != null && !appSpecificDownloads.exists()) { appSpecificDownloads.mkdirs(); @@ -543,125 +348,547 @@ 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()); - } 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(); } - // Save the download record saveDownloadRecord(startTimestamp, fileName, id, dateSubmitted); loadDownloadRecords(); + + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Download complete", Toast.LENGTH_SHORT).show()); + + } catch (Exception e) { + Log.e(TAG, "downloadTrajectory processing failed", e); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, "Download failed: " + e.getMessage(), Toast.LENGTH_SHORT).show()); + } finally { + if (byteArrayOutputStream != null) try { byteArrayOutputStream.close(); } catch (IOException ignored) {} + if (zipInputStream != null) try { zipInputStream.close(); } catch (IOException ignored) {} + if (inputStream != null) try { inputStream.close(); } catch (IOException ignored) {} + if (response.body() != null) response.body().close(); + } + } + }); + } + + private ZipEntry findMatchingZipEntry(ZipInputStream zipInputStream, String id, int fallbackPosition) + throws IOException { + if (zipInputStream == null) { + return null; + } + + String expectedName = id == null ? null : id + ".pkt"; + ZipEntry zipEntry; + int zipCount = 0; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + String entryName = zipEntry.getName(); + if (expectedName != null && expectedName.equals(entryName)) { + Log.i(TAG, "Matched download ZIP entry by id: " + entryName); + return zipEntry; + } + if (expectedName == null && zipCount == fallbackPosition) { + Log.w(TAG, "Falling back to ZIP entry position " + fallbackPosition); + return zipEntry; + } + zipCount++; + } + return null; + } + + private String buildDownloadedTrajectoryFileName(String id, String dateSubmitted) { + String safeId = sanitizeFileComponent(id, "trajectory"); + String safeDate = sanitizeFileComponent(dateSubmitted, "download"); + return String.format(Locale.US, "trajectory_%s_%s.txt", safeId, safeDate); + } + + private String sanitizeFileComponent(String value, String fallback) { + if (value == null || value.trim().isEmpty()) { + return fallback; + } + String sanitized = Pattern.compile("[^\\p{L}\\p{N}._-]+") + .matcher(value.trim()) + .replaceAll("_"); + sanitized = sanitized.replaceAll("_+", "_"); + sanitized = sanitized.replaceAll("^[_ .-]+|[_ .-]+$", ""); + return sanitized.isEmpty() ? fallback : sanitized; + } + + private String resolveCampaign(String campaign) { + String resolvedCampaign = campaign; + if (resolvedCampaign == null || resolvedCampaign.trim().isEmpty()) { + resolvedCampaign = VenueSelectionHelper.getSelectedCampaign(context); + } + if (resolvedCampaign == null || resolvedCampaign.trim().isEmpty()) { + return LEGACY_UPLOAD_CAMPAIGN; + } + if (VenueSelectionHelper.DEFAULT_CAMPAIGN.equals(resolvedCampaign)) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( + context.getApplicationContext()); + String storedCampaign = prefs.getString(VenueSelectionHelper.PREF_CURRENT_CAMPAIGN, null); + String storedBuilding = prefs.getString(VenueSelectionHelper.PREF_SELECTED_BUILDING, null); + boolean hasExplicitVenue = (storedCampaign != null && !storedCampaign.trim().isEmpty()) + || (storedBuilding != null && !storedBuilding.trim().isEmpty()); + if (!hasExplicitVenue) { + return LEGACY_UPLOAD_CAMPAIGN; + } + } + return resolvedCampaign; + } + + private String buildCampaignUploadUrl(String campaign) { + String resolvedCampaign = resolveCampaign(campaign); + return "https://openpositioning.org/api/live/trajectory/upload/" + + resolvedCampaign + "/" + userKey + "/?key=" + masterKey; + } + + private String buildDownloadUrl(int position) { + int limit = Math.max(position + 1, 30); + return downloadBaseURL + "?skip=0&limit=" + limit + "&key=" + masterKey; + } + + // Builds a retry chain across current and legacy-compatible upload payloads. + private List buildUploadAttempts(String preferredCampaign, byte[] rawBytes, + byte[] compatBytes) { + List attempts = new ArrayList<>(); + addUploadAttempt(attempts, preferredCampaign, rawBytes, "raw/current"); + if (compatBytes != null) { + addUploadAttempt(attempts, preferredCampaign, compatBytes, "compat/legacy"); + } + if (!LEGACY_UPLOAD_CAMPAIGN.equals(preferredCampaign)) { + addUploadAttempt(attempts, LEGACY_UPLOAD_CAMPAIGN, rawBytes, "raw/current fallback"); + if (compatBytes != null) { + addUploadAttempt(attempts, LEGACY_UPLOAD_CAMPAIGN, compatBytes, + "compat/legacy fallback"); + } + } + return attempts; + } + + private void addUploadAttempt(List attempts, String campaign, byte[] payload, + String label) { + String resolvedCampaign = resolveCampaign(campaign); + for (UploadAttempt existing : attempts) { + if (existing.campaign.equals(resolvedCampaign) + && Arrays.equals(existing.payload, payload)) { + return; + } + } + attempts.add(new UploadAttempt(resolvedCampaign, payload, label)); + } + + // Tries each upload strategy in sequence until one is accepted by the API. + private void executeUploadAttempt(List attempts, int attemptIndex, String fileName, + Runnable onSuccess) { + if (attemptIndex >= attempts.size()) { + success = false; + infoResponse = "Upload failed: no upload strategy succeeded"; + Log.e(TAG, infoResponse); + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); + return; + } + + UploadAttempt attempt = attempts.get(attemptIndex); + Log.i(TAG, "Upload attempt " + (attemptIndex + 1) + "/" + attempts.size() + + " using campaign=" + attempt.campaign + + " payload=" + attempt.label + + " bytes=" + attempt.payload.length); + + OkHttpClient client = new OkHttpClient(); + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileName, + RequestBody.create(PROTO_MEDIA_TYPE, attempt.payload)) + .build(); + + Request request = new Request.Builder() + .url(buildCampaignUploadUrl(attempt.campaign)) + .post(requestBody) + .addHeader("accept", PROTOCOL_ACCEPT_TYPE) + .build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + Log.e(TAG, "Upload failed on attempt " + (attemptIndex + 1), e); + success = false; + infoResponse = "Upload failed: " + e.getClass().getSimpleName() + " - " + e.getMessage(); + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + String bodyStr = null; + try (ResponseBody responseBody = response.body()) { + bodyStr = (responseBody != null) ? responseBody.string() : null; + if (response.isSuccessful()) { + success = true; + infoResponse = "Upload successful!"; + Log.i(TAG, "Upload succeeded on attempt " + (attemptIndex + 1) + + ": HTTP " + response.code()); + if (bodyStr != null && !bodyStr.trim().isEmpty()) { + Log.i(TAG, "Upload response body: " + bodyStr); + } + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show(); + if (onSuccess != null) { + onSuccess.run(); + } + }); + return; + } + + String failureMessage = "Upload failed: HTTP " + response.code() + + " " + response.message(); + if (bodyStr != null && !bodyStr.trim().isEmpty()) { + Log.e(TAG, failureMessage + ", body=" + bodyStr); + } else { + Log.e(TAG, failureMessage); + } + + if (attemptIndex + 1 < attempts.size()) { + Log.w(TAG, "Retrying upload with next compatibility strategy"); + executeUploadAttempt(attempts, attemptIndex + 1, fileName, onSuccess); + return; + } + + success = false; + infoResponse = failureMessage; + notifyObservers(1); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(context, infoResponse, Toast.LENGTH_SHORT).show()); } } }); + } + + private byte[] readFileBytes(File file) throws IOException { + try (InputStream inputStream = new FileInputStream(file); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray(); + } + } + + // Rejects uploads that are known to fail the server-side minimum-duration check. + private String validateTrajectoryForServerUpload(Traj.Trajectory trajectory) { + if (trajectory == null) { + return "Upload failed: trajectory is empty"; + } + double imuDurationSec = calculateDurationSec( + trajectory.getImuDataCount(), + index -> trajectory.getImuData(index).getRelativeTimestamp()); + if (imuDurationSec >= MIN_SERVER_UPLOAD_DURATION_SEC) { + return null; + } + return "Upload skipped: server requires at least " + + String.format(java.util.Locale.US, "%.0f", MIN_SERVER_UPLOAD_DURATION_SEC) + + "s of recorded trajectory data."; + } + + private double calculateDurationSec(int count, TimestampProvider timestampProvider) { + if (count <= 1) { + return 0.0; + } + long startTs = timestampProvider.getTimestamp(0); + long endTs = timestampProvider.getTimestamp(count - 1); + return Math.max(0L, endTs - startTs) / 1000.0; + } + + private static final class UploadAttempt { + private final String campaign; + private final byte[] payload; + private final String label; + + private UploadAttempt(String campaign, byte[] payload, String label) { + this.campaign = campaign; + this.payload = payload; + this.label = label; + } + } + private interface TimestampProvider { + long getTimestamp(int index); } /** - * API request for information about submitted trajectories. If the response is successful, - * the {@link ServerCommunications#infoResponse} field is updated and observes notified. - * + * Requests information about all submitted trajectories from the server. */ public void sendInfoRequest() { - // Create a new OkHttpclient OkHttpClient client = new OkHttpClient(); - - // Create GET info request with appropriate URL and header - okhttp3.Request request = new okhttp3.Request.Builder() + Request request = new Request.Builder() .url(infoRequestURL) .addHeader("accept", PROTOCOL_ACCEPT_TYPE) .get() .build(); - // Enqueue the GET request for asynchronous execution - client.newCall(request).enqueue(new okhttp3.Callback() { - @Override public void onFailure(Call call, IOException e) { - e.printStackTrace(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + Log.e(TAG, "sendInfoRequest failed", e); } - @Override public void onResponse(Call call, Response response) throws IOException { + @Override + public void onResponse(Call call, Response response) throws IOException { try (ResponseBody responseBody = response.body()) { - // Check if the response is successful - if (!response.isSuccessful()) throw new IOException("Unexpected code " + - response); - - // Get the requested information from the response body and save it in a string - // TODO: add printing to the screen somewhere - infoResponse = responseBody.string(); - // Print a message in the console and notify observers - System.out.println("Response received"); + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + infoResponse = responseBody.string(); notifyObservers(0); } } }); } + private void logDataSize(Traj.Trajectory trajectory) { + Log.d(TAG, "Trajectory summary:" + + " imu=" + trajectory.getImuDataCount() + + " wifi=" + trajectory.getWifiFingerprintsCount() + + " gnss=" + trajectory.getGnssDataCount() + + " pdr=" + trajectory.getPdrDataCount() + + " test_points=" + trajectory.getTestPointsCount()); + } + + @Override + public void registerObserver(Observer o) { + this.observers.add(o); + } + + @Override + public void notifyObservers(int index) { + for (Observer o : observers) { + if (index == 0 && o instanceof FilesFragment) { + o.update(new String[]{infoResponse}); + } else if (index == 1 && o instanceof MainActivity) { + o.update(new Boolean[]{success}); + } + } + } + + public interface BuildingCallback { + void onBuildingsReceived(List buildings); + void onError(String message); + } + + public interface ImageCallback { + void onImageLoaded(Bitmap bitmap); + void onError(String message); + } + /** - * This method checks the device's connection status. It sets boolean variables depending on - * the type of active network connection. + * Fetches nearby buildings based on coordinates. */ - private void checkNetworkStatus() { - // Get active network information - NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); - - // Check for active connection and set flags accordingly - if (activeInfo != null && activeInfo.isConnected()) { - isWifiConn = activeInfo.getType() == ConnectivityManager.TYPE_WIFI; - isMobileConn = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE; - } else { - isWifiConn = false; - isMobileConn = false; + public void getNearbyBuildings(double lat, double lng, BuildingCallback callback) { + OkHttpClient client = new OkHttpClient(); + JSONObject jsonBody = new JSONObject(); + try { + jsonBody.put("lat", lat); + jsonBody.put("lon", lng); + jsonBody.put("macs", new JSONArray()); + } catch (JSONException e) { + Log.e(TAG, "Failed to build nearby building request", e); + return; } - } + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(JSON, jsonBody.toString()); - 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()); + Request request = new Request.Builder() + .url(floorPlanRequestURL) + .post(body) + .addHeader("accept", "application/json") + .build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + new Handler(Looper.getMainLooper()).post(() -> callback.onError(e.getMessage())); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (!response.isSuccessful()) { + String errorMsg = responseBody != null ? responseBody.string() : "Error"; + new Handler(Looper.getMainLooper()).post(() -> callback.onError(errorMsg)); + return; + } + String jsonString = responseBody.string(); + try { + List buildings = parseBuildingsJson(jsonString); + new Handler(Looper.getMainLooper()).post(() -> callback.onBuildingsReceived(buildings)); + } catch (JSONException e) { + new Handler(Looper.getMainLooper()).post(() -> callback.onError(e.getMessage())); + } + } + } + }); } /** - * {@inheritDoc} - * - * Implement default method from Observable Interface to add new observers to the list of - * registered observers. - * - * @param o Classes which implement the Observer interface to receive updates from the class. + * Downloads floor map image from the provided URL. */ - @Override - public void registerObserver(Observer o) { - this.observers.add(o); + public void downloadFloorMapImage(String url, ImageCallback callback) { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder().url(url).build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + new Handler(Looper.getMainLooper()).post(() -> callback.onError("Image Download Failed")); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful() && response.body() != null) { + InputStream inputStream = response.body().byteStream(); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + new Handler(Looper.getMainLooper()).post(() -> callback.onImageLoaded(bitmap)); + } else { + new Handler(Looper.getMainLooper()).post(() -> callback.onError("Image Response Failed")); + } + } + }); } /** - * {@inheritDoc} - * - * Method for notifying all registered observers. The observer is notified based on the index - * passed to the method. - * - * @param index Index for identifying the observer to be notified. + * Parses building JSON data including outline and floor plans. */ - @Override - public void notifyObservers(int index) { - for(Observer o : observers) { - if(index == 0 && o instanceof FilesFragment) { - o.update(new String[] {infoResponse}); + // Parses the API building payload into app-side models with ordered floor plans. + private List parseBuildingsJson(String jsonString) throws JSONException { + List buildingList = new ArrayList<>(); + JSONArray jsonArray = new JSONArray(jsonString); + + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject bObj = jsonArray.getJSONObject(i); + String id = bObj.has("id") && !bObj.isNull("id") ? bObj.getString("id") : "unknown"; + String name = bObj.has("name") ? bObj.getString("name") : "Unknown Building"; + + List> outline = new ArrayList<>(); + if (bObj.has("outline") && bObj.get("outline") instanceof String) { + extractPolygonsFromGeoJson(bObj.getString("outline"), outline); + } + + List floors = new ArrayList<>(); + if (bObj.has("map_shapes")) { + JSONObject mapShapes = new JSONObject(bObj.getString("map_shapes")); + Iterator keys = mapShapes.keys(); + while (keys.hasNext()) { + String floorCode = keys.next(); + + + List>> walls = new ArrayList<>(); + List>> stairs = new ArrayList<>(); + List>> lifts = new ArrayList<>(); + + + extractFeaturesFromGeoJson(mapShapes.get(floorCode).toString(), walls, stairs, lifts); + + + floors.add(new FloorPlan(floorCode, 0, null, new double[]{0, 0, 0, 0}, walls, stairs, lifts)); + } + } + + Collections.sort(floors, (f1, f2) -> Integer.compare(getFloorOrderValue(f1.getFloorCode()), getFloorOrderValue(f2.getFloorCode()))); + buildingList.add(new Building(id, name, outline, floors)); + } + return buildingList; + } + + private int getFloorOrderValue(String code) { + if (code == null) return 0; + String raw = code.trim().toUpperCase(); + if (raw.equals("G") || raw.equals("GROUND") || raw.equals("0")) return 0; + if (raw.equals("LG") || raw.startsWith("B")) return -1; + try { return Integer.parseInt(raw); } catch (NumberFormatException e) { return 0; } + } + + private void extractPolygonsFromGeoJson(String geoJsonStr, List> outList) { + try { + JSONObject featureCollection = new JSONObject(geoJsonStr); + JSONArray features = featureCollection.getJSONArray("features"); + if (features.length() > 0) { + JSONObject geometry = features.getJSONObject(0).getJSONObject("geometry"); + JSONArray coordinates = geometry.getJSONArray("coordinates"); + String type = geometry.getString("type"); + JSONArray ring = type.equalsIgnoreCase("MultiPolygon") ? coordinates.getJSONArray(0).getJSONArray(0) : coordinates.getJSONArray(0); + for (int j = 0; j < ring.length(); j++) { + JSONArray point = ring.getJSONArray(j); + List latLng = new ArrayList<>(); + latLng.add(point.getDouble(1)); + latLng.add(point.getDouble(0)); + outList.add(latLng); + } } - else if (index == 1 && o instanceof MainActivity) { - o.update(new Boolean[] {success}); + } catch (Exception e) { Log.e(TAG, "Failed to parse building outline GeoJSON", e); } + } + // Extracts wall, stair, and lift geometry from one floor-plan feature collection. + private void extractFeaturesFromGeoJson(String geoJsonStr, + List>> wallsList, + List>> stairsList, + List>> liftsList) { + try { + JSONObject featureCollection = new JSONObject(geoJsonStr); + JSONArray features = featureCollection.getJSONArray("features"); + for (int i = 0; i < features.length(); i++) { + JSONObject feature = features.getJSONObject(i); + JSONObject geometry = feature.getJSONObject("geometry"); + String type = geometry.getString("type"); + JSONArray coords = geometry.getJSONArray("coordinates"); + + String indoorType = "wall"; // default fallback + + // Try reading feature properties. + if (feature.has("properties") && !feature.isNull("properties")) { + JSONObject properties = feature.getJSONObject("properties"); + + // Prefer the explicit indoor classification before falling back to generic fields. + if (properties.has("indoor_type")) { + indoorType = properties.getString("indoor_type").toLowerCase(); + } else if (properties.has("type")) { + indoorType = properties.getString("type").toLowerCase(); + } else if (properties.has("name")) { + indoorType = properties.getString("name").toLowerCase(); + } + } + + List>> targetList = wallsList; + if (indoorType.contains("stairs") || indoorType.contains("stair")) { + targetList = stairsList; + } else if (indoorType.contains("lift") || indoorType.contains("elevator")) { + targetList = liftsList; + } + + // Normalize each supported GeoJSON geometry into a polyline-style path list. + if (type.equalsIgnoreCase("MultiLineString")) { + for (int k = 0; k < coords.length(); k++) parseLineString(coords.getJSONArray(k), targetList); + } else if (type.equalsIgnoreCase("LineString") || type.equalsIgnoreCase("Polygon")) { + parseLineString(type.equalsIgnoreCase("LineString") ? coords : coords.getJSONArray(0), targetList); + } else if (type.equalsIgnoreCase("MultiPolygon")) { + for (int k = 0; k < coords.length(); k++) parseLineString(coords.getJSONArray(k).getJSONArray(0), targetList); + } } + } catch (Exception e) { + Log.e(TAG, "GeoJSON parse failed", e); + } + } + private void parseLineString(JSONArray lineArray, List>> wallsList) throws JSONException { + List> path = new ArrayList<>(); + for (int p = 0; p < lineArray.length(); p++) { + JSONArray point = lineArray.getJSONArray(p); + List latLng = new ArrayList<>(); + latLng.add(point.getDouble(1)); + latLng.add(point.getDouble(0)); + path.add(latLng); } + if (!path.isEmpty()) wallsList.add(path); } -} \ No newline at end of file + +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/remote/TrajectoryUploadCompat.java b/app/src/main/java/com/openpositioning/PositionMe/data/remote/TrajectoryUploadCompat.java new file mode 100644 index 00000000..929897a2 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/data/remote/TrajectoryUploadCompat.java @@ -0,0 +1,366 @@ +package com.openpositioning.PositionMe.data.remote; + +import androidx.annotation.NonNull; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.openpositioning.PositionMe.Traj; +import com.openpositioning.PositionMe.UploadTraj; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +final class TrajectoryUploadCompat { + + // Prevents this compatibility helper from being instantiated. + private TrajectoryUploadCompat() { + } + + // Converts the local trajectory object into the server byte format. + static byte[] toServerBytes(@NonNull Traj.Trajectory source) { + return toServerTrajectory(source).toByteArray(); + } + + // Reads a local file and converts it into uploadable server bytes. + static byte[] localFileToServerBytes(@NonNull File localTrajectory) throws IOException { + byte[] rawBytes = Files.readAllBytes(localTrajectory.toPath()); + try { + return toServerBytes(Traj.Trajectory.parseFrom(rawBytes)); + } catch (InvalidProtocolBufferException currentParseError) { + try { + UploadTraj.Trajectory.parseFrom(rawBytes); + return rawBytes; + } catch (InvalidProtocolBufferException legacyParseError) { + IOException ioException = new IOException("Unsupported local trajectory proto format"); + ioException.addSuppressed(currentParseError); + ioException.addSuppressed(legacyParseError); + throw ioException; + } + } + } + + // Parses server bytes and returns the current local trajectory model. + static Traj.Trajectory parseServerBytes(@NonNull byte[] rawBytes) throws IOException { + try { + Traj.Trajectory currentTrajectory = Traj.Trajectory.parseFrom(rawBytes); + if (looksLikeCurrentTrajectory(currentTrajectory)) { + return currentTrajectory; + } + } catch (InvalidProtocolBufferException ignored) { + } + return fromServerTrajectory(UploadTraj.Trajectory.parseFrom(rawBytes)); + } + + // Checks whether the bytes already match the newer local schema. + private static boolean looksLikeCurrentTrajectory(@NonNull Traj.Trajectory trajectory) { + return trajectory.getStartTimestamp() > 0 + || trajectory.getImuDataCount() > 0 + || trajectory.getPdrDataCount() > 0 + || trajectory.getGnssDataCount() > 0 + || trajectory.getWifiFingerprintsCount() > 0 + || trajectory.getMagnetometerDataCount() > 0; + } + + // Maps the current local trajectory fields into the server schema. + private static UploadTraj.Trajectory toServerTrajectory(@NonNull Traj.Trajectory source) { + UploadTraj.Trajectory.Builder target = UploadTraj.Trajectory.newBuilder(); + + if (!source.getAndroidVersion().isEmpty()) { + target.setAndroidVersion(source.getAndroidVersion()); + } + if (!source.getTrajectoryId().isEmpty()) { + target.setDataIdentifier(source.getTrajectoryId()); + } + if (source.getStartTimestamp() != 0L) { + target.setStartTimestamp(source.getStartTimestamp()); + } + + for (Traj.IMUReading reading : source.getImuDataList()) { + UploadTraj.MotionSample.Builder motion = UploadTraj.MotionSample.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setStepCount(reading.getStepCount()); + if (reading.hasAcc()) { + motion.setAccX(reading.getAcc().getX()) + .setAccY(reading.getAcc().getY()) + .setAccZ(reading.getAcc().getZ()); + } + if (reading.hasGyr()) { + motion.setGyrX(reading.getGyr().getX()) + .setGyrY(reading.getGyr().getY()) + .setGyrZ(reading.getGyr().getZ()); + } + if (reading.hasRotationVector()) { + motion.setRotationVectorX(reading.getRotationVector().getX()) + .setRotationVectorY(reading.getRotationVector().getY()) + .setRotationVectorZ(reading.getRotationVector().getZ()) + .setRotationVectorW(reading.getRotationVector().getW()); + } + target.addImuData(motion.build()); + } + + int pdrIndex = 0; + for (Traj.RelativePosition position : source.getPdrDataList()) { + long relativeTimestamp = position.getRelativeTimestamp(); + float x = Math.abs(position.getX()) < 1e-3f ? 0f : position.getX(); + float y = Math.abs(position.getY()) < 1e-3f ? 0f : position.getY(); + if (pdrIndex == 0) { + x = 0f; + y = 0f; + if (relativeTimestamp <= 0L) { + relativeTimestamp = 1L; + } + } + target.addPdrData(UploadTraj.PdrSample.newBuilder() + .setRelativeTimestamp(relativeTimestamp) + .setX(x) + .setY(y) + .build()); + pdrIndex++; + } + + for (Traj.MagnetometerReading reading : source.getMagnetometerDataList()) { + UploadTraj.PositionSample.Builder position = UploadTraj.PositionSample.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()); + if (reading.hasMag()) { + position.setMagX(reading.getMag().getX()) + .setMagY(reading.getMag().getY()) + .setMagZ(reading.getMag().getZ()); + } + target.addPositionData(position.build()); + } + + for (Traj.BarometerReading reading : source.getPressureDataList()) { + target.addPressureData(UploadTraj.PressureSample.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setPressure(reading.getPressure()) + .build()); + } + + for (Traj.LightReading reading : source.getLightDataList()) { + target.addLightData(UploadTraj.LightSample.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setLight(reading.getLight()) + .build()); + } + + for (Traj.GNSSReading reading : source.getGnssDataList()) { + UploadTraj.GnssSample.Builder gnss = UploadTraj.GnssSample.newBuilder() + .setAccuracy(reading.getAccuracy()) + .setSpeed(reading.getSpeed()) + .setProvider(reading.getProvider()); + if (reading.hasPosition()) { + gnss.setRelativeTimestamp(reading.getPosition().getRelativeTimestamp()) + .setLatitude((float) reading.getPosition().getLatitude()) + .setLongitude((float) reading.getPosition().getLongitude()) + .setAltitude((float) reading.getPosition().getAltitude()); + } + target.addGnssData(gnss.build()); + } + + for (Traj.Fingerprint fingerprint : source.getWifiFingerprintsList()) { + UploadTraj.WifiSample.Builder wifiSample = UploadTraj.WifiSample.newBuilder() + .setRelativeTimestamp(fingerprint.getRelativeTimestamp()); + for (Traj.RFScan scan : fingerprint.getRfScansList()) { + wifiSample.addMacScans(UploadTraj.MacScan.newBuilder() + .setRelativeTimestamp(scan.getRelativeTimestamp() != 0L + ? scan.getRelativeTimestamp() + : fingerprint.getRelativeTimestamp()) + .setMac(scan.getMac()) + .setRssi(scan.getRssi()) + .build()); + } + target.addWifiData(wifiSample.build()); + } + + for (Traj.WiFiAPData apData : source.getApsDataList()) { + target.addApsData(UploadTraj.ApData.newBuilder() + .setMac(apData.getMac()) + .setSsid(apData.getSsid()) + .setFrequency(apData.getFrequency()) + .build()); + } + + if (source.hasAccelerometerInfo()) { + target.setAccelerometerInfo(toServerSensorInfo(source.getAccelerometerInfo())); + } + if (source.hasGyroscopeInfo()) { + target.setGyroscopeInfo(toServerSensorInfo(source.getGyroscopeInfo())); + } + if (source.hasRotationVectorInfo()) { + target.setRotationVectorInfo(toServerSensorInfo(source.getRotationVectorInfo())); + } + if (source.hasMagnetometerInfo()) { + target.setMagnetometerInfo(toServerSensorInfo(source.getMagnetometerInfo())); + } + if (source.hasBarometerInfo()) { + target.setBarometerInfo(toServerSensorInfo(source.getBarometerInfo())); + } + if (source.hasLightSensorInfo()) { + target.setLightSensorInfo(toServerSensorInfo(source.getLightSensorInfo())); + } + + return target.build(); + } + + // Converts the server schema back into the current local model. + private static Traj.Trajectory fromServerTrajectory(@NonNull UploadTraj.Trajectory source) { + Traj.Trajectory.Builder target = Traj.Trajectory.newBuilder() + .setTrajectoryVersion(2.0f); + + if (!source.getAndroidVersion().isEmpty()) { + target.setAndroidVersion(source.getAndroidVersion()); + } + if (!source.getDataIdentifier().isEmpty()) { + target.setTrajectoryId(source.getDataIdentifier()); + } + if (source.getStartTimestamp() != 0L) { + target.setStartTimestamp(source.getStartTimestamp()); + } + + for (UploadTraj.MotionSample reading : source.getImuDataList()) { + target.addImuData(Traj.IMUReading.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setAcc(Traj.Vector3.newBuilder() + .setX(reading.getAccX()) + .setY(reading.getAccY()) + .setZ(reading.getAccZ()) + .build()) + .setGyr(Traj.Vector3.newBuilder() + .setX(reading.getGyrX()) + .setY(reading.getGyrY()) + .setZ(reading.getGyrZ()) + .build()) + .setRotationVector(Traj.Quaternion.newBuilder() + .setX(reading.getRotationVectorX()) + .setY(reading.getRotationVectorY()) + .setZ(reading.getRotationVectorZ()) + .setW(reading.getRotationVectorW()) + .build()) + .setStepCount(reading.getStepCount()) + .build()); + } + + for (UploadTraj.PdrSample position : source.getPdrDataList()) { + target.addPdrData(Traj.RelativePosition.newBuilder() + .setRelativeTimestamp(position.getRelativeTimestamp()) + .setX(position.getX()) + .setY(position.getY()) + .build()); + } + + for (UploadTraj.PositionSample reading : source.getPositionDataList()) { + target.addMagnetometerData(Traj.MagnetometerReading.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setMag(Traj.Vector3.newBuilder() + .setX(reading.getMagX()) + .setY(reading.getMagY()) + .setZ(reading.getMagZ()) + .build()) + .build()); + } + + for (UploadTraj.PressureSample reading : source.getPressureDataList()) { + target.addPressureData(Traj.BarometerReading.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setPressure(reading.getPressure()) + .build()); + } + + for (UploadTraj.LightSample reading : source.getLightDataList()) { + target.addLightData(Traj.LightReading.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setLight(reading.getLight()) + .build()); + } + + for (UploadTraj.GnssSample reading : source.getGnssDataList()) { + target.addGnssData(Traj.GNSSReading.newBuilder() + .setPosition(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(reading.getRelativeTimestamp()) + .setLatitude(reading.getLatitude()) + .setLongitude(reading.getLongitude()) + .setAltitude(reading.getAltitude()) + .build()) + .setAccuracy(reading.getAccuracy()) + .setSpeed(reading.getSpeed()) + .setProvider(reading.getProvider()) + .build()); + } + + for (UploadTraj.WifiSample sample : source.getWifiDataList()) { + Traj.Fingerprint.Builder fingerprint = Traj.Fingerprint.newBuilder() + .setRelativeTimestamp(sample.getRelativeTimestamp()); + for (UploadTraj.MacScan scan : sample.getMacScansList()) { + fingerprint.addRfScans(Traj.RFScan.newBuilder() + .setRelativeTimestamp(scan.getRelativeTimestamp()) + .setMac(scan.getMac()) + .setRssi(scan.getRssi()) + .build()); + } + target.addWifiFingerprints(fingerprint.build()); + } + + for (UploadTraj.ApData apData : source.getApsDataList()) { + target.addApsData(Traj.WiFiAPData.newBuilder() + .setMac(apData.getMac()) + .setSsid(apData.getSsid()) + .setFrequency(apData.getFrequency()) + .build()); + } + + if (source.hasAccelerometerInfo()) { + target.setAccelerometerInfo(fromServerSensorInfo(source.getAccelerometerInfo())); + } + if (source.hasGyroscopeInfo()) { + target.setGyroscopeInfo(fromServerSensorInfo(source.getGyroscopeInfo())); + } + if (source.hasRotationVectorInfo()) { + target.setRotationVectorInfo(fromServerSensorInfo(source.getRotationVectorInfo())); + } + if (source.hasMagnetometerInfo()) { + target.setMagnetometerInfo(fromServerSensorInfo(source.getMagnetometerInfo())); + } + if (source.hasBarometerInfo()) { + target.setBarometerInfo(fromServerSensorInfo(source.getBarometerInfo())); + } + if (source.hasLightSensorInfo()) { + target.setLightSensorInfo(fromServerSensorInfo(source.getLightSensorInfo())); + } + + if (source.getGnssDataCount() > 0) { + UploadTraj.GnssSample firstGnss = source.getGnssData(0); + target.setInitialPosition(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(firstGnss.getRelativeTimestamp()) + .setLatitude(firstGnss.getLatitude()) + .setLongitude(firstGnss.getLongitude()) + .setAltitude(firstGnss.getAltitude()) + .build()); + } + + return target.build(); + } + + // Copies sensor information into the server protobuf type. + private static UploadTraj.SensorInfo toServerSensorInfo(@NonNull Traj.SensorInfo source) { + return UploadTraj.SensorInfo.newBuilder() + .setName(source.getName()) + .setVendor(source.getVendor()) + .setResolution(source.getResolution()) + .setPower(source.getPower()) + .setVersion(source.getVersion()) + .setType(source.getType()) + .build(); + } + + // Copies sensor information back into the local protobuf type. + private static Traj.SensorInfo fromServerSensorInfo(@NonNull UploadTraj.SensorInfo source) { + return Traj.SensorInfo.newBuilder() + .setName(source.getName()) + .setVendor(source.getVendor()) + .setResolution(source.getResolution()) + .setPower(source.getPower()) + .setVersion(source.getVersion()) + .setType(source.getType()) + .build(); + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/MainActivity.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/MainActivity.java index 995f010d..1e255d81 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/MainActivity.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/activity/MainActivity.java @@ -35,6 +35,7 @@ import com.openpositioning.PositionMe.utils.PermissionManager; +import java.util.ArrayList; import java.util.Objects; /** @@ -61,6 +62,10 @@ * @author Virginia Cangelosi */ public class MainActivity extends AppCompatActivity implements Observer { + private static final String PREF_FLOOR_HEIGHT = "floor_height"; + private static final String PREF_FLOOR_HEIGHT_MIGRATED = "floor_height_bounds_migrated"; + private static final int DEFAULT_FLOOR_HEIGHT_METERS = 4; + private static final int MAX_FLOOR_HEIGHT_METERS = 10; //region Instance variables @@ -111,6 +116,7 @@ protected void onCreate(Bundle savedInstanceState) { // Get handle for settings this.settings = PreferenceManager.getDefaultSharedPreferences(this); + migrateLegacyFloorHeightPreference(); settings.edit().putBoolean("permanentDeny", false).apply(); // Initialize SensorFusion early so that its context is set @@ -121,8 +127,8 @@ protected void onCreate(Bundle savedInstanceState) { multiplePermissionsLauncher = registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), result -> { - boolean locationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false); - boolean activityGranted = result.getOrDefault(Manifest.permission.ACTIVITY_RECOGNITION, false); + boolean locationGranted = hasLocationPermission(); + boolean activityGranted = hasActivityRecognitionPermission(); if (locationGranted && activityGranted) { // Both permissions granted @@ -176,22 +182,22 @@ public void onResume() { new Handler().postDelayed(() -> { if (isActivityVisible()) { // Check if both permissions are granted - boolean locationGranted = ContextCompat.checkSelfPermission( - this, Manifest.permission.ACCESS_FINE_LOCATION - ) == PackageManager.PERMISSION_GRANTED; - - boolean activityGranted = ContextCompat.checkSelfPermission( - this, Manifest.permission.ACTIVITY_RECOGNITION - ) == PackageManager.PERMISSION_GRANTED; + boolean locationGranted = hasLocationPermission(); + boolean activityGranted = hasActivityRecognitionPermission(); if (!locationGranted || !activityGranted) { - // Request both permissions using ActivityResultLauncher - multiplePermissionsLauncher.launch(new String[]{ - Manifest.permission.ACCESS_FINE_LOCATION - }); - multiplePermissionsLauncher.launch(new String[]{ - Manifest.permission.ACTIVITY_RECOGNITION - }); + ArrayList missingPermissions = new ArrayList<>(); + if (!locationGranted) { + missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); + } + if (!activityGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + missingPermissions.add(Manifest.permission.ACTIVITY_RECOGNITION); + } + if (!missingPermissions.isEmpty()) { + multiplePermissionsLauncher.launch( + missingPermissions.toArray(new String[0]) + ); + } } else { // Both permissions are already granted allPermissionsObtained(); @@ -208,6 +214,43 @@ private boolean isActivityVisible() { return !isFinishing() && !isDestroyed(); } + private boolean hasLocationPermission() { + return ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED; + } + + private boolean hasActivityRecognitionPermission() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q + || ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACTIVITY_RECOGNITION + ) == PackageManager.PERMISSION_GRANTED; + } + + /** + * Clamps stored floor-height preferences into the effective supported range once. + */ + private void migrateLegacyFloorHeightPreference() { + if (settings == null || settings.getBoolean(PREF_FLOOR_HEIGHT_MIGRATED, false)) { + return; + } + + SharedPreferences.Editor editor = settings.edit(); + if (settings.contains(PREF_FLOOR_HEIGHT)) { + int storedValue = settings.getInt(PREF_FLOOR_HEIGHT, DEFAULT_FLOOR_HEIGHT_METERS); + int clampedValue = Math.max( + DEFAULT_FLOOR_HEIGHT_METERS, + Math.min(MAX_FLOOR_HEIGHT_METERS, storedValue) + ); + if (storedValue != clampedValue) { + editor.putInt(PREF_FLOOR_HEIGHT, clampedValue); + } + } + editor.putBoolean(PREF_FLOOR_HEIGHT_MIGRATED, true).apply(); + } + /** @@ -358,9 +401,11 @@ public void update(Object[] objList) { * Task that displays negative toast on the main UI thread. * Called when {@link ServerCommunications} fails to upload a trajectory. */ - private final Runnable displayToastTaskFailure = () -> { -// Toast.makeText(MainActivity.this, "Failed to complete trajectory upload", Toast.LENGTH_SHORT).show(); - }; + private final Runnable displayToastTaskFailure = () -> Toast.makeText( + MainActivity.this, + "Failed to upload trajectory", + Toast.LENGTH_SHORT + ).show(); //endregion -} \ No newline at end of file +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DataDisplayController.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DataDisplayController.java new file mode 100644 index 00000000..d4e49f6c --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DataDisplayController.java @@ -0,0 +1,480 @@ +package com.openpositioning.PositionMe.presentation.display; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.maps.model.LatLng; +import com.openpositioning.PositionMe.presentation.fragment.TrajectoryMapFragment; +import com.openpositioning.PositionMe.sensors.SensorFusion; +import com.openpositioning.PositionMe.utils.UtilFunctions; + +/** + * Dedicated controller for Assignment 2 live data display. + * + * Keeps display updates out of RecordingFragment so rendering stays modular: + * - renders the current fused estimate + * - applies optional visual smoothing + * - stores and plots the last N GNSS / WiFi / PDR observations + * - appends the fused trajectory periodically or when movement is detected + */ +public class DataDisplayController { + + private static final double DEGREE_IN_METERS = 111111.0; + private static final long TRAJECTORY_APPEND_INTERVAL_MS = 1000L; + private static final double TRAJECTORY_APPEND_DISTANCE_METERS = 0.20; + private static final double TRAJECTORY_APPEND_STATIC_MIN_METERS = 0.04; + private static final double OBSERVATION_DISTANCE_EPSILON_METERS = 0.6; + private static final double PDR_OBSERVATION_DISTANCE_METERS = 1.0; + private static final long FUSED_STALE_TIMEOUT_MS = 4500L; + private static final double BACKTRACK_REJECTION_MAX_METERS = 0.45; + private static final double BACKTRACK_COS_THRESHOLD = -0.97; + private static final double BACKTRACK_REJECTION_MIN_SEGMENT_METERS = 0.55; + private static final double BACKTRACK_RETURN_TO_PREV_MAX_METERS = 0.25; + private static final double MAX_RENDER_STEP_METERS = 3.4; + private static final double STRAIGHT_LINE_MIN_SEGMENT_METERS = 0.70; + private static final double STRAIGHT_LINE_MAX_CROSSTRACK_METERS = 0.18; + private static final double STRAIGHT_LINE_MAX_CORRECTION_STEP_METERS = 1.45; + private static final long CAMERA_UPDATE_INTERVAL_MS = 900L; + private static final double CAMERA_UPDATE_DISTANCE_METERS = 1.10; + private static final double MARKER_RENDER_DISTANCE_EPSILON_METERS = 0.08; + private static final double MARKER_RENDER_HEADING_EPSILON_DEGREES = 5.0; + private static final double STATIONARY_FREEZE_DISTANCE_METERS = 2.0; + private static final double STATIONARY_WIFI_OBSERVATION_DISTANCE_METERS = 2.2; + private static final double STATIONARY_GNSS_OBSERVATION_DISTANCE_METERS = 2.5; + + private final SensorFusion sensorFusion; + private final TrajectoryMapFragment trajectoryMapFragment; + private final ExponentialLatLngSmoother smoother; + private final SensorFusion.DisplaySnapshot displaySnapshot = new SensorFusion.DisplaySnapshot(); + private final LatLngCache wifiLatLngCache = new LatLngCache(); + private final LatLngCache pdrLatLngCache = new LatLngCache(); + private final LatLngCache fusedLatLngCache = new LatLngCache(); + + private LatLng lastTrajectoryPoint; + private long lastTrajectoryAppendTimeMs; + + private LatLng lastGnssObservation; + private LatLng lastWifiObservation; + private LatLng lastPdrObservation; + private long lastWifiObservationFixTimeMs; + private LatLng lastDrawnMarkerPoint; + private LatLng lastRenderedPoint; + private LatLng secondLastRenderedPoint; + private float lastRenderedOrientationDeg = Float.NaN; + private LatLng lastCameraPoint; + private long lastCameraUpdateTimeMs; + + // Connects sensor data with the map fragment display logic. + public DataDisplayController(SensorFusion sensorFusion, TrajectoryMapFragment trajectoryMapFragment) { + this.sensorFusion = sensorFusion; + this.trajectoryMapFragment = trajectoryMapFragment; + this.smoother = new ExponentialLatLngSmoother(0.78); + } + + // Clears the saved render state before a new session starts. + public void reset() { + lastTrajectoryPoint = null; + lastTrajectoryAppendTimeMs = 0L; + lastGnssObservation = null; + lastWifiObservation = null; + lastPdrObservation = null; + lastWifiObservationFixTimeMs = 0L; + lastDrawnMarkerPoint = null; + lastRenderedPoint = null; + secondLastRenderedPoint = null; + lastRenderedOrientationDeg = Float.NaN; + lastCameraPoint = null; + lastCameraUpdateTimeMs = 0L; + smoother.reset(); + wifiLatLngCache.reset(); + pdrLatLngCache.reset(); + fusedLatLngCache.reset(); + } + + // Builds one display frame from the latest fused sensor snapshot. + public void renderFrame() { + if (trajectoryMapFragment == null) return; + + sensorFusion.fillDisplaySnapshot(displaySnapshot, FUSED_STALE_TIMEOUT_MS); + + LatLng gnssLatLng = displaySnapshot.gnssLatLng; + LatLng wifiLatLng = toLatLng( + displaySnapshot, + displaySnapshot.hasWifiXY ? displaySnapshot.wifiXY : null, + wifiLatLngCache + ); + if (wifiLatLng == null) { + wifiLatLng = displaySnapshot.wifiLatLng; + } + if (wifiLatLng == null) { + wifiLatLng = displaySnapshot.wifiPositioningLatLng; + } + LatLng pdrLatLng = toLatLng( + displaySnapshot, + displaySnapshot.hasPdrXY ? displaySnapshot.pdrXY : null, + pdrLatLngCache + ); + boolean wifiFresh = displaySnapshot.wifiFresh; + boolean gnssFresh = displaySnapshot.gnssFresh; + boolean stationary = displaySnapshot.stationary; + float gnssAccuracyMeters = displaySnapshot.gnssAccuracyMeters; + double gnssObservationDistance = stationary + ? STATIONARY_GNSS_OBSERVATION_DISTANCE_METERS + : OBSERVATION_DISTANCE_EPSILON_METERS; + if (shouldPlotObservation(lastGnssObservation, gnssLatLng, gnssObservationDistance)) { + trajectoryMapFragment.enqueueObservationMarker(DisplayObservationType.GNSS, gnssLatLng); + lastGnssObservation = gnssLatLng; + } + + boolean wifiObservationReady = wifiFresh + && wifiLatLng != null + && displaySnapshot.wifiFixTimeMs > 0L + && displaySnapshot.wifiApCount >= 4 + && displaySnapshot.wifiQuality01 >= 0.18f; + double wifiObservationDistance = stationary + ? STATIONARY_WIFI_OBSERVATION_DISTANCE_METERS + : OBSERVATION_DISTANCE_EPSILON_METERS; + boolean wifiFixAdvanced = displaySnapshot.wifiFixTimeMs != lastWifiObservationFixTimeMs; + if (wifiObservationReady + && (wifiFixAdvanced + || shouldPlotObservation(lastWifiObservation, wifiLatLng, wifiObservationDistance))) { + trajectoryMapFragment.enqueueObservationMarker(DisplayObservationType.WIFI, wifiLatLng); + lastWifiObservation = wifiLatLng; + lastWifiObservationFixTimeMs = displaySnapshot.wifiFixTimeMs; + } + + if (shouldPlotObservation(lastPdrObservation, pdrLatLng, PDR_OBSERVATION_DISTANCE_METERS)) { + trajectoryMapFragment.enqueueObservationMarker(DisplayObservationType.PDR, pdrLatLng); + lastPdrObservation = pdrLatLng; + } + + boolean usingWallSafeTrajectoryPoint = displaySnapshot.hasDisplayTrajectoryXY; + LatLng fusedByFilter = usingWallSafeTrajectoryPoint + ? toLatLng(displaySnapshot, displaySnapshot.displayTrajectoryXY, fusedLatLngCache) + : (displaySnapshot.hasFusedXY + ? toLatLng(displaySnapshot, displaySnapshot.fusedXY, fusedLatLngCache) + : null); + LatLng fusedLatLng = null; + if (fusedByFilter != null) { + fusedLatLng = fusedByFilter; + } else if (pdrLatLng != null) { + fusedLatLng = pdrLatLng; + } else if (wifiFresh && wifiLatLng != null) { + fusedLatLng = wifiLatLng; + } else if (gnssFresh && gnssLatLng != null) { + fusedLatLng = gnssLatLng; + } else if (wifiLatLng != null) { + fusedLatLng = wifiLatLng; + } else if (gnssLatLng != null) { + fusedLatLng = gnssLatLng; + } + + long nowMs = displaySnapshot.frameTimeMs; + if (fusedLatLng != null) { + if (stationary && lastRenderedPoint != null) { + double stationaryJitterMeters = UtilFunctions.distanceBetweenPoints(lastRenderedPoint, fusedLatLng); + if (stationaryJitterMeters <= STATIONARY_FREEZE_DISTANCE_METERS) { + fusedLatLng = lastRenderedPoint; + } + } + if (!usingWallSafeTrajectoryPoint && trajectoryMapFragment.isSmoothDisplayEnabled()) { + fusedLatLng = smoother.filter(fusedLatLng); + } else { + smoother.reset(fusedLatLng); + } + float orientationDeg = stationary && !Float.isNaN(lastRenderedOrientationDeg) + ? lastRenderedOrientationDeg + : (float) Math.toDegrees(displaySnapshot.orientationRad); + if (!Float.isFinite(orientationDeg)) { + orientationDeg = Float.isNaN(lastRenderedOrientationDeg) ? 0f : lastRenderedOrientationDeg; + } + boolean moveCamera = !stationary && shouldMoveCamera(fusedLatLng, nowMs); + if (shouldRenderMarker(fusedLatLng, orientationDeg, moveCamera)) { + trajectoryMapFragment.renderFusedPosition( + fusedLatLng, + orientationDeg, + moveCamera + ); + lastDrawnMarkerPoint = fusedLatLng; + lastRenderedOrientationDeg = orientationDeg; + } + secondLastRenderedPoint = lastRenderedPoint; + lastRenderedPoint = fusedLatLng; + } + + if (!stationary && shouldAppendTrajectoryPoint(fusedLatLng, nowMs)) { + trajectoryMapFragment.appendFusedTrajectoryPoint(fusedLatLng); + lastTrajectoryPoint = fusedLatLng; + lastTrajectoryAppendTimeMs = nowMs; + } + + trajectoryMapFragment.flushPendingObservationMarkers(); + } + + // Decides when a new fused trajectory point should be added. + private boolean shouldAppendTrajectoryPoint(@Nullable LatLng point, long nowMs) { + if (point == null) return false; + if (lastTrajectoryPoint == null) return true; + double distance = UtilFunctions.distanceBetweenPoints(lastTrajectoryPoint, point); + if (distance >= TRAJECTORY_APPEND_DISTANCE_METERS) return true; + if (nowMs - lastTrajectoryAppendTimeMs >= TRAJECTORY_APPEND_INTERVAL_MS) { + return distance >= TRAJECTORY_APPEND_STATIC_MIN_METERS; + } + return false; + } + + // Filters repeated observation markers that are too close together. + private boolean shouldPlotObservation(@Nullable LatLng lastPoint, @Nullable LatLng newPoint, double epsilonMeters) { + if (newPoint == null) return false; + if (lastPoint == null) return true; + return UtilFunctions.distanceBetweenPoints(lastPoint, newPoint) >= epsilonMeters; + } + + // Converts local xy coordinates back into latitude and longitude. + @Nullable + private LatLng toLatLng(@NonNull SensorFusion.DisplaySnapshot snapshot, + @Nullable float[] xy, + @NonNull LatLngCache cache) { + if (!snapshot.hasOrigin || xy == null || xy.length < 2) { + cache.reset(); + return null; + } + double originLat = snapshot.originLatLon[0]; + double originLon = snapshot.originLatLon[1]; + float x = xy[0]; + float y = xy[1]; + if (cache.matches(originLat, originLon, x, y)) { + return cache.value; + } + double lat = originLat + (y / DEGREE_IN_METERS); + double lon = originLon + (x / (DEGREE_IN_METERS * Math.cos(Math.toRadians(originLat)))); + LatLng converted = new LatLng(lat, lon); + cache.update(originLat, originLon, x, y, converted); + return converted; + } + + // Stops tiny backward jumps from being rendered as true motion. + @Nullable + private LatLng suppressBacktrackingNoise(@Nullable LatLng candidate) { + if (candidate == null) { + return null; + } + if (lastRenderedPoint == null || secondLastRenderedPoint == null) { + return candidate; + } + + double distToLast = UtilFunctions.distanceBetweenPoints(lastRenderedPoint, candidate); + if (distToLast < 0.06) { + return lastRenderedPoint; + } + if (distToLast > BACKTRACK_REJECTION_MAX_METERS) { + return candidate; + } + + double prevEast = eastMeters(secondLastRenderedPoint, lastRenderedPoint); + double prevNorth = northMeters(secondLastRenderedPoint, lastRenderedPoint); + double nextEast = eastMeters(lastRenderedPoint, candidate); + double nextNorth = northMeters(lastRenderedPoint, candidate); + double prevNorm = Math.sqrt(prevEast * prevEast + prevNorth * prevNorth); + double nextNorm = Math.sqrt(nextEast * nextEast + nextNorth * nextNorth); + if (prevNorm < 0.35 || nextNorm < 0.35) { + return candidate; + } + + double cos = (prevEast * nextEast + prevNorth * nextNorth) / (prevNorm * nextNorm); + double returnToPrevDistance = UtilFunctions.distanceBetweenPoints(secondLastRenderedPoint, candidate); + if (cos < BACKTRACK_COS_THRESHOLD + && nextNorm <= BACKTRACK_REJECTION_MIN_SEGMENT_METERS + && returnToPrevDistance <= BACKTRACK_RETURN_TO_PREV_MAX_METERS) { + return lastRenderedPoint; + } + return candidate; + } + + // Limits side-to-side wobble when the path should stay mostly straight. + @Nullable + private LatLng suppressCrossTrackJitter(@Nullable LatLng candidate) { + if (candidate == null || lastRenderedPoint == null || secondLastRenderedPoint == null) { + return candidate; + } + + double prevEast = eastMeters(secondLastRenderedPoint, lastRenderedPoint); + double prevNorth = northMeters(secondLastRenderedPoint, lastRenderedPoint); + double prevNorm = Math.sqrt(prevEast * prevEast + prevNorth * prevNorth); + if (prevNorm < STRAIGHT_LINE_MIN_SEGMENT_METERS) { + return candidate; + } + + double candEast = eastMeters(lastRenderedPoint, candidate); + double candNorth = northMeters(lastRenderedPoint, candidate); + double candNorm = Math.sqrt(candEast * candEast + candNorth * candNorth); + if (candNorm < 0.15 || candNorm > STRAIGHT_LINE_MAX_CORRECTION_STEP_METERS) { + return candidate; + } + + double dirEast = prevEast / prevNorm; + double dirNorth = prevNorth / prevNorm; + double alongTrack = candEast * dirEast + candNorth * dirNorth; + double crossTrack = -candEast * dirNorth + candNorth * dirEast; + if (alongTrack <= 0.08 || alongTrack <= Math.abs(crossTrack) * 0.6) { + return candidate; + } + if (Math.abs(crossTrack) <= STRAIGHT_LINE_MAX_CROSSTRACK_METERS) { + return candidate; + } + + double clampedCrossTrack = Math.copySign(STRAIGHT_LINE_MAX_CROSSTRACK_METERS, crossTrack); + double correctedEast = alongTrack * dirEast - clampedCrossTrack * dirNorth; + double correctedNorth = alongTrack * dirNorth + clampedCrossTrack * dirEast; + return offsetMeters(lastRenderedPoint, correctedEast, correctedNorth); + } + + // Measures northward distance between two map points. + private double northMeters(@NonNull LatLng from, @NonNull LatLng to) { + final double earthRadiusMeters = 6378137.0; + double dLat = Math.toRadians(to.latitude - from.latitude); + return dLat * earthRadiusMeters; + } + + // Measures eastward distance between two map points. + private double eastMeters(@NonNull LatLng from, @NonNull LatLng to) { + final double earthRadiusMeters = 6378137.0; + double dLon = Math.toRadians(to.longitude - from.longitude); + double meanLat = Math.toRadians((from.latitude + to.latitude) * 0.5); + return dLon * Math.cos(meanLat) * earthRadiusMeters; + } + + // Applies meter offsets to a latitude and longitude point. + @NonNull + private LatLng offsetMeters(@NonNull LatLng origin, double eastMeters, double northMeters) { + double lat = origin.latitude + (northMeters / 111111.0); + double lon = origin.longitude + (eastMeters / (111111.0 * Math.cos(Math.toRadians(origin.latitude)))); + return new LatLng(lat, lon); + } + + // Mixes the fused point with PDR to keep movement visually continuous. + @Nullable + private LatLng blendWithPdrForContinuity(@Nullable LatLng fused, + @Nullable LatLng pdr, + boolean strongGnss, + boolean mediumGnss) { + if (fused == null) { + return pdr; + } + if (pdr == null) { + return fused; + } + double innovationMeters = UtilFunctions.distanceBetweenPoints(fused, pdr); + double alphaToPdr = strongGnss ? 0.05 : (mediumGnss ? 0.10 : 0.16); + if (innovationMeters >= 1.5) { + alphaToPdr = strongGnss ? 0.02 : (mediumGnss ? 0.05 : 0.06); + } else if (innovationMeters >= 0.8) { + alphaToPdr = strongGnss ? 0.03 : (mediumGnss ? 0.07 : 0.10); + } else if (innovationMeters >= 0.35) { + alphaToPdr = strongGnss ? 0.04 : (mediumGnss ? 0.08 : 0.12); + } + if (innovationMeters <= 0.02 || alphaToPdr <= 1e-3) { + return fused; + } + return new LatLng( + fused.latitude * (1.0 - alphaToPdr) + pdr.latitude * alphaToPdr, + fused.longitude * (1.0 - alphaToPdr) + pdr.longitude * alphaToPdr + ); + } + + // Caps a large render jump so the marker moves more smoothly. + @Nullable + private LatLng limitRenderStep(@Nullable LatLng candidate, double maxStepMeters) { + if (candidate == null || lastRenderedPoint == null) { + return candidate; + } + double distance = UtilFunctions.distanceBetweenPoints(lastRenderedPoint, candidate); + if (distance <= maxStepMeters || distance <= 1e-4) { + return candidate; + } + double ratio = maxStepMeters / distance; + double lat = lastRenderedPoint.latitude + (candidate.latitude - lastRenderedPoint.latitude) * ratio; + double lon = lastRenderedPoint.longitude + (candidate.longitude - lastRenderedPoint.longitude) * ratio; + return new LatLng(lat, lon); + } + + // Decides whether the camera should follow the new location. + private boolean shouldMoveCamera(@NonNull LatLng location, long nowMs) { + if (lastCameraPoint == null) { + lastCameraPoint = location; + lastCameraUpdateTimeMs = nowMs; + return true; + } + double distance = UtilFunctions.distanceBetweenPoints(lastCameraPoint, location); + if (distance >= CAMERA_UPDATE_DISTANCE_METERS + || (nowMs - lastCameraUpdateTimeMs) >= CAMERA_UPDATE_INTERVAL_MS) { + lastCameraPoint = location; + lastCameraUpdateTimeMs = nowMs; + return true; + } + return false; + } + + // Decides whether the marker redraw is worth doing now. + private boolean shouldRenderMarker(@NonNull LatLng location, float orientationDeg, boolean moveCamera) { + if (lastDrawnMarkerPoint == null || Float.isNaN(lastRenderedOrientationDeg) || moveCamera) { + return true; + } + double distance = UtilFunctions.distanceBetweenPoints(lastDrawnMarkerPoint, location); + if (distance >= MARKER_RENDER_DISTANCE_EPSILON_METERS) { + return true; + } + float deltaHeading = Math.abs(normalizeHeadingDeltaDegrees(orientationDeg - lastRenderedOrientationDeg)); + return deltaHeading >= MARKER_RENDER_HEADING_EPSILON_DEGREES; + } + + // Wraps heading differences into the normal -180 to 180 range. + private float normalizeHeadingDeltaDegrees(float deltaDeg) { + while (deltaDeg > 180f) { + deltaDeg -= 360f; + } + while (deltaDeg < -180f) { + deltaDeg += 360f; + } + return deltaDeg; + } + + private static final class LatLngCache { + private double originLat = Double.NaN; + private double originLon = Double.NaN; + private float x = Float.NaN; + private float y = Float.NaN; + private LatLng value; + + // Checks whether the cached conversion still matches the inputs. + private boolean matches(double candidateOriginLat, double candidateOriginLon, float candidateX, float candidateY) { + return value != null + && Double.compare(originLat, candidateOriginLat) == 0 + && Double.compare(originLon, candidateOriginLon) == 0 + && Float.compare(x, candidateX) == 0 + && Float.compare(y, candidateY) == 0; + } + + // Saves the latest converted point in the cache. + private void update(double candidateOriginLat, + double candidateOriginLon, + float candidateX, + float candidateY, + @NonNull LatLng candidateValue) { + originLat = candidateOriginLat; + originLon = candidateOriginLon; + x = candidateX; + y = candidateY; + value = candidateValue; + } + + // Clears all cached coordinate values. + private void reset() { + originLat = Double.NaN; + originLon = Double.NaN; + x = Float.NaN; + y = Float.NaN; + value = null; + } + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DisplayObservationType.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DisplayObservationType.java new file mode 100644 index 00000000..a6ef5526 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/DisplayObservationType.java @@ -0,0 +1,10 @@ +package com.openpositioning.PositionMe.presentation.display; + +/** + * Marker types used for color-coded observation history. + */ +public enum DisplayObservationType { + GNSS, + WIFI, + PDR +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ExponentialLatLngSmoother.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ExponentialLatLngSmoother.java new file mode 100644 index 00000000..840023a1 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ExponentialLatLngSmoother.java @@ -0,0 +1,64 @@ +package com.openpositioning.PositionMe.presentation.display; + +import androidx.annotation.NonNull; + +import com.google.android.gms.maps.model.LatLng; + +/** + * Lightweight exponential smoother for live map display only. + * It improves visual stability without changing the underlying fusion state. + */ +public class ExponentialLatLngSmoother { + + private final double alpha; + private LatLng lastOutput; + + // Stores the smoothing factor for later display updates. + public ExponentialLatLngSmoother(double alpha) { + this.alpha = alpha; + } + + // Smooths the next point while still following larger moves quickly. + public LatLng filter(@NonNull LatLng input) { + if (lastOutput == null) { + lastOutput = input; + return input; + } + + double effectiveAlpha = alpha; + double gapMeters = approximateDistanceMeters(lastOutput, input); + if (gapMeters >= 0.75) { + effectiveAlpha = Math.max(effectiveAlpha, 0.92); + } else if (gapMeters >= 0.35) { + effectiveAlpha = Math.max(effectiveAlpha, 0.86); + } else if (gapMeters >= 0.15) { + effectiveAlpha = Math.max(effectiveAlpha, 0.80); + } + + double lat = lastOutput.latitude + effectiveAlpha * (input.latitude - lastOutput.latitude); + double lon = lastOutput.longitude + effectiveAlpha * (input.longitude - lastOutput.longitude); + lastOutput = new LatLng(lat, lon); + return lastOutput; + } + + // Clears the saved output so smoothing starts over. + public void reset() { + lastOutput = null; + } + + // Resets the smoother with a given starting point. + public void reset(@NonNull LatLng initialValue) { + lastOutput = initialValue; + } + + // Estimates the straight-line distance between two map points. + private double approximateDistanceMeters(@NonNull LatLng a, @NonNull LatLng b) { + final double earthRadiusMeters = 6378137.0; + double dLat = Math.toRadians(b.latitude - a.latitude); + double dLon = Math.toRadians(b.longitude - a.longitude); + double meanLat = Math.toRadians((a.latitude + b.latitude) * 0.5); + double north = dLat * earthRadiusMeters; + double east = dLon * Math.cos(meanLat) * earthRadiusMeters; + return Math.sqrt(east * east + north * north); + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/display/MapControlPanelController.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/MapControlPanelController.java new file mode 100644 index 00000000..33d70a0f --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/MapControlPanelController.java @@ -0,0 +1,50 @@ +package com.openpositioning.PositionMe.presentation.display; + +import android.view.View; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; + +import com.openpositioning.PositionMe.R; + +/** + * Controls collapse/expand state of the map control list panel. + */ +public class MapControlPanelController { + + private final View panelContent; + private final ImageButton toggleButton; + private boolean collapsed; + + // Connects the panel views and applies the initial state. + public MapControlPanelController(@NonNull View panelContent, @NonNull ImageButton toggleButton) { + this.panelContent = panelContent; + this.toggleButton = toggleButton; + this.collapsed = false; + applyState(); + this.toggleButton.setOnClickListener(v -> toggle()); + } + + // Switches the panel between collapsed and expanded. + public void toggle() { + collapsed = !collapsed; + applyState(); + } + + // Sets the panel state directly from outside this class. + public void setCollapsed(boolean collapsed) { + this.collapsed = collapsed; + applyState(); + } + + // Updates the panel visibility and button icon together. + private void applyState() { + panelContent.setVisibility(collapsed ? View.GONE : View.VISIBLE); + toggleButton.setImageResource( + collapsed ? android.R.drawable.arrow_down_float : android.R.drawable.arrow_up_float + ); + toggleButton.setContentDescription(toggleButton.getContext().getString( + collapsed ? R.string.map_controls_expand : R.string.map_controls_collapse + )); + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ObservationMarkerFactory.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ObservationMarkerFactory.java new file mode 100644 index 00000000..d41f5e67 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/display/ObservationMarkerFactory.java @@ -0,0 +1,102 @@ +package com.openpositioning.PositionMe.presentation.display; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; + +import androidx.annotation.NonNull; + +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; + +/** + * Builds and caches custom map marker bitmaps for Assignment 2 data display. + */ +public class ObservationMarkerFactory { + + private final Context appContext; + + private BitmapDescriptor gnssObservationIcon; + private BitmapDescriptor wifiObservationIcon; + private BitmapDescriptor pdrObservationIcon; + private BitmapDescriptor bestEstimateDotIcon; + + // Keeps an application context for bitmap and density work. + public ObservationMarkerFactory(@NonNull Context context) { + this.appContext = context.getApplicationContext(); + } + + // Returns the cached icon for each observation type. + @NonNull + public BitmapDescriptor getObservationIcon(@NonNull DisplayObservationType type) { + switch (type) { + case GNSS: + if (gnssObservationIcon == null) { + gnssObservationIcon = createCircleIcon(0xE03064FF, 12f, 0xCCFFFFFF, 1.0f); + } + return gnssObservationIcon; + case WIFI: + if (wifiObservationIcon == null) { + wifiObservationIcon = createCircleIcon(0xE0FF9800, 12f, 0xCC4A2C00, 1.2f); + } + return wifiObservationIcon; + default: + if (pdrObservationIcon == null) { + pdrObservationIcon = createCircleIcon(0xD017C964, 12f, 0xCCFFFFFF, 1.0f); + } + return pdrObservationIcon; + } + } + + // Returns the small marker used for the best estimate point. + @NonNull + public BitmapDescriptor getBestEstimateDotIcon() { + if (bestEstimateDotIcon == null) { + bestEstimateDotIcon = createCircleIcon(0xFFE11D48, 7f); + } + return bestEstimateDotIcon; + } + + // Creates a circle icon without any outline. + @NonNull + private BitmapDescriptor createCircleIcon(int colorArgb, float diameterDp) { + return createCircleIcon(colorArgb, diameterDp, 0x00000000, 0f); + } + + // Draws a colored circle icon and an optional stroke. + @NonNull + private BitmapDescriptor createCircleIcon(int colorArgb, + float diameterDp, + int strokeColorArgb, + float strokeWidthDp) { + int diameterPx = dpToPx(diameterDp); + if (diameterPx < 2) diameterPx = 2; + + Bitmap bitmap = Bitmap.createBitmap(diameterPx, diameterPx, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.FILL); + paint.setColor(colorArgb); + + float center = diameterPx / 2f; + float radius = (diameterPx / 2f) - 0.6f; + canvas.drawCircle(center, center, radius, paint); + + if ((strokeColorArgb >>> 24) != 0 && strokeWidthDp > 0f) { + Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setColor(strokeColorArgb); + strokePaint.setStrokeWidth(dpToPx(strokeWidthDp)); + canvas.drawCircle(center, center, Math.max(0f, radius - strokePaint.getStrokeWidth() / 2f), strokePaint); + } + + return BitmapDescriptorFactory.fromBitmap(bitmap); + } + + // Converts dp units into pixels for drawing. + private int dpToPx(float dp) { + return Math.round(dp * appContext.getResources().getDisplayMetrics().density); + } +} 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..8dda16d4 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 @@ -166,18 +166,23 @@ private List> processInfoResponse(String infoString) { for (int i = 0; i < jsonArray.length(); i++) { JSONObject trajectoryEntry = jsonArray.getJSONObject(i); Map entryMap = new HashMap<>(); - entryMap.put("owner_id", String.valueOf(trajectoryEntry.get("owner_id"))); - entryMap.put("date_submitted", (String) trajectoryEntry.get("date_submitted")); - entryMap.put("id", String.valueOf(trajectoryEntry.get("id"))); + entryMap.put("owner_id", String.valueOf(trajectoryEntry.opt("owner_id"))); + // Use optString to safely handle null values without ClassCastException + entryMap.put("date_submitted", trajectoryEntry.optString("date_submitted", "")); + entryMap.put("id", String.valueOf(trajectoryEntry.opt("id"))); + entryMap.put("server_position", String.valueOf(i)); // Add decoded map to list of entries entryList.add(entryMap); } - } catch (JSONException e) { - System.err.println("JSON reading failed"); - e.printStackTrace(); + } catch (Exception e) { + android.util.Log.e("FilesFragment", "Failed to parse info response", e); } // Sort the list by the ID fields of the maps - entryList.sort(Comparator.comparing(m -> Integer.parseInt(m.get("id")), Comparator.nullsLast(Comparator.naturalOrder()))); + try { + entryList.sort(Comparator.comparing(m -> Integer.parseInt(m.get("id")), Comparator.nullsLast(Comparator.naturalOrder()))); + } catch (Exception e) { + android.util.Log.w("FilesFragment", "Sort failed, using original order", e); + } return entryList; } @@ -200,9 +205,11 @@ private void updateView(List> entryList) { Map selectedItem = entryList.get(position); String id = selectedItem.get("id"); String dateSubmitted = selectedItem.get("date_submitted"); + // Use the original server position so ZIP entry index matches server order + int serverPosition = 0; + try { serverPosition = Integer.parseInt(selectedItem.get("server_position")); } catch (Exception ignored) {} - // Pass ID and date_submitted - serverCommunications.downloadTrajectory(position, id, dateSubmitted); + serverCommunications.downloadTrajectory(serverPosition, id, dateSubmitted); // new AlertDialog.Builder(getContext()) // .setTitle("File downloaded") 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..62dfe55b 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 @@ -50,6 +50,8 @@ public class HomeFragment extends Fragment implements OnMapReadyCallback { private Button start; private Button measurements; private Button files; + // Task D: New button for Indoor Map + private MaterialButton indoorMapButton; private TextView gnssStatusTextView; // For the map @@ -116,6 +118,13 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Navigation.findNavController(v).navigate(action); }); + // Task D: Indoor Map Button Logic + indoorMapButton = view.findViewById(R.id.indoorButton); + indoorMapButton.setOnClickListener(v -> { + // Navigate to IndoorMapDisplayFragment using the ID defined in main_nav.xml + Navigation.findNavController(v).navigate(R.id.action_homeFragment_to_indoorMapDisplayFragment); + }); + // TextView to display GNSS disabled message gnssStatusTextView = view.findViewById(R.id.gnssStatusTextView); @@ -215,4 +224,4 @@ private void checkAndUpdatePermissions() { showEdinburghAndMessage("GNSS is disabled. Please enable in settings."); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/IndoorMapDisplayFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/IndoorMapDisplayFragment.java new file mode 100644 index 00000000..7dd8ea5d --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/IndoorMapDisplayFragment.java @@ -0,0 +1,281 @@ +package com.openpositioning.PositionMe.presentation.fragment; + +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.TextView; +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.LatLng; +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.data.remote.Building; +import com.openpositioning.PositionMe.data.remote.FloorPlan; +import com.openpositioning.PositionMe.data.remote.ServerCommunications; +import com.openpositioning.PositionMe.utils.VenueSelectionHelper; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * New code guide: + * 1. Requests nearby teaching venues on map startup. + * 2. Draws selectable building outlines for manual venue selection. + * 3. Renders floor walls for the selected building. + * 4. Persists the chosen venue for upload and fusion workflows. + */ +public class IndoorMapDisplayFragment extends Fragment implements OnMapReadyCallback { + + private static final String TAG = "IndoorMapFragment"; + + // UI Components + private GoogleMap mMap; + private TextView floorText; + private Button btnUp, btnDown; + + // Logic Components + private ServerCommunications serverCommunications; + private Building selectedBuilding; + private int currentFloorIndex = 0; + private List currentFloorLines = new ArrayList<>(); + private Set loadedBuildingNames = new HashSet<>(); + + // Locations + private static final LatLng LOC_NUCLEUS = new LatLng(55.9232, -3.1742); + private static final LatLng LOC_MURCHISON = new LatLng(55.924131, -3.179167); + private static final LatLng CAMERA_CENTER = new LatLng(55.9236, -3.1767); + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_indoor_map, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + floorText = view.findViewById(R.id.floorText); + btnUp = view.findViewById(R.id.btnUp); + btnDown = view.findViewById(R.id.btnDown); + updateUIState(false); + + serverCommunications = new ServerCommunications(requireContext()); + + SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapContainer); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } + + btnUp.setOnClickListener(v -> changeFloor(1)); + btnDown.setOnClickListener(v -> changeFloor(-1)); + } + + @Override + public void onMapReady(@NonNull GoogleMap googleMap) { + mMap = googleMap; + mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + mMap.getUiSettings().setZoomControlsEnabled(true); + mMap.getUiSettings().setCompassEnabled(true); + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(CAMERA_CENTER, 16.5f)); + + requestAllBuildings(); + + mMap.setOnPolygonClickListener(polygon -> { + Building building = (Building) polygon.getTag(); + if (building != null) { + onBuildingSelected(building); + } + }); + + // Long press to manually scan a location + mMap.setOnMapLongClickListener(latLng -> { + Toast.makeText(getContext(), "Scanning location...", Toast.LENGTH_SHORT).show(); + serverCommunications.getNearbyBuildings(latLng.latitude, latLng.longitude, new ServerCommunications.BuildingCallback() { + @Override + public void onBuildingsReceived(List buildings) { + if (!buildings.isEmpty()) { + addBuildingsToMap(buildings); + Toast.makeText(getContext(), "Found: " + buildings.get(0).getName(), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), "No buildings found here", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onError(String message) { + Log.e(TAG, "Manual scan error: " + message); + } + }); + }); + } + + // Loads the default venue set used in the assignment demo area. + private void requestAllBuildings() { + loadedBuildingNames.clear(); + mMap.clear(); + + // Step 1: Request Nucleus + serverCommunications.getNearbyBuildings(LOC_NUCLEUS.latitude, LOC_NUCLEUS.longitude, new ServerCommunications.BuildingCallback() { + @Override + public void onBuildingsReceived(List buildings) { + addBuildingsToMap(buildings); + requestMurchison(); + } + + @Override + public void onError(String message) { + Log.e(TAG, "Nucleus request failed: " + message); + requestMurchison(); + } + }); + } + + private void requestMurchison() { + // Step 2: Request Murchison + serverCommunications.getNearbyBuildings(LOC_MURCHISON.latitude, LOC_MURCHISON.longitude, new ServerCommunications.BuildingCallback() { + @Override + public void onBuildingsReceived(List buildings) { + addBuildingsToMap(buildings); + } + + @Override + public void onError(String message) { + Log.e(TAG, "Murchison request failed: " + message); + } + }); + } + + // Adds only new buildings so repeated scans do not duplicate outlines. + private void addBuildingsToMap(List newBuildings) { + if (mMap == null) return; + + for (Building b : newBuildings) { + String name = (b.getName() != null) ? b.getName() : "Unknown"; + if (loadedBuildingNames.contains(name)) continue; + + loadedBuildingNames.add(name); + drawSingleBuildingOutline(b); + } + } + + // Draws a color-coded outline that can be tapped to select a venue. + private void drawSingleBuildingOutline(Building building) { + if (building.getOutline() == null || building.getOutline().isEmpty()) return; + + List points = new ArrayList<>(); + for (List point : building.getOutline()) { + if (point.size() >= 2) points.add(new LatLng(point.get(0), point.get(1))); + } + + int strokeColor = Color.RED; + int fillColor = Color.argb(50, 255, 0, 0); + + String name = building.getName().toLowerCase(); + if (name.contains("nucleus")) { + strokeColor = Color.rgb(255, 191, 0); // Amber + fillColor = Color.argb(50, 255, 191, 0); + } else if (name.contains("fleeming") || name.contains("jenkin")) { + strokeColor = Color.BLUE; + fillColor = Color.argb(50, 0, 0, 255); + } + + Polygon polygon = mMap.addPolygon(new PolygonOptions() + .addAll(points) + .strokeColor(strokeColor) + .fillColor(fillColor) + .strokeWidth(5) + .clickable(true)); + polygon.setTag(building); + } + + // Persists the selected venue and switches the fragment to floor-view mode. + private void onBuildingSelected(Building building) { + this.selectedBuilding = building; + String venueName = building.getName(); + VenueSelectionHelper.persistSelectedBuilding(requireContext(), venueName); + Toast.makeText(getContext(), "Venue set to: " + venueName, Toast.LENGTH_SHORT).show(); + + // Select default floor (G or 0) + this.currentFloorIndex = 0; + for (int i = 0; i < building.getFloors().size(); i++) { + String code = building.getFloors().get(i).getFloorCode(); + if (code.equalsIgnoreCase("G") || code.equals("0")) { + this.currentFloorIndex = i; + break; + } + } + + updateUIState(true); + if (!building.getFloors().isEmpty()) { + drawCurrentFloorWalls(); + } + } + + private void changeFloor(int delta) { + if (selectedBuilding == null || selectedBuilding.getFloors().isEmpty()) return; + + int newIndex = currentFloorIndex + delta; + if (newIndex < 0) newIndex = 0; + if (newIndex >= selectedBuilding.getFloors().size()) newIndex = selectedBuilding.getFloors().size() - 1; + + if (newIndex != currentFloorIndex) { + currentFloorIndex = newIndex; + drawCurrentFloorWalls(); + } + } + + // Redraws only the wall geometry for the currently selected floor. + private void drawCurrentFloorWalls() { + // Clear previous lines + for (Polyline line : currentFloorLines) { + line.remove(); + } + currentFloorLines.clear(); + + FloorPlan floor = selectedBuilding.getFloors().get(currentFloorIndex); + floorText.setText("Floor: " + floor.getFloorCode()); + + if (floor.getWalls() != null) { + for (List> wallPath : floor.getWalls()) { + List points = new ArrayList<>(); + for (List point : wallPath) { + points.add(new LatLng(point.get(0), point.get(1))); + } + + Polyline line = mMap.addPolyline(new PolylineOptions() + .addAll(points) + .color(Color.YELLOW) + .width(6) + .zIndex(100)); + + currentFloorLines.add(line); + } + } + } + + // Shows floor controls only after a building has been selected. + private void updateUIState(boolean isVisible) { + int v = isVisible ? View.VISIBLE : View.GONE; + if (floorText != null) floorText.setVisibility(v); + if (btnUp != null) btnUp.setVisibility(v); + if (btnDown != null) btnDown.setVisibility(v); + } +} 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..d5d461b9 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 @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,7 @@ import com.openpositioning.PositionMe.presentation.viewitems.WifiListAdapter; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -47,9 +49,13 @@ public class MeasurementsFragment extends Fragment { // UI elements private ConstraintLayout sensorMeasurementList; private RecyclerView wifiListView; + private WifiListAdapter wifiListAdapter; + private TextView[][] sensorValueViews; + private String[][] lastRenderedSensorStrings; // List of string resource IDs private int[] prefaces; private int[] gnssPrefaces; + private long lastWifiSignature = Long.MIN_VALUE; /** @@ -76,7 +82,7 @@ public void onCreate(Bundle savedInstanceState) { gnssPrefaces = new int[]{R.string.lati, R.string.longi}; // Create new handler to refresh the UI. - this.refreshDataHandler = new Handler(); + this.refreshDataHandler = new Handler(Looper.getMainLooper()); } /** @@ -90,7 +96,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // Inflate the layout for this fragment View rootView = inflater.inflate(R.layout.fragment_measurements, container, false); getActivity().setTitle("Sensor Measurements"); - this.refreshDataHandler.post(refreshTableTask); return rootView; } @@ -110,8 +115,9 @@ public void onPause() { */ @Override public void onResume() { - refreshDataHandler.postDelayed(refreshTableTask, REFRESH_TIME); super.onResume(); + refreshDataHandler.removeCallbacks(refreshTableTask); + refreshDataHandler.post(refreshTableTask); } /** @@ -122,9 +128,128 @@ public void onResume() { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - sensorMeasurementList = (ConstraintLayout) getView().findViewById(R.id.sensorMeasurementList); - wifiListView = (RecyclerView) getView().findViewById(R.id.wifiList); + sensorMeasurementList = view.findViewById(R.id.sensorMeasurementList); + wifiListView = view.findViewById(R.id.wifiList); wifiListView.setLayoutManager(new LinearLayoutManager(getActivity())); + wifiListView.setHasFixedSize(true); + wifiListAdapter = new WifiListAdapter(requireContext(), java.util.Collections.emptyList()); + wifiListView.setAdapter(wifiListAdapter); + cacheSensorValueViews(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + refreshDataHandler.removeCallbacks(refreshTableTask); + sensorMeasurementList = null; + wifiListView = null; + wifiListAdapter = null; + sensorValueViews = null; + lastRenderedSensorStrings = null; + lastWifiSignature = Long.MIN_VALUE; + } + + private void cacheSensorValueViews() { + SensorTypes[] sensorTypes = SensorTypes.values(); + sensorValueViews = new TextView[sensorTypes.length][]; + lastRenderedSensorStrings = new String[sensorTypes.length][]; + + if (sensorMeasurementList == null) { + return; + } + + int childCount = sensorMeasurementList.getChildCount(); + int rowCount = Math.min(sensorTypes.length, childCount); + for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) { + View rowCard = sensorMeasurementList.getChildAt(rowIndex); + if (!(rowCard instanceof CardView)) { + continue; + } + View rowContent = ((CardView) rowCard).getChildAt(0); + if (!(rowContent instanceof ConstraintLayout)) { + continue; + } + + ConstraintLayout currentRow = (ConstraintLayout) rowContent; + int valueCount = Math.max(0, currentRow.getChildCount() - 1); + TextView[] rowViews = new TextView[valueCount]; + String[] rowStrings = new String[valueCount]; + for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) { + View valueView = currentRow.getChildAt(valueIndex + 1); + if (valueView instanceof TextView) { + rowViews[valueIndex] = (TextView) valueView; + } + } + sensorValueViews[rowIndex] = rowViews; + lastRenderedSensorStrings[rowIndex] = rowStrings; + } + } + + private void updateSensorTable(@NonNull Map sensorValueMap) { + if (sensorValueViews == null || lastRenderedSensorStrings == null) { + return; + } + for (SensorTypes st : SensorTypes.values()) { + int sensorIndex = st.ordinal(); + TextView[] rowViews = sensorValueViews[sensorIndex]; + String[] lastRowStrings = lastRenderedSensorStrings[sensorIndex]; + float[] values = sensorValueMap.get(st); + if (rowViews == null || lastRowStrings == null || values == null) { + continue; + } + int valueCount = Math.min(values.length, rowViews.length); + for (int i = 0; i < valueCount; i++) { + TextView valueView = rowViews[i]; + if (valueView == null) { + continue; + } + String valueString; + if (values.length == 1) { + valueString = getString(R.string.level, String.format(Locale.US, "%.2f", values[i])); + } else if (values.length == 2) { + int wrapper = (st == SensorTypes.GNSSLATLONG) ? gnssPrefaces[i] : prefaces[i]; + valueString = getString(wrapper, String.format(Locale.US, "%.2f", values[i])); + } else { + valueString = getString(prefaces[i], String.format(Locale.US, "%.2f", values[i])); + } + if (!valueString.equals(lastRowStrings[i])) { + valueView.setText(valueString); + lastRowStrings[i] = valueString; + } + } + } + } + + private void updateWifiList(@Nullable List wifiObjects) { + if (wifiListAdapter == null) { + return; + } + long signature = computeWifiSignature(wifiObjects); + if (signature == lastWifiSignature) { + return; + } + lastWifiSignature = signature; + wifiListAdapter.submitItems(wifiObjects); + } + + private long computeWifiSignature(@Nullable List wifiObjects) { + if (wifiObjects == null || wifiObjects.isEmpty()) { + return 0L; + } + long signature = 1469598103934665603L; + for (Wifi wifi : wifiObjects) { + if (wifi == null) { + continue; + } + signature ^= wifi.getBssid(); + signature *= 1099511628211L; + signature ^= wifi.getLevel(); + signature *= 1099511628211L; + signature ^= wifi.getFrequency(); + signature *= 1099511628211L; + } + signature ^= wifiObjects.size(); + return signature; } /** @@ -139,39 +264,17 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat private final Runnable refreshTableTask = new Runnable() { @Override public void run() { + if (!isAdded() || sensorMeasurementList == null) { + return; + } // Get all the values from SensorFusion Map sensorValueMap = sensorFusion.getSensorValueMap(); - // Loop through UI elements and update the values - for(SensorTypes st : SensorTypes.values()) { - CardView cardView = (CardView) sensorMeasurementList.getChildAt(st.ordinal()); - ConstraintLayout currentRow = (ConstraintLayout) cardView.getChildAt(0); - float[] values = sensorValueMap.get(st); - for (int i = 0; i < values.length; i++) { - String valueString; - // Set string wrapper based on data type. - if(values.length == 1) { - valueString = getString(R.string.level, String.format("%.2f", values[0])); - } - else if(values.length == 2){ - if(st == SensorTypes.GNSSLATLONG) - valueString = getString(gnssPrefaces[i], String.format("%.2f", values[i])); - else - valueString = getString(prefaces[i], String.format("%.2f", values[i])); - } - else{ - valueString = getString(prefaces[i], String.format("%.2f", values[i])); - } - ((TextView) currentRow.getChildAt(i + 1)).setText(valueString); - } - } + updateSensorTable(sensorValueMap); // 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. - if(wifiObjects != null) { - wifiListView.setAdapter(new WifiListAdapter(getActivity(), wifiObjects)); - } + updateWifiList(wifiObjects); // Restart the data updater task in REFRESH_TIME milliseconds. refreshDataHandler.postDelayed(refreshTableTask, REFRESH_TIME); } }; -} \ No newline at end of file +} 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..ddb9a5d0 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 @@ -7,59 +7,61 @@ import android.os.Bundle; import android.os.CountDownTimer; import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.widget.Button; +import android.widget.EditText; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import com.google.android.material.button.MaterialButton; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.material.button.MaterialButton; import com.openpositioning.PositionMe.R; import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; +import com.openpositioning.PositionMe.presentation.display.DataDisplayController; import com.openpositioning.PositionMe.sensors.SensorFusion; -import com.openpositioning.PositionMe.sensors.SensorTypes; +import com.openpositioning.PositionMe.utils.PathView; import com.openpositioning.PositionMe.utils.UtilFunctions; -import com.google.android.gms.maps.model.LatLng; +import java.util.Locale; +import java.util.Date; /** - * 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. - * - * @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. - * - * @author Shu Gu + * New code guide: + * 1. Live refresh loop for fused map display. + * 2. Recording-name flow and controlled stop handling. + * 3. Test-point restore and tagging on the live map. + * 4. Lightweight UI updates for distance, elevation, and GNSS error. */ - public class RecordingFragment extends Fragment { + private static final double DEGREE_IN_METERS = 111111.0; + private static final long LIVE_REFRESH_INTERVAL_MS = 130L; + private static final long LIVE_REFRESH_INTERVAL_SLOW_MS = 180L; + private static final long LIVE_REFRESH_INTERVAL_MAX_MS = 240L; + private static final long FLOOR_UI_SYNC_INTERVAL_MS = 450L; + private static final long GNSS_ERROR_REFRESH_INTERVAL_MS = 320L; + private static final float DISTANCE_UI_EPSILON_METERS = 0.02f; + private static final float ELEVATION_UI_EPSILON_METERS = 0.05f; + private static final double GNSS_ERROR_UI_EPSILON_METERS = 0.05; + // UI elements - private MaterialButton completeButton, cancelButton; + private MaterialButton completeButton, cancelButton, addTagButton; + private PathView pathView; private ImageView recIcon; private ProgressBar timeRemaining; private TextView elevation, distanceTravelled, gnssError; @@ -71,21 +73,50 @@ public class RecordingFragment extends Fragment { private SensorFusion sensorFusion; private Handler refreshDataHandler; private CountDownTimer autoStop; + private DataDisplayController dataDisplayController; // Distance tracking private float distance = 0f; private float previousPosX = 0f; private float previousPosY = 0f; + private float lastDisplayedDistance = Float.NaN; + private float lastDisplayedElevation = Float.NaN; + private double lastDisplayedGnssError = Double.NaN; + private final float[] latestPdrPositionBuffer = new float[2]; + private final float[] latestFusedPositionBuffer = new float[2]; + private final double[] displayOriginLatLonBuffer = new double[2]; // References to the child map fragment private TrajectoryMapFragment trajectoryMapFragment; + // Add Tag counter + private int tagCount = 0; + private long nextRefreshIntervalMs = LIVE_REFRESH_INTERVAL_MS; + private long lastFloorUiSyncTimeMs = 0L; + private long lastGnssErrorRefreshTimeMs = 0L; + + // Save the test point of the user pressing "Add Tag" + private final java.util.ArrayList testPoints = + new java.util.ArrayList<>(); + // Timestamp + private long startTimestampMs = 0L; + + // Runs the live display loop with adaptive pacing based on the last frame cost. private final Runnable refreshDataTask = new Runnable() { @Override public void run() { - updateUIandPosition(); - // Loop again - refreshDataHandler.postDelayed(refreshDataTask, 200); + long frameStartMs = SystemClock.elapsedRealtime(); + try { + updateUIandPosition(); + } catch (Exception e) { + Log.e("RecordingFragment", "Live refresh failed", e); + } + if (refreshDataHandler != null && isAdded()) { + long frameDurationMs = SystemClock.elapsedRealtime() - frameStartMs; + nextRefreshIntervalMs = computeNextRefreshDelayMs(frameDurationMs); + refreshDataHandler.removeCallbacks(refreshDataTask); + refreshDataHandler.postDelayed(refreshDataTask, nextRefreshIntervalMs); + } } }; @@ -99,7 +130,7 @@ public void onCreate(Bundle savedInstanceState) { this.sensorFusion = SensorFusion.getInstance(); Context context = requireActivity(); this.settings = PreferenceManager.getDefaultSharedPreferences(context); - this.refreshDataHandler = new Handler(); + this.refreshDataHandler = new Handler(Looper.getMainLooper()); } @Nullable @@ -107,7 +138,6 @@ 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); } @@ -116,163 +146,258 @@ 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 + java.util.List cachedPoints = + sensorFusion.getTestPoints(); + testPoints.clear(); + if (cachedPoints != null) { + testPoints.addAll(cachedPoints); + } + tagCount = testPoints.size(); + long storedStartTimestamp = sensorFusion.getStartTimestampMs(); + startTimestampMs = storedStartTimestamp > 0L ? storedStartTimestamp : System.currentTimeMillis(); + distance = 0f; + previousPosX = 0f; + previousPosY = 0f; + lastDisplayedDistance = Float.NaN; + lastDisplayedElevation = Float.NaN; + lastDisplayedGnssError = Double.NaN; + nextRefreshIntervalMs = LIVE_REFRESH_INTERVAL_MS; + lastFloorUiSyncTimeMs = 0L; + lastGnssErrorRefreshTimeMs = 0L; + + sensorFusion.setStartTimestampMs(startTimestampMs); + sensorFusion.setTestPoints(testPoints); + 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(); + .commitNow(); } - // Initialize UI references + dataDisplayController = new DataDisplayController(sensorFusion, trajectoryMapFragment); + dataDisplayController.reset(); + trajectoryMapFragment.clearMapAndReset(); + restorePersistedTagMarkers(); + elevation = view.findViewById(R.id.currentElevation); distanceTravelled = view.findViewById(R.id.currentDistanceTraveled); gnssError = view.findViewById(R.id.gnssError); - + pathView = view.findViewById(R.id.pathView); completeButton = view.findViewById(R.id.stopButton); cancelButton = view.findViewById(R.id.cancelButton); + addTagButton = view.findViewById(R.id.addTagButton); + addTagButton.bringToFront(); + addTagButton.setElevation(20f); recIcon = view.findViewById(R.id.redDot); timeRemaining = view.findViewById(R.id.timeRemainingBar); - // Hide or initialize default values + // Assignment 2 uses the map-based live display, so hide the old PathView overlay. + if (pathView != null) { + pathView.setVisibility(View.GONE); + } + sensorFusion.setPathView(null); + gnssError.setVisibility(View.GONE); elevation.setText(getString(R.string.elevation, "0")); distanceTravelled.setText(getString(R.string.meter, "0")); - // Buttons completeButton.setOnClickListener(v -> { - // Stop recording & go to correction - if (autoStop != null) autoStop.cancel(); - sensorFusion.stopRecording(); - // Show Correction screen - ((RecordingActivity) requireActivity()).showCorrectionScreen(); + showRecordingNameDialog(); }); - - // 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 + .setPositiveButton("No", (dialogInterface, which) -> dialogInterface.dismiss()) + .create(); - // 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 + negativeButton.setTextColor(Color.RED); }); - dialog.show(); // Finally, show the dialog + dialog.show(); + }); + + addTagButton.setOnClickListener(v -> { + LatLng current = resolveCurrentTagLocation(); + if (current == null) { + Toast.makeText(requireContext(), "Position not ready yet, please try again.", Toast.LENGTH_SHORT).show(); + return; + } + + tagCount++; + if (trajectoryMapFragment != null) { + trajectoryMapFragment.addTagPoint(current, tagCount); + } + + long relativeTs = Math.max(1L, System.currentTimeMillis() - startTimestampMs); + com.openpositioning.PositionMe.Traj.GNSSPosition p = + com.openpositioning.PositionMe.Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(relativeTs) + .setLatitude(current.latitude) + .setLongitude(current.longitude) + .setAltitude((double) sensorFusion.getElevation()) + .build(); + + testPoints.add(p); + sensorFusion.appendTestPoint(p); }); - // The blinking effect for recIcon blinkingRecordingIcon(); - // Start the timed or indefinite UI refresh if (this.settings.getBoolean("split_trajectory", false)) { - // A maximum recording time is set - long limit = this.settings.getInt("split_duration", 30) * 60000L; + int splitDurationMinutes = Math.max(5, Math.min(30, this.settings.getInt("split_duration", 10))); + long limit = splitDurationMinutes * 60000L; + timeRemaining.setVisibility(View.VISIBLE); timeRemaining.setMax((int) (limit / 1000)); timeRemaining.setProgress(0); - timeRemaining.setScaleY(3f); autoStop = new CountDownTimer(limit, 1000) { @Override public void onTick(long millisUntilFinished) { timeRemaining.incrementProgressBy(1); - updateUIandPosition(); } @Override public void onFinish() { sensorFusion.stopRecording(); - ((RecordingActivity) requireActivity()).showCorrectionScreen(); + if (!isAdded()) return; + if (getActivity() instanceof RecordingActivity) { + ((RecordingActivity) getActivity()).showCorrectionScreen(); + } } }.start(); - } else { - // No set time limit, just keep refreshing - refreshDataHandler.post(refreshDataTask); } + startLiveRefreshLoop(); + } + + private void startLiveRefreshLoop() { + if (refreshDataHandler == null || !isAdded()) { + return; + } + nextRefreshIntervalMs = LIVE_REFRESH_INTERVAL_MS; + refreshDataHandler.removeCallbacks(refreshDataTask); + refreshDataHandler.post(refreshDataTask); + } + + private long computeNextRefreshDelayMs(long frameDurationMs) { + if (frameDurationMs >= 120L) { + return LIVE_REFRESH_INTERVAL_MAX_MS; + } + if (frameDurationMs >= 70L) { + return LIVE_REFRESH_INTERVAL_SLOW_MS; + } + return LIVE_REFRESH_INTERVAL_MS; + } + + // Lets the user confirm a readable recording name before the upload/save step. + private void showRecordingNameDialog() { + if (!isAdded()) { + return; + } + + EditText input = new EditText(requireContext()); + input.setHint(getString(R.string.recording_name_hint)); + input.setSingleLine(true); + input.setText(buildDefaultRecordingName()); + input.setSelection(input.getText().length()); + + new AlertDialog.Builder(requireContext()) + .setTitle(R.string.recording_name_title) + .setView(input) + .setPositiveButton(R.string.save_recording_name, (dialog, which) -> { + String chosenName = input.getText() == null ? "" : input.getText().toString().trim(); + finalizeRecordingWithName(chosenName); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .show(); + } + + private String buildDefaultRecordingName() { + String timestampLabel = new java.text.SimpleDateFormat( + "yyyy-MM-dd HH:mm", + Locale.getDefault() + ).format(new Date(Math.max(startTimestampMs, System.currentTimeMillis()))); + return getString(R.string.recording_name_default) + " " + timestampLabel; + } + + // Freezes the chosen metadata and hands control back to the correction screen. + private void finalizeRecordingWithName(String chosenName) { + sensorFusion.setPendingRecordingName(chosenName); + sensorFusion.setStartTimestampMs(startTimestampMs); + sensorFusion.setTestPoints(testPoints); + + if (autoStop != null) autoStop.cancel(); + sensorFusion.stopRecording(); + ((RecordingActivity) requireActivity()).showCorrectionScreen(); } /** - * Update the UI with sensor data and pass map updates to TrajectoryMapFragment. + * Update the UI with sensor data and hand off live map rendering to DataDisplayController. */ 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 } - ); - - // Pass the location + orientation to the map - if (trajectoryMapFragment != null) { - trajectoryMapFragment.updateUserLocation(newLocation, - (float) Math.toDegrees(sensorFusion.passOrientation())); + try { + long nowMs = SystemClock.elapsedRealtime(); + if (!sensorFusion.copyLatestPdrPositionXY(latestPdrPositionBuffer)) return; + + float dx = latestPdrPositionBuffer[0] - previousPosX; + float dy = latestPdrPositionBuffer[1] - previousPosY; + float segmentDistance = (float) Math.hypot(dx, dy); + if (Float.isFinite(segmentDistance) && segmentDistance > 1e-4f) { + distance += segmentDistance; + } + updateDistanceText(distance); + + float elevationVal = sensorFusion.getElevation(); + updateElevationText(elevationVal); + + if (trajectoryMapFragment != null + && (nowMs - lastFloorUiSyncTimeMs) >= FLOOR_UI_SYNC_INTERVAL_MS) { + trajectoryMapFragment.updateElevation(); + lastFloorUiSyncTimeMs = nowMs; + } + + if (dataDisplayController != null) { + dataDisplayController.renderFrame(); } - } - // 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)); + if (trajectoryMapFragment != null && trajectoryMapFragment.isGnssEnabled()) { + if ((nowMs - lastGnssErrorRefreshTimeMs) >= GNSS_ERROR_REFRESH_INTERVAL_MS) { + LatLng currentLoc = trajectoryMapFragment.getCurrentLocation(); + LatLng gnssLocation = sensorFusion.getLatestGnssLatLng(); + if (currentLoc != null && gnssLocation != null) { + double errorDist = UtilFunctions.distanceBetweenPoints(currentLoc, gnssLocation); + updateGnssError(errorDist); + } else { + lastDisplayedGnssError = Double.NaN; + gnssError.setVisibility(View.GONE); + } + lastGnssErrorRefreshTimeMs = nowMs; } - trajectoryMapFragment.updateGNSS(gnssLocation); } else { + lastDisplayedGnssError = Double.NaN; gnssError.setVisibility(View.GONE); - trajectoryMapFragment.clearGNSS(); } - } - // Update previous - previousPosX = pdrValues[0]; - previousPosY = pdrValues[1]; + previousPosX = latestPdrPositionBuffer[0]; + previousPosY = latestPdrPositionBuffer[1]; + } catch (Exception e) { + Log.e("RecordingFragment", "updateUIandPosition failed", e); + } } - /** - * Start the blinking effect for the recording icon. - */ private void blinkingRecordingIcon() { Animation blinking = new AlphaAnimation(1, 0); blinking.setDuration(800); @@ -282,17 +407,120 @@ private void blinkingRecordingIcon() { recIcon.startAnimation(blinking); } + private void updateDistanceText(float distanceMeters) { + if (!Float.isNaN(lastDisplayedDistance) + && Math.abs(distanceMeters - lastDisplayedDistance) < DISTANCE_UI_EPSILON_METERS) { + return; + } + lastDisplayedDistance = distanceMeters; + distanceTravelled.setText(getString( + R.string.meter, + String.format(Locale.US, "%.2f", distanceMeters) + )); + } + + private void updateElevationText(float elevationMeters) { + if (!Float.isNaN(lastDisplayedElevation) + && Math.abs(elevationMeters - lastDisplayedElevation) < ELEVATION_UI_EPSILON_METERS) { + return; + } + lastDisplayedElevation = elevationMeters; + elevation.setText(getString( + R.string.elevation, + String.format(Locale.US, "%.1f", elevationMeters) + )); + } + + private void updateGnssError(double errorMeters) { + gnssError.setVisibility(View.VISIBLE); + if (!Double.isNaN(lastDisplayedGnssError) + && Math.abs(errorMeters - lastDisplayedGnssError) < GNSS_ERROR_UI_EPSILON_METERS) { + return; + } + lastDisplayedGnssError = errorMeters; + gnssError.setText(String.format(Locale.US, "%s%.2fm", getString(R.string.gnss_error), errorMeters)); + } + + // Restores previously saved test points when the fragment is recreated mid-session. + private void restorePersistedTagMarkers() { + if (trajectoryMapFragment == null || testPoints.isEmpty()) { + return; + } + int markerIndex = 1; + for (com.openpositioning.PositionMe.Traj.GNSSPosition point : testPoints) { + if (point == null) { + continue; + } + double lat = point.getLatitude(); + double lon = point.getLongitude(); + if (Double.isNaN(lat) || Double.isNaN(lon)) { + continue; + } + trajectoryMapFragment.addTagPoint(new LatLng(lat, lon), markerIndex++); + } + } + + // Resolves the best available map position for a new manual test marker. + @Nullable + private LatLng resolveCurrentTagLocation() { + if (trajectoryMapFragment != null) { + LatLng current = trajectoryMapFragment.getCurrentLocation(); + if (current != null) { + return current; + } + } + + if (sensorFusion.copyLatestFusedPositionXY(latestFusedPositionBuffer) + && sensorFusion.copyDisplayOriginLatLon(displayOriginLatLonBuffer)) { + double lat = displayOriginLatLonBuffer[0] + (latestFusedPositionBuffer[1] / DEGREE_IN_METERS); + double lon = displayOriginLatLonBuffer[1] + (latestFusedPositionBuffer[0] + / (DEGREE_IN_METERS * Math.cos(Math.toRadians(displayOriginLatLonBuffer[0])))); + return new LatLng(lat, lon); + } + + LatLng wifiLocation = sensorFusion.getLatestWifiLatLng(); + if (wifiLocation != null) { + return wifiLocation; + } + return sensorFusion.getLatestGnssLatLng(); + } + @Override public void onPause() { super.onPause(); - refreshDataHandler.removeCallbacks(refreshDataTask); + if (refreshDataHandler != null) { + refreshDataHandler.removeCallbacks(refreshDataTask); + } + if (recIcon != null) { + recIcon.clearAnimation(); + } } @Override public void onResume() { super.onResume(); - if(!this.settings.getBoolean("split_trajectory", false)) { - refreshDataHandler.postDelayed(refreshDataTask, 500); + sensorFusion.setPathView(null); + sensorFusion.resumeListening(); + if (recIcon != null) { + blinkingRecordingIcon(); + } + startLiveRefreshLoop(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (refreshDataHandler != null) { + refreshDataHandler.removeCallbacks(refreshDataTask); + } + if (autoStop != null) { + autoStop.cancel(); + } + if (recIcon != null) { + recIcon.clearAnimation(); } + dataDisplayController = null; + trajectoryMapFragment = null; + pathView = null; } } 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..287c2790 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 @@ -272,7 +272,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 +283,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 point.gnssLocation; } } return null; // None found @@ -302,7 +302,7 @@ public void run() { Log.i(TAG, "Playing index: " + currentIndex); updateMapForIndex(currentIndex); currentIndex++; - playbackSeekBar.setProgress(currentIndex); + playbackSeekBar.setProgress(Math.min(currentIndex, replayData.size() - 1)); if (currentIndex < replayData.size()) { playbackHandler.postDelayed(this, PLAYBACK_INTERVAL_MS); diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SettingsFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SettingsFragment.java index c1f6501c..f776564e 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SettingsFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/SettingsFragment.java @@ -2,7 +2,9 @@ import android.os.Bundle; import android.text.InputType; +import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.preference.EditTextPreference; import androidx.preference.PreferenceFragmentCompat; @@ -35,23 +37,85 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); getActivity().setTitle("Settings"); weibergK = findPreference("weiberg_k"); - weibergK.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + bindPositiveDecimalPreference(weibergK, R.string.settings_value_positive_number); elevationSeconds = findPreference("elevation_seconds"); - elevationSeconds.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER)); + bindPositiveIntegerPreference(elevationSeconds, R.string.settings_value_positive_integer); accelSamples = findPreference("accel_samples"); - accelSamples.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER)); + bindPositiveIntegerPreference(accelSamples, R.string.settings_value_positive_integer); epsilon = findPreference("epsilon"); - epsilon.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + bindNonNegativeDecimalPreference(epsilon, R.string.settings_value_non_negative_number); accelFilter = findPreference("accel_filter"); - accelFilter.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + bindUnitIntervalPreference(accelFilter, R.string.settings_value_unit_interval); wifiInterval = findPreference("wifi_interval"); - wifiInterval.setOnBindEditTextListener(editText -> editText.setInputType( - InputType.TYPE_CLASS_NUMBER)); + bindPositiveIntegerPreference(wifiInterval, R.string.settings_value_positive_integer); + } + + private void bindPositiveIntegerPreference(EditTextPreference preference, @StringRes int errorResId) { + if (preference == null) return; + preference.setOnBindEditTextListener(editText -> editText.setInputType(InputType.TYPE_CLASS_NUMBER)); + preference.setOnPreferenceChangeListener((pref, newValue) -> + validatePositiveInteger(newValue, errorResId)); + } + + private void bindPositiveDecimalPreference(EditTextPreference preference, @StringRes int errorResId) { + if (preference == null) return; + preference.setOnBindEditTextListener(editText -> editText.setInputType( + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + preference.setOnPreferenceChangeListener((pref, newValue) -> + validatePositiveDecimal(newValue, errorResId)); + } + + private void bindNonNegativeDecimalPreference(EditTextPreference preference, @StringRes int errorResId) { + if (preference == null) return; + preference.setOnBindEditTextListener(editText -> editText.setInputType( + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + preference.setOnPreferenceChangeListener((pref, newValue) -> + validateNonNegativeDecimal(newValue, errorResId)); + } + + private void bindUnitIntervalPreference(EditTextPreference preference, @StringRes int errorResId) { + if (preference == null) return; + preference.setOnBindEditTextListener(editText -> editText.setInputType( + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL)); + preference.setOnPreferenceChangeListener((pref, newValue) -> + validateUnitInterval(newValue, errorResId)); + } + + private boolean validatePositiveInteger(Object newValue, @StringRes int errorResId) { + try { + return Integer.parseInt(String.valueOf(newValue).trim()) > 0 || rejectValue(errorResId); + } catch (RuntimeException e) { + return rejectValue(errorResId); + } + } + + private boolean validatePositiveDecimal(Object newValue, @StringRes int errorResId) { + try { + return Float.parseFloat(String.valueOf(newValue).trim()) > 0f || rejectValue(errorResId); + } catch (RuntimeException e) { + return rejectValue(errorResId); + } + } + + private boolean validateNonNegativeDecimal(Object newValue, @StringRes int errorResId) { + try { + return Float.parseFloat(String.valueOf(newValue).trim()) >= 0f || rejectValue(errorResId); + } catch (RuntimeException e) { + return rejectValue(errorResId); + } + } + + private boolean validateUnitInterval(Object newValue, @StringRes int errorResId) { + try { + float value = Float.parseFloat(String.valueOf(newValue).trim()); + return (value >= 0f && value <= 1f) || rejectValue(errorResId); + } catch (RuntimeException e) { + return rejectValue(errorResId); + } + } + private boolean rejectValue(@StringRes int errorResId) { + Toast.makeText(requireContext(), getString(errorResId), Toast.LENGTH_SHORT).show(); + return false; } -} \ 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..14fd8e0d 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 @@ -22,7 +22,6 @@ import com.openpositioning.PositionMe.presentation.activity.RecordingActivity; import com.openpositioning.PositionMe.presentation.activity.ReplayActivity; import com.openpositioning.PositionMe.sensors.SensorFusion; -import com.openpositioning.PositionMe.utils.NucleusBuildingManager; /** * A simple {@link Fragment} subclass. The startLocation fragment is displayed before the trajectory @@ -46,10 +45,6 @@ public class StartLocationFragment extends Fragment { private float[] startPosition = new float[2]; // Zoom level for the Google map private float zoom = 19f; - // Instance for managing indoor building overlays (if any) - private NucleusBuildingManager nucleusBuildingManager; - // Dummy variable for floor index - private int FloorNK; /** * Public Constructor for the class. @@ -72,8 +67,24 @@ 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); + // Prefer fresh Wi-Fi anchor first; fallback to GNSS when Wi-Fi is unavailable. + LatLng latestWifi = sensorFusion.getLatestWifiLatLng(); + LatLng latestGnss = sensorFusion.getLatestGnssLatLng(); + boolean wifiFresh = latestWifi != null && sensorFusion.isLatestWifiFresh(); + boolean gnssFresh = latestGnss != null && sensorFusion.isLatestGnssFresh(); + if (wifiFresh) { + startPosition[0] = (float) latestWifi.latitude; + startPosition[1] = (float) latestWifi.longitude; + } else if (gnssFresh) { + startPosition[0] = (float) latestGnss.latitude; + startPosition[1] = (float) latestGnss.longitude; + } else { + startPosition = sensorFusion.getGNSSLatitude(false); + if ((startPosition[0] == 0f && startPosition[1] == 0f) && latestWifi != null) { + startPosition[0] = (float) latestWifi.latitude; + startPosition[1] = (float) latestWifi.longitude; + } + } // If no location found, zoom the map out if (startPosition[0] == 0 && startPosition[1] == 0) { zoom = 1f; @@ -102,12 +113,9 @@ public void onMapReady(GoogleMap mMap) { mMap.getUiSettings().setRotateGesturesEnabled(true); mMap.getUiSettings().setScrollGesturesEnabled(true); - // *** FIX: Clear any existing markers so the start marker isnโ€™t duplicated *** + // *** FIX: Clear any existing markers so the start marker isn't duplicated *** mMap.clear(); - - // Create NucleusBuildingManager instance (if needed) - nucleusBuildingManager = new NucleusBuildingManager(mMap); - nucleusBuildingManager.getIndoorMapManager().hideMap(); + // Assignment 2: no local indoor bitmap overlay, only API/Google map. // Add a marker at the current GPS location and move the camera position = new LatLng(startPosition[0], startPosition[1]); @@ -169,9 +177,9 @@ public void onClick(View view) { // If the Activity is RecordingActivity if (requireActivity() instanceof RecordingActivity) { - // Start sensor recording + set the start location - sensorFusion.startRecording(); + // Set the start location first, then start recording with this origin. sensorFusion.setStartGNSSLatitude(startPosition); + sensorFusion.startRecording(); // Now switch to the recording screen ((RecordingActivity) requireActivity()).showRecordingScreen(); @@ -191,16 +199,4 @@ public void onClick(View view) { }); } - /** - * Switches the indoor map to the specified floor. - * - * @param floorIndex the index of the floor to switch to - */ - private void switchFloorNU(int floorIndex) { - FloorNK = floorIndex; // Set the current floor index - if (nucleusBuildingManager != null) { - // Call the switchFloor method of the IndoorMapManager to switch to the specified floor - nucleusBuildingManager.getIndoorMapManager().switchFloor(floorIndex); - } - } } 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..608516fb 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 @@ -1,97 +1,193 @@ package com.openpositioning.PositionMe.presentation.fragment; +import android.app.AlertDialog; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; import android.os.Bundle; -import android.util.Log; +import android.text.InputType; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; import android.widget.Spinner; -import com.google.android.material.switchmaterial.SwitchMaterial; +import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.google.android.gms.maps.OnMapReadyCallback; -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.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.*; +import com.google.android.gms.maps.model.BitmapDescriptor; +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.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.presentation.display.DisplayObservationType; +import com.openpositioning.PositionMe.presentation.display.ExponentialLatLngSmoother; +import com.openpositioning.PositionMe.presentation.display.MapControlPanelController; +import com.openpositioning.PositionMe.presentation.display.ObservationMarkerFactory; +import com.openpositioning.PositionMe.utils.BuildingMapController; +import com.openpositioning.PositionMe.utils.UtilFunctions; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; - +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * 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 + * New code guide: + * 1. Observation marker queues and smoothing controls. + * 2. Indoor map floor sync with SensorFusion. + * 3. Fused trajectory rendering and compaction. + * 4. Numbered test-point markers and reset helpers. */ +public class TrajectoryMapFragment extends Fragment implements OnMapReadyCallback { + private static final int DEFAULT_MAX_OBSERVATION_MARKERS = 3; + private static final long FLOOR_SYNC_MIN_INTERVAL_MS = 250L; + private static final double TRAJECTORY_RENDER_MIN_DISTANCE_METERS = 0.08; + private static final int MAX_TRAJECTORY_RENDER_POINTS = 2400; + private static final int TARGET_TRAJECTORY_RENDER_POINTS = 1600; + private static final int TRAJECTORY_RECENT_TAIL_POINTS = 160; + private static final int MAX_PENDING_OBSERVATION_UPDATES = 48; + private static final int MAX_OBSERVATION_UPDATES_PER_FLUSH = 8; + private static final float OBSERVATION_HISTORY_MIN_ALPHA = 0.28f; + + private GoogleMap gMap; + private LatLng currentLocation; + private Marker orientationMarker; + private Marker bestEstimateDotMarker; + private Marker gnssMarker; + private Polyline fusedPolyline; + private boolean isRed = true; + private boolean isGnssOn = true; + + private LatLng pendingCameraPosition = null; + private boolean hasPendingCameraMove = false; + + private BuildingMapController mapController; -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; - - - // UI private Spinner switchMapSpinner; - + private Spinner observationCountSpinner; + private View mapControlList; + private ImageButton mapControlToggleButton; private SwitchMaterial gnssSwitch; + private SwitchMaterial wifiObservationSwitch; + private SwitchMaterial pdrObservationSwitch; private SwitchMaterial autoFloorSwitch; + private SwitchMaterial smoothDisplaySwitch; - private com.google.android.material.floatingactionbutton.FloatingActionButton floorUpButton, floorDownButton; + private FloatingActionButton floorUpButton, floorDownButton; private Button switchColorButton; - private Polygon buildingPolygon; - + private TextView currentFloorIndicator; + private View floorControlCard; + + // --- Auto Floor Logic --- + private static final boolean DEFAULT_SMOOTH_DISPLAY_ENABLED = true; + private boolean isAutoFloorEnabled = true; + private boolean smoothDisplayEnabled = DEFAULT_SMOOTH_DISPLAY_ENABLED; + private int currentFloorValue = 0; + private int manualFloorOffset = 0; + private static final double FLOOR_HEIGHT_STEP = 4.0; + + private static final int MIN_FLOOR_VAL = -1; // BF + private static final int MAX_FLOOR_VAL = 3; // 3F + private int maxObservationMarkers = DEFAULT_MAX_OBSERVATION_MARKERS; + private boolean isWifiObservationOn = true; + private boolean isPdrObservationOn = true; + // Keeps recent observation markers bounded so the map stays readable. + private final ArrayDeque gnssObservationMarkers = new ArrayDeque<>(); + private final ArrayDeque wifiObservationMarkers = new ArrayDeque<>(); + private final ArrayDeque pdrObservationMarkers = new ArrayDeque<>(); + private final ArrayDeque pendingObservationUpdates = new ArrayDeque<>(); + private final List fusedPathPoints = new ArrayList<>(); + private final List pendingTagLocations = new ArrayList<>(); + private final List pendingTagIndices = new ArrayList<>(); + private final SparseArray numberedTagIconCache = new SparseArray<>(); + private ObservationMarkerFactory observationMarkerFactory; + private MapControlPanelController mapControlPanelController; + private final ExponentialLatLngSmoother replaySmoother = new ExponentialLatLngSmoother(0.65); // was 0.25 โ€” matched to DataDisplayController smoother (0.78) for consistent smooth behaviour + private boolean floorSelectionSyncArmed = false; + private boolean sensorFloorSyncInProgress = false; + private long lastFloorSyncAttemptTimeMs = 0L; + private int lastRequestedSensorFloor = Integer.MIN_VALUE; + + private static final class PendingObservationMarker { + private final DisplayObservationType type; + private final LatLng point; + + private PendingObservationMarker(@NonNull DisplayObservationType type, @NonNull LatLng point) { + this.type = type; + this.point = point; + } + } - public TrajectoryMapFragment() { - // Required empty public constructor + // Creates cached numbered icons for assignment test-point markers. + private BitmapDescriptor createNumberedMarkerIcon(int number) { + BitmapDescriptor cached = numberedTagIconCache.get(number); + if (cached != null) { + return cached; + } + final int size = 56; + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + circlePaint.setStyle(Paint.Style.FILL); + circlePaint.setColor(0xFF2196F3); + + float cx = size / 2f; + float cy = size / 2f; + float r = size / 2f; + canvas.drawCircle(cx, cy, r, circlePaint); + + Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setStrokeWidth(3f); + strokePaint.setColor(0xFFFFFFFF); + canvas.drawCircle(cx, cy, r - 3f, strokePaint); + + Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(0xFFFFFFFF); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setFakeBoldText(true); + textPaint.setTextSize(25f); + + String text = String.valueOf(number); + Rect textBounds = new Rect(); + textPaint.getTextBounds(text, 0, text.length(), textBounds); + float textY = cy + textBounds.height() / 2f; + canvas.drawText(text, cx, textY, textPaint); + + BitmapDescriptor descriptor = BitmapDescriptorFactory.fromBitmap(bitmap); + numberedTagIconCache.put(number, descriptor); + return descriptor; } + public TrajectoryMapFragment() {} + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - // Inflate the separate layout containing map + map-related UI return inflater.inflate(R.layout.fragment_trajectory_map, container, false); } @@ -100,150 +196,250 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // Grab references to UI controls switchMapSpinner = view.findViewById(R.id.mapSwitchSpinner); + observationCountSpinner = view.findViewById(R.id.observationCountSpinner); + mapControlList = view.findViewById(R.id.mapControlList); + mapControlToggleButton = view.findViewById(R.id.mapControlToggleButton); gnssSwitch = view.findViewById(R.id.gnssSwitch); + wifiObservationSwitch = view.findViewById(R.id.wifiObservationSwitch); + pdrObservationSwitch = view.findViewById(R.id.pdrObservationSwitch); autoFloorSwitch = view.findViewById(R.id.autoFloor); + smoothDisplaySwitch = view.findViewById(R.id.smoothDisplaySwitch); floorUpButton = view.findViewById(R.id.floorUpButton); floorDownButton = view.findViewById(R.id.floorDownButton); switchColorButton = view.findViewById(R.id.lineColorButton); + floorControlCard = view.findViewById(R.id.floorControlCard); + currentFloorIndicator = view.findViewById(R.id.currentFloorIndicator); + observationMarkerFactory = new ObservationMarkerFactory(requireContext()); + if (mapControlList != null && mapControlToggleButton != null) { + mapControlPanelController = new MapControlPanelController(mapControlList, mapControlToggleButton); + mapControlPanelController.setCollapsed(true); + } - // Setup floor up/down UI hidden initially until we know there's an indoor map - setFloorControlsVisibility(View.GONE); + currentFloorValue = 0; + updateFloorIndicatorUI(0); + updateManualFloorControlsVisibility(); - // Initialize the map asynchronously SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.trajectoryMap); if (mapFragment != null) { - 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; - } + mapFragment.getMapAsync(this); + } + + initMapTypeSpinner(); + initObservationCountSpinner(); + + if (gnssSwitch != null) { + gnssSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + isGnssOn = isChecked; + if (gnssMarker != null) { + gnssMarker.setVisible(isChecked); + } + setMarkerCollectionVisible(gnssObservationMarkers, isChecked); + }); + } - drawBuildingPolygon(); + if (wifiObservationSwitch != null) { + wifiObservationSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + isWifiObservationOn = isChecked; + setMarkerCollectionVisible(wifiObservationMarkers, isChecked); + }); + } - Log.d("TrajectoryMapFragment", "onMapReady: Map is ready!"); + if (pdrObservationSwitch != null) { + pdrObservationSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + isPdrObservationOn = isChecked; + setMarkerCollectionVisible(pdrObservationMarkers, isChecked); + }); + } + if (smoothDisplaySwitch != null) { + smoothDisplaySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + smoothDisplayEnabled = isChecked; + if (!isChecked) { + replaySmoother.reset(); + } + }); + smoothDisplaySwitch.setChecked(smoothDisplayEnabled); + } + if (switchColorButton != null) { + switchColorButton.setOnClickListener(v -> { + if (fusedPolyline != null) { + if (isRed) { + switchColorButton.setBackgroundColor(Color.BLACK); + fusedPolyline.setColor(Color.BLACK); + isRed = false; + } else { + switchColorButton.setBackgroundColor(Color.RED); + fusedPolyline.setColor(Color.RED); + isRed = true; + } } }); } - // Map type spinner setup - initMapTypeSpinner(); + if (autoFloorSwitch != null) { + autoFloorSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> { + isAutoFloorEnabled = isChecked; + if (isChecked) { + Toast.makeText(requireContext(), "Auto Floor: ON", Toast.LENGTH_SHORT).show(); + manualFloorOffset = 0; + } else { + com.openpositioning.PositionMe.sensors.SensorFusion + .getInstance() + .consumePendingFloorDelta(); + } + updateManualFloorControlsVisibility(); + }); + autoFloorSwitch.setChecked(true); + } + + if (floorUpButton != null) { + floorUpButton.setOnClickListener(v -> { + if (mapController != null) { + floorSelectionSyncArmed = true; + manualFloorOffset++; + mapController.changeFloor(1); + } + }); + } + + if (floorDownButton != null) { + floorDownButton.setOnClickListener(v -> { + if (mapController != null) { + floorSelectionSyncArmed = true; + manualFloorOffset--; + mapController.changeFloor(-1); + } + }); + } + } + + public void updateElevation() { + if (!isAutoFloorEnabled || mapController == null) { + return; + } + syncMapFloorToSensorFusion(false); + } - // GNSS Switch - gnssSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - isGnssOn = isChecked; - if (!isChecked && gnssMarker != null) { - gnssMarker.remove(); - gnssMarker = null; + private String getFloorLabel(int val) { + if (val == -1) return "BF"; + if (val == 0) return "GF"; + return val + "F"; + } + + private void updateFloorIndicatorUI(int floorVal) { + if (currentFloorIndicator != null) { + String label = getFloorLabel(floorVal); + currentFloorIndicator.setText("Floor: " + label); + } + } + + @Override + public void onMapReady(@NonNull GoogleMap googleMap) { + gMap = googleMap; + initMapSettings(gMap); + + mapController = new BuildingMapController(requireContext(), gMap); + + mapController.setSelectionListener((buildingName, floorCode) -> { + updateManualFloorControlsVisibility(); + + int val = parseFloorCode(floorCode); + currentFloorValue = val; + updateFloorIndicatorUI(val); + + if (!isAutoFloorEnabled) { + manualFloorOffset = val; } - }); - // Color switch - switchColorButton.setOnClickListener(v -> { - if (polyline != null) { - if (isRed) { - switchColorButton.setBackgroundColor(Color.BLACK); - polyline.setColor(Color.BLACK); - isRed = false; - } else { - switchColorButton.setBackgroundColor(Color.RED); - polyline.setColor(Color.RED); - isRed = true; + // Keep SensorFusion floor state aligned with the selected map floor. + if (mapController != null) { + com.openpositioning.PositionMe.sensors.SensorFusion sensorFusion = + com.openpositioning.PositionMe.sensors.SensorFusion.getInstance(); + sensorFusion + .setAvailableFloorPlans(mapController.getSelectedFloorPlanMap()); + sensorFusion + .setCurrentFloorPlan(mapController.getCurrentFloorPlan()); + if (floorSelectionSyncArmed) { + sensorFusion + .setCurrentFloorByMapMatching(val); + } + floorSelectionSyncArmed = false; + if (!sensorFloorSyncInProgress && isAutoFloorEnabled) { + syncMapFloorToSensorFusion(true); } } }); - // Floor up/down logic - autoFloorSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> { + gMap.setOnPolygonClickListener(polygon -> mapController.onPolygonClick(polygon)); - //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); - }); + if (hasPendingCameraMove && pendingCameraPosition != null) { + gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pendingCameraPosition, 19f)); + hasPendingCameraMove = false; + if (mapController != null) { + mapController.downloadNearbyBuildings(pendingCameraPosition); + } + pendingCameraPosition = null; + } else { + LatLng defaultLoc = new LatLng(55.92330, -3.17450); + gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultLoc, 18f)); + if (mapController != null) { + mapController.downloadNearbyBuildings(defaultLoc); + } + } + flushPendingTags(); + flushPendingObservationMarkers(); + } - floorUpButton.setOnClickListener(v -> { - // If user manually changes floor, turn off auto floor - autoFloorSwitch.setChecked(false); - if (indoorMapManager != null) { - indoorMapManager.increaseFloor(); + // Normalizes API floor labels into the local integer floor model. + private int parseFloorCode(String code) { + if (code == null) return 0; + String raw = code.toUpperCase().trim(); + + if (raw.equals("BF") || raw.equals("B")) return -1; + if (raw.equals("GF") || raw.equals("G") || raw.equals("0") || raw.contains("GROUND")) return 0; + if (raw.contains("BASEMENT") || raw.contains("BF")) return -1; + + if (raw.startsWith("B")) { + String digits = raw.replaceAll("[^0-9]", ""); + if (!digits.isEmpty()) { + try { + return -Integer.parseInt(digits); + } catch (Exception ignored) { + } } - }); + return -1; + } - floorDownButton.setOnClickListener(v -> { - autoFloorSwitch.setChecked(false); - if (indoorMapManager != null) { - indoorMapManager.decreaseFloor(); + try { + Matcher matcher = Pattern.compile("-?\\d+").matcher(raw); + if (matcher.find()) { + return Integer.parseInt(matcher.group()); } - }); - } + } catch (Exception ignored) { + } - /** - * 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 - */ + return 0; + } private void initMapSettings(GoogleMap map) { - // Basic map settings map.getUiSettings().setCompassEnabled(true); map.getUiSettings().setTiltGesturesEnabled(true); map.getUiSettings().setRotateGesturesEnabled(true); 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() + fusedPolyline = map.addPolyline(new PolylineOptions() .color(Color.RED) .width(5f) - .add() // start empty - ); - - // GNSS path in blue - gnssPolyline = map.addPolyline(new PolylineOptions() - .color(Color.BLUE) - .width(5f) - .add() // start empty + .zIndex(200f) + .add() ); + fusedPathPoints.clear(); } - - /** - * 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[]{ @@ -260,10 +456,9 @@ private void initMapTypeSpinner() { switchMapSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView parent, View view, - int position, long id) { + 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,267 +470,498 @@ public void onItemSelected(AdapterView parent, View view, break; } } + @Override - public void onNothingSelected(AdapterView parent) {} + public void onNothingSelected(AdapterView parent) { + } }); } + private void initObservationCountSpinner() { + if (observationCountSpinner == null) return; + String[] countOptions = new String[]{"1", "2", "3", getString(R.string.custom_observation_count)}; + ArrayAdapter adapter = new ArrayAdapter<>( + requireContext(), + android.R.layout.simple_spinner_dropdown_item, + countOptions + ); + observationCountSpinner.setAdapter(adapter); + observationCountSpinner.setSelection(2, false); - /** - * 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; + observationCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (position >= 0 && position <= 2) { + setMaxObservationMarkers(position + 1); + } else { + showCustomObservationCountDialog(); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } + private void showCustomObservationCountDialog() { + EditText input = new EditText(requireContext()); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + input.setHint(getString(R.string.custom_observation_count_hint)); + input.setText(String.valueOf(maxObservationMarkers)); + + new AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.custom_observation_count_title)) + .setView(input) + .setPositiveButton(getString(R.string.ok), (dialog, which) -> { + String value = input.getText() == null ? "" : input.getText().toString().trim(); + if (value.isEmpty()) return; + try { + int parsed = Integer.parseInt(value); + if (parsed <= 0) { + Toast.makeText(requireContext(), getString(R.string.custom_observation_count_invalid), Toast.LENGTH_SHORT).show(); + return; + } + setMaxObservationMarkers(parsed); + } catch (NumberFormatException e) { + Toast.makeText(requireContext(), getString(R.string.custom_observation_count_invalid), Toast.LENGTH_SHORT).show(); + } + }) + .setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.dismiss()) + .show(); + } + private void setMaxObservationMarkers(int count) { + maxObservationMarkers = count; + trimObservationQueue(gnssObservationMarkers, getMaxObservationMarkersForType(DisplayObservationType.GNSS)); + trimObservationQueue(wifiObservationMarkers, getMaxObservationMarkersForType(DisplayObservationType.WIFI)); + trimObservationQueue(pdrObservationMarkers, getMaxObservationMarkersForType(DisplayObservationType.PDR)); + refreshObservationQueueStyle(DisplayObservationType.GNSS, gnssObservationMarkers); + refreshObservationQueueStyle(DisplayObservationType.WIFI, wifiObservationMarkers); + refreshObservationQueueStyle(DisplayObservationType.PDR, pdrObservationMarkers); + } + private void trimObservationQueue(ArrayDeque queue, int limit) { + while (queue.size() > limit) { + Marker oldest = queue.removeFirst(); + if (oldest != null) oldest.remove(); + } + } + + private int getMaxObservationMarkersForType(@NonNull DisplayObservationType type) { + return maxObservationMarkers; + } + + // Draws the current fused position and the heading marker used in live display. + public void renderFusedPosition(@NonNull LatLng newLocation, float orientation, boolean moveCamera) { + if (gMap == null || observationMarkerFactory == null) return; - // Keep track of current location - LatLng oldLocation = this.currentLocation; this.currentLocation = newLocation; + float normalizedOrientation = normalizeMarkerRotationDegrees(orientation); - // If no marker, create it if (orientationMarker == null) { orientationMarker = gMap.addMarker(new MarkerOptions() .position(newLocation) .flat(true) - .title("Current Position") + .anchor(0.5f, 0.5f) + .title("Best Estimate") + .zIndex(300f) .icon(BitmapDescriptorFactory.fromBitmap( UtilFunctions.getBitmapFromVector(requireContext(), R.drawable.ic_baseline_navigation_24))) ); + if (orientationMarker != null) { + orientationMarker.setRotation(normalizedOrientation); + } gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(newLocation, 19f)); } else { - // Update marker position + orientation orientationMarker.setPosition(newLocation); - orientationMarker.setRotation(orientation); - // Move camera a bit - gMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation)); + orientationMarker.setRotation(normalizedOrientation); + if (moveCamera) { + gMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation)); + } + } + if (observationMarkerFactory != null) { + if (bestEstimateDotMarker == null) { + bestEstimateDotMarker = gMap.addMarker(new MarkerOptions() + .position(newLocation) + .anchor(0.5f, 0.5f) + .zIndex(320f) + .icon(observationMarkerFactory.getBestEstimateDotIcon())); + } else { + bestEstimateDotMarker.setPosition(newLocation); + } + } + // Assignment 2 requires API map rendering only; local indoor overlays are disabled. + } + + // Backward-compatible wrapper for existing callers + public void updateUserLocation(@NonNull LatLng newLocation, float orientation) { + LatLng displayPoint = newLocation; + if (smoothDisplayEnabled) { + displayPoint = replaySmoother.filter(newLocation); + } else { + replaySmoother.reset(newLocation); + } + renderFusedPosition(displayPoint, orientation, true); + appendFusedTrajectoryPoint(displayPoint); + } + public void appendFusedTrajectoryPoint(@NonNull LatLng point) { + if (gMap == null || fusedPolyline == null) return; + if (fusedPathPoints.isEmpty()) { + fusedPathPoints.add(point); + fusedPolyline.setPoints(fusedPathPoints); + return; + } + LatLng lastPoint = fusedPathPoints.get(fusedPathPoints.size() - 1); + if (UtilFunctions.distanceBetweenPoints(lastPoint, point) < TRAJECTORY_RENDER_MIN_DISTANCE_METERS) { + fusedPathPoints.set(fusedPathPoints.size() - 1, point); + } else { + fusedPathPoints.add(point); + compactTrajectoryIfNeeded(); + } + fusedPolyline.setPoints(fusedPathPoints); + } + + private void compactTrajectoryIfNeeded() { + int currentSize = fusedPathPoints.size(); + if (currentSize <= MAX_TRAJECTORY_RENDER_POINTS) { + return; + } + int overflow = currentSize - TARGET_TRAJECTORY_RENDER_POINTS; + if (overflow <= 0) { + return; + } + int removable = Math.max(0, fusedPathPoints.size() - TRAJECTORY_RECENT_TAIL_POINTS - 1); + int removeCount = Math.min(overflow, removable); + if (removeCount <= 0) { + return; + } + fusedPathPoints.subList(0, removeCount).clear(); + } + + public void addTagPoint(@NonNull LatLng latLng, int index) { + if (gMap == null) { + pendingTagLocations.add(latLng); + pendingTagIndices.add(index); + return; + } + gMap.addMarker(new MarkerOptions() + .position(latLng) + .anchor(0.5f, 0.5f) + .zIndex(300f) + .icon(createNumberedMarkerIcon(index))); + } + + // Buffers marker updates so frequent sensor callbacks do not overload the UI thread. + public void enqueueObservationMarker(@NonNull DisplayObservationType type, @NonNull LatLng point) { + if (pendingObservationUpdates.size() >= MAX_PENDING_OBSERVATION_UPDATES) { + pendingObservationUpdates.removeFirst(); + } + pendingObservationUpdates.addLast(new PendingObservationMarker(type, point)); + } + + public void addObservationMarker(@NonNull DisplayObservationType type, @NonNull LatLng point) { + enqueueObservationMarker(type, point); + flushPendingObservationMarkers(); + } + + public void flushPendingObservationMarkers() { + if (gMap == null || observationMarkerFactory == null || pendingObservationUpdates.isEmpty()) { + return; + } + int processed = 0; + while (!pendingObservationUpdates.isEmpty() && processed < MAX_OBSERVATION_UPDATES_PER_FLUSH) { + PendingObservationMarker pendingMarker = pendingObservationUpdates.removeFirst(); + applyObservationMarker(pendingMarker.type, pendingMarker.point); + processed++; + } + } + + private void applyObservationMarker(@NonNull DisplayObservationType type, @NonNull LatLng point) { + ArrayDeque targetQueue = getObservationQueue(type); + Marker marker = obtainReusableObservationMarker(type, point, targetQueue); + if (marker != null) { + targetQueue.addLast(marker); + refreshObservationQueueStyle(type, targetQueue); + } + + if (type == DisplayObservationType.GNSS) { + if (gnssMarker == null) { + gnssMarker = gMap.addMarker(new MarkerOptions() + .position(point) + .title("Latest GNSS") + .zIndex(260f) + .visible(isGnssOn) + .icon(observationMarkerFactory.getObservationIcon(DisplayObservationType.GNSS))); + } else { + gnssMarker.setPosition(point); + gnssMarker.setVisible(isGnssOn); + } + } + } + + private void refreshObservationQueueStyle(@NonNull DisplayObservationType type, + @NonNull ArrayDeque targetQueue) { + int size = targetQueue.size(); + if (size <= 0) { + return; + } + int index = 0; + for (Marker queuedMarker : targetQueue) { + if (queuedMarker == null) { + index++; + continue; + } + float progress = size <= 1 ? 1f : (index + 1f) / size; + float alpha = OBSERVATION_HISTORY_MIN_ALPHA + + (1f - OBSERVATION_HISTORY_MIN_ALPHA) * progress; + queuedMarker.setAlpha(alpha); + queuedMarker.setZIndex(236f + index); + queuedMarker.setVisible(isObservationVisible(type)); + index++; } + } + + private ArrayDeque getObservationQueue(@NonNull DisplayObservationType type) { + switch (type) { + case GNSS: + return gnssObservationMarkers; + case WIFI: + return wifiObservationMarkers; + default: + return pdrObservationMarkers; + } + } - // 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); + private Marker obtainReusableObservationMarker(@NonNull DisplayObservationType type, + @NonNull LatLng point, + @NonNull ArrayDeque targetQueue) { + Marker marker = null; + int limit = getMaxObservationMarkersForType(type); + if (targetQueue.size() >= limit) { + marker = targetQueue.removeFirst(); + } + if (marker == null) { + marker = gMap.addMarker(new MarkerOptions() + .position(point) + .anchor(0.5f, 0.5f) + .zIndex(240f) + .alpha(1f) + .visible(isObservationVisible(type)) + .icon(observationMarkerFactory.getObservationIcon(type))); + } else { + marker.setPosition(point); + marker.setVisible(isObservationVisible(type)); + marker.setAlpha(1f); + marker.setZIndex(240f); + } + return marker; + } + private boolean isObservationVisible(@NonNull DisplayObservationType type) { + switch (type) { + case GNSS: + return isGnssOn; + case WIFI: + return isWifiObservationOn; + default: + return isPdrObservationOn; } + } - // Update indoor map overlay - if (indoorMapManager != null) { - indoorMapManager.setCurrentLocation(newLocation); - setFloorControlsVisibility(indoorMapManager.getIsIndoorMapSet() ? View.VISIBLE : View.GONE); + // Keeps the displayed indoor floor aligned with the floor chosen by fusion logic. + private void syncMapFloorToSensorFusion(boolean force) { + if (mapController == null) { + return; + } + com.openpositioning.PositionMe.sensors.SensorFusion sensorFusion = + com.openpositioning.PositionMe.sensors.SensorFusion.getInstance(); + int sensorFloor = sensorFusion.getCurrentFloorByMapMatching(); + int mapFloor = mapController.getCurrentFloorValue(); + long nowMs = System.currentTimeMillis(); + if (!force + && sensorFloor == lastRequestedSensorFloor + && sensorFloor == mapFloor + && (nowMs - lastFloorSyncAttemptTimeMs) < FLOOR_SYNC_MIN_INTERVAL_MS) { + return; + } + lastRequestedSensorFloor = sensorFloor; + lastFloorSyncAttemptTimeMs = nowMs; + if (sensorFloor == mapFloor) { + return; + } + sensorFloorSyncInProgress = true; + try { + mapController.setFloorByValue(sensorFloor); + } finally { + sensorFloorSyncInProgress = false; } } + private void setMarkerCollectionVisible(ArrayDeque markers, boolean visible) { + for (Marker marker : markers) { + if (marker != null) { + marker.setVisible(visible); + } + } + } + private void flushPendingTags() { + if (gMap == null || pendingTagLocations.isEmpty()) { + return; + } + int count = Math.min(pendingTagLocations.size(), pendingTagIndices.size()); + for (int i = 0; i < count; i++) { + LatLng latLng = pendingTagLocations.get(i); + Integer index = pendingTagIndices.get(i); + if (latLng != null && index != null) { + gMap.addMarker(new MarkerOptions() + .position(latLng) + .anchor(0.5f, 0.5f) + .zIndex(300f) + .icon(createNumberedMarkerIcon(index))); + } + } + pendingTagLocations.clear(); + pendingTagIndices.clear(); + } - /** - * 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. - */ 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. - */ public LatLng getCurrentLocation() { return currentLocation; } - /** - * Called when we want to set or update the GNSS marker position - */ + public boolean isGnssEnabled() { + return isGnssOn; + } + + public boolean isSmoothDisplayEnabled() { + return smoothDisplayEnabled; + } 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") - .icon(BitmapDescriptorFactory - .defaultMarker(BitmapDescriptorFactory.HUE_AZURE))); - lastGnssLocation = gnssLocation; + .title("Latest GNSS") + .zIndex(260f) + .visible(isGnssOn) + .icon(observationMarkerFactory != null + ? observationMarkerFactory.getObservationIcon(DisplayObservationType.GNSS) + : BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))); } 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); - gnssPolyline.setPoints(gnssPoints); - } - lastGnssLocation = gnssLocation; + gnssMarker.setVisible(isGnssOn); } } - - /** - * Remove GNSS marker if user toggles it off - */ public void clearGNSS() { if (gnssMarker != null) { gnssMarker.remove(); gnssMarker = null; } + for (Marker marker : gnssObservationMarkers) { + if (marker != null) marker.remove(); + } + gnssObservationMarkers.clear(); } - /** - * Whether user is currently showing GNSS or not - */ - public boolean isGnssEnabled() { - return isGnssOn; + private void setFloorControlsVisibility(int visibility) { + if (floorControlCard != null) { + floorControlCard.setVisibility(visibility); + } } - private void setFloorControlsVisibility(int visibility) { - floorUpButton.setVisibility(visibility); - floorDownButton.setVisibility(visibility); - autoFloorSwitch.setVisibility(visibility); + private void updateManualFloorControlsVisibility() { + setFloorControlsVisibility(isAutoFloorEnabled ? View.GONE : View.VISIBLE); } + // Clears all temporary map state so the next recording starts from a clean display. public void clearMapAndReset() { - if (polyline != null) { - polyline.remove(); - polyline = null; - } - if (gnssPolyline != null) { - gnssPolyline.remove(); - gnssPolyline = null; + if (fusedPolyline != null) { + fusedPolyline.remove(); + fusedPolyline = null; } if (orientationMarker != null) { orientationMarker.remove(); orientationMarker = null; } + if (bestEstimateDotMarker != null) { + bestEstimateDotMarker.remove(); + bestEstimateDotMarker = null; + } if (gnssMarker != null) { gnssMarker.remove(); gnssMarker = null; } - lastGnssLocation = null; - currentLocation = null; - // Re-create empty polylines with your chosen colors + clearObservationQueue(gnssObservationMarkers); + clearObservationQueue(wifiObservationMarkers); + clearObservationQueue(pdrObservationMarkers); + pendingObservationUpdates.clear(); + fusedPathPoints.clear(); + pendingTagLocations.clear(); + pendingTagIndices.clear(); + pendingCameraPosition = null; + hasPendingCameraMove = false; + + currentLocation = null; + + isAutoFloorEnabled = true; + smoothDisplayEnabled = DEFAULT_SMOOTH_DISPLAY_ENABLED; + isRed = true; + replaySmoother.reset(); + floorSelectionSyncArmed = false; + sensorFloorSyncInProgress = false; + lastFloorSyncAttemptTimeMs = 0L; + lastRequestedSensorFloor = Integer.MIN_VALUE; + isWifiObservationOn = true; + isPdrObservationOn = true; + com.openpositioning.PositionMe.sensors.SensorFusion.getInstance().consumePendingFloorDelta(); + maxObservationMarkers = DEFAULT_MAX_OBSERVATION_MARKERS; + currentFloorValue = 0; + manualFloorOffset = 0; + + if (currentFloorIndicator != null) updateFloorIndicatorUI(0); + if (gnssSwitch != null) gnssSwitch.setChecked(true); + if (wifiObservationSwitch != null) wifiObservationSwitch.setChecked(true); + if (pdrObservationSwitch != null) pdrObservationSwitch.setChecked(true); + if (autoFloorSwitch != null) autoFloorSwitch.setChecked(true); + if (smoothDisplaySwitch != null) smoothDisplaySwitch.setChecked(DEFAULT_SMOOTH_DISPLAY_ENABLED); + if (observationCountSpinner != null) observationCountSpinner.setSelection(2, false); + if (mapControlPanelController != null) mapControlPanelController.setCollapsed(true); + if (switchColorButton != null) switchColorButton.setBackgroundColor(Color.RED); + updateManualFloorControlsVisibility(); + if (gMap != null) { - polyline = gMap.addPolyline(new PolylineOptions() + fusedPolyline = gMap.addPolyline(new PolylineOptions() .color(Color.RED) .width(5f) + .zIndex(200f) .add()); - gnssPolyline = gMap.addPolyline(new PolylineOptions() - .color(Color.BLUE) - .width(5f) - .add()); + fusedPathPoints.clear(); } } - /** - * 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"); - return; + private void clearObservationQueue(ArrayDeque queue) { + while (!queue.isEmpty()) { + Marker marker = queue.removeFirst(); + if (marker != null) marker.remove(); } + } - // 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); - - - - 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 - if (buildingPolygon != null) { - buildingPolygon.remove(); + private float normalizeMarkerRotationDegrees(float degrees) { + if (!Float.isFinite(degrees)) { + return 0f; } - - // 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()); + float normalized = degrees % 360f; + if (normalized < 0f) { + normalized += 360f; + } + return normalized; } - - } diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/UploadFragment.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/UploadFragment.java index 9d435812..e7585abb 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/UploadFragment.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/fragment/UploadFragment.java @@ -5,8 +5,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import android.os.Environment; -import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -63,26 +61,33 @@ public UploadFragment() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Get communication class serverCommunications = new ServerCommunications(getActivity()); + localTrajectories = new java.util.ArrayList<>(); + } - // Determine the directory to load trajectory files from. - File trajectoriesDir = null; - - // for android 13 or higher use dedicated external storage - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - trajectoriesDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); - if (trajectoriesDir == null) { - trajectoriesDir = getActivity().getFilesDir(); - } - } else { // for android 12 or lower use internal storage - trajectoriesDir = getActivity().getFilesDir(); + /** + * Rescans local storage each time the fragment becomes visible, so newly saved recordings + * are found without requiring the user to restart the app. + */ + @Override + public void onResume() { + super.onResume(); + // Use the same directory and extension as SensorFusion.stopRecording(): + // getExternalFilesDir(null) with "term_project_trajectory_*.proto" + File trajectoriesDir = requireActivity().getExternalFilesDir(null); + if (trajectoriesDir == null) { + trajectoriesDir = requireActivity().getFilesDir(); + } + File[] files = trajectoriesDir.listFiles((file, name) -> + name.contains("trajectory_") && name.endsWith(".proto")); + localTrajectories = files != null + ? Stream.of(files).filter(file -> !file.isDirectory()).collect(Collectors.toList()) + : new java.util.ArrayList<>(); + + // Refresh UI if the view is already created + if (emptyNotice != null && uploadList != null) { + refreshListUI(); } - - localTrajectories = Stream.of(trajectoriesDir.listFiles((file, name) -> - name.contains("trajectory_") && name.endsWith(".txt"))) - .filter(file -> !file.isDirectory()) - .collect(Collectors.toList()); } /** @@ -113,34 +118,31 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - this.emptyNotice = view.findViewById(R.id.emptyUpload); this.uploadList = view.findViewById(R.id.uploadTrajectories); - // Check if there are locally saved trajectories - if(localTrajectories.isEmpty()) { + LinearLayoutManager manager = new LinearLayoutManager(getActivity()); + uploadList.setLayoutManager(manager); + uploadList.setHasFixedSize(true); + refreshListUI(); + } + + private void refreshListUI() { + if (localTrajectories.isEmpty()) { uploadList.setVisibility(View.GONE); emptyNotice.setVisibility(View.VISIBLE); - } - else { + } else { uploadList.setVisibility(View.VISIBLE); emptyNotice.setVisibility(View.GONE); - - // Set up RecyclerView - LinearLayoutManager manager = new LinearLayoutManager(getActivity()); - uploadList.setLayoutManager(manager); - uploadList.setHasFixedSize(true); - listAdapter = new UploadListAdapter(getActivity(), localTrajectories, new DownloadClickListener() { - /** - * {@inheritDoc} - * Upload the trajectory at the clicked position, remove it from the recycler view - * and the local list. - */ - @Override - public void onPositionClicked(int position) { - serverCommunications.uploadLocalTrajectory(localTrajectories.get(position)); -// localTrajectories.remove(position); -// listAdapter.notifyItemRemoved(position); - } + listAdapter = new UploadListAdapter(getActivity(), localTrajectories, position -> { + File file = localTrajectories.get(position); + serverCommunications.uploadLocalTrajectory(file, () -> { + // Delete the local file and remove from list after successful upload + file.delete(); + if (localTrajectories.remove(file)) { + listAdapter.notifyDataSetChanged(); + if (localTrajectories.isEmpty()) refreshListUI(); + } + }); }); uploadList.setAdapter(listAdapter); } 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..971ad042 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 @@ -85,7 +85,14 @@ public TrajDownloadListAdapter(Context context, List> respon */ private void loadDownloadRecords() { try { - File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "download_records.json"); + File downloadsFolder = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); + if (downloadsFolder == null) { + Log.w("TrajDownloadListAdapter", "Downloads directory is unavailable."); + return; + } + + ServerCommunications.downloadRecords.clear(); + File file = new File(downloadsFolder, "download_records.json"); if (file.exists()) { // Read the file line by line to reduce memory usage. StringBuilder jsonBuilder = new StringBuilder(); @@ -98,7 +105,6 @@ private void loadDownloadRecords() { // Parse the JSON content. JSONObject jsonObject = new JSONObject(jsonBuilder.toString()); - ServerCommunications.downloadRecords.clear(); // Preallocate HashMap capacity to reduce resizing overhead. int estimatedSize = jsonObject.length(); @@ -128,7 +134,7 @@ private void loadDownloadRecords() { System.out.println("Download records file not found."); } } catch (Exception e) { - e.printStackTrace(); + Log.e("TrajDownloadListAdapter", "Failed to load download records.", e); } } @@ -142,12 +148,15 @@ private void initFileObserver() { return; } // Create a FileObserver for the directory where the file is located. - fileObserver = new FileObserver(downloadsFolder.getAbsolutePath(), FileObserver.MODIFY) { + fileObserver = new FileObserver( + downloadsFolder.getAbsolutePath(), + FileObserver.CREATE | FileObserver.MODIFY | FileObserver.CLOSE_WRITE | FileObserver.MOVED_TO + ) { @Override public void onEvent(int event, String path) { // Only act if the modified file is "download_records.json". if (path != null && path.equals("download_records.json")) { - Log.i("FileObserver", "download_records.json has been modified."); + Log.i("TrajDownloadListAdapter", "download_records.json changed. event=" + event); // On file modification, load the records and update the UI on the main thread. new Handler(Looper.getMainLooper()).post(() -> { loadDownloadRecords(); @@ -158,6 +167,21 @@ public void onEvent(int event, String path) { fileObserver.startWatching(); } + private File resolveDownloadedFile(JSONObject recordDetails) { + if (recordDetails == null) { + return null; + } + + String fileName = recordDetails.optString("file_name", null); + File downloadsFolder = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); + if (fileName == null || downloadsFolder == null) { + return null; + } + + File file = new File(downloadsFolder, fileName); + return file.isFile() ? file : null; + } + /** * Creates a new view holder for a trajectory item. * @@ -197,30 +221,27 @@ public void onBindViewHolder(@NonNull TrajDownloadViewHolder holder, int positio // Parse and format the submission date. String dateSubmittedStr = responseItems.get(position).get("date_submitted"); - assert dateSubmittedStr != null; - holder.getTrajDate().setText( - dateFormat.format( - LocalDateTime.parse(dateSubmittedStr.split("\\.")[0]) - ) - ); + if (dateSubmittedStr != null) { + try { + // Strip sub-seconds; support both ISO 'T' and space separator + String datePart = dateSubmittedStr.split("\\.")[0].replace(' ', 'T'); + holder.getTrajDate().setText(dateFormat.format(LocalDateTime.parse(datePart))); + } catch (Exception e) { + holder.getTrajDate().setText(dateSubmittedStr); + } + } else { + holder.getTrajDate().setText("N/A"); + } // Determine if the trajectory is already downloaded by checking the records. JSONObject recordDetails = ServerCommunications.downloadRecords.get(id); - boolean matched = recordDetails != null; - String filePath = null; + File downloadedFile = resolveDownloadedFile(recordDetails); + boolean matched = downloadedFile != null; + String filePath = downloadedFile != null ? downloadedFile.getAbsolutePath() : null; if (matched) { - try { - String fileName = recordDetails.optString("file_name", null); - if (fileName != null) { - File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName); - filePath = file.getAbsolutePath(); - } - // Set the button state to "downloaded". - setButtonState(holder.downloadButton, 1); - } catch (Exception e) { - e.printStackTrace(); - } + // Set the button state to "downloaded". + 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); @@ -245,6 +266,9 @@ public void onBindViewHolder(@NonNull TrajDownloadViewHolder holder, int positio context.startActivity(intent); } } else { + if (downloadingTrajIds.contains(trajId)) { + return; + } // If the item is not downloaded, trigger the download action. listener.onPositionClicked(position); // Mark the trajectory as downloading. @@ -268,6 +292,15 @@ public int getItemCount() { return responseItems.size(); } + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + if (fileObserver != null) { + fileObserver.stopWatching(); + fileObserver = null; + } + } + /** * Sets the appearance of the button based on its state. * diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadViewHolder.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadViewHolder.java index af14249f..ee36c381 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadViewHolder.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/TrajDownloadViewHolder.java @@ -63,13 +63,9 @@ public TextView getTrajDate() { */ @Override public void onClick(View view) { - listenerReference.get().onPositionClicked(getAdapterPosition()); DownloadClickListener listener = listenerReference.get(); if (listener != null) { listener.onPositionClicked(getAdapterPosition()); - System.out.println("Click detected at position: " + getAdapterPosition()); - } else { - System.err.println("Listener reference is null."); } } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadListAdapter.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadListAdapter.java index b564e231..15d0fd74 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadListAdapter.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadListAdapter.java @@ -65,16 +65,24 @@ public UploadViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewTy */ @Override public void onBindViewHolder(@NonNull UploadViewHolder holder, int position) { - holder.trajId.setText(String.valueOf(position)); - Pattern datePattern = Pattern.compile("_(.*?)\\.txt"); + holder.trajId.setText(String.valueOf(position + 1)); + Pattern datePattern = Pattern.compile("_(\\d+)\\.proto"); Matcher dateMatcher = datePattern.matcher(uploadItems.get(position).getName()); - String dateString = dateMatcher.find() ? dateMatcher.group(1) : "N/A"; - System.err.println("UPLOAD - Date string: " + dateString); + String dateString = "N/A"; + if (dateMatcher.find()) { + try { + long epochMs = Long.parseLong(dateMatcher.group(1)); + dateString = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", + java.util.Locale.getDefault()).format(new java.util.Date(epochMs)); + } catch (NumberFormatException e) { + dateString = dateMatcher.group(1); + } + } holder.trajDate.setText(dateString); - // Set click listener for the delete button + // Bind both buttons directly with position to avoid getAdapterPosition() returning NO_ID + holder.uploadButton.setOnClickListener(v -> listener.onPositionClicked(position)); holder.deletebutton.setOnClickListener(v -> deleteFileAtPosition(position)); - } /** diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadViewHolder.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadViewHolder.java index e6068969..1047e025 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadViewHolder.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/UploadViewHolder.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + import com.google.android.material.button.MaterialButton; import com.openpositioning.PositionMe.R; import com.openpositioning.PositionMe.presentation.fragment.UploadFragment; @@ -57,6 +58,14 @@ public UploadViewHolder(@NonNull View itemView, DownloadClickListener listener) */ @Override public void onClick(View view) { - listenerReference.get().onPositionClicked(getAdapterPosition()); + // Upload button click is handled per-position in onBindViewHolder to avoid + // getAdapterPosition() returning NO_ID after RecyclerView recycles this holder. + DownloadClickListener listener = listenerReference.get(); + if (listener != null) { + int pos = getAdapterPosition(); + if (pos != RecyclerView.NO_ID) { + listener.onPositionClicked(pos); + } + } } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/WifiListAdapter.java b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/WifiListAdapter.java index 887e7689..6689baf2 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/WifiListAdapter.java +++ b/app/src/main/java/com/openpositioning/PositionMe/presentation/viewitems/WifiListAdapter.java @@ -10,6 +10,8 @@ import com.openpositioning.PositionMe.R; import com.openpositioning.PositionMe.sensors.Wifi; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -22,8 +24,8 @@ */ public class WifiListAdapter extends RecyclerView.Adapter { - Context context; - List items; + private final Context context; + private final List items; /** * Default public constructor with context for inflating views and list to be displayed. @@ -35,7 +37,20 @@ public class WifiListAdapter extends RecyclerView.Adapter { */ public WifiListAdapter(Context context, List items) { this.context = context; - this.items = items; + this.items = new ArrayList<>(); + submitItems(items); + } + + public void submitItems(List items) { + this.items.clear(); + if (items != null && !items.isEmpty()) { + this.items.addAll(items); + } + notifyDataSetChanged(); + } + + public List getItems() { + return Collections.unmodifiableList(items); } /** 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..6aca8a23 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/GNSSDataProcessor.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; +import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.widget.Toast; @@ -100,18 +101,90 @@ private boolean checkLocationPermissions() { public void startLocationUpdates() { //if (sharedPreferences.getBoolean("location", true)) { boolean permissionGranted = checkLocationPermissions(); - if (permissionGranted && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && - locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){ + if (!permissionGranted) { + return; + } + + boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + boolean networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + boolean requestedAnyProvider = false; + + if (gpsEnabled) { + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0, + 0, + locationListener + ); + requestedAnyProvider = true; + } + if (networkEnabled) { + locationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + 0, + 0, + locationListener + ); + requestedAnyProvider = true; + } - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); - locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener); + if (requestedAnyProvider) { + dispatchBestLastKnownLocation(gpsEnabled, networkEnabled); } - else if(permissionGranted && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){ + + if (!gpsEnabled) { Toast.makeText(context, "Open GPS", Toast.LENGTH_LONG).show(); } - else if(permissionGranted && !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){ - Toast.makeText(context, "Turn on WiFi", Toast.LENGTH_LONG).show(); + if (!networkEnabled) { + Toast.makeText(context, "Enable network location/WiFi", Toast.LENGTH_LONG).show(); + } + } + + @SuppressLint("MissingPermission") + private void dispatchBestLastKnownLocation(boolean gpsEnabled, boolean networkEnabled) { + Location bestLocation = null; + + if (gpsEnabled) { + bestLocation = chooseBetterLocation( + bestLocation, + locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) + ); + } + if (networkEnabled) { + bestLocation = chooseBetterLocation( + bestLocation, + locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) + ); + } + + if (bestLocation != null) { + locationListener.onLocationChanged(bestLocation); + } + } + + private Location chooseBetterLocation(Location currentBest, Location candidate) { + if (candidate == null) { + return currentBest; + } + if (currentBest == null) { + return candidate; + } + + long timeDeltaMs = candidate.getTime() - currentBest.getTime(); + if (timeDeltaMs > 15_000L) { + return candidate; + } + if (candidate.hasAccuracy() && currentBest.hasAccuracy()) { + if (candidate.getAccuracy() + 2f < currentBest.getAccuracy()) { + return candidate; + } + if (timeDeltaMs >= 0 && candidate.getAccuracy() <= currentBest.getAccuracy() + 4f) { + return candidate; + } + } else if (timeDeltaMs >= 0) { + return candidate; } + return currentBest; } /** diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/MovementSensor.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/MovementSensor.java index a5282150..2cfb38b2 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/MovementSensor.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/MovementSensor.java @@ -24,7 +24,7 @@ public class MovementSensor { protected Sensor sensor; // Information about the sensor stored in a SensorInfo object protected SensorInfo sensorInfo; - + public float[] values; /** * Public default constructor for the Movement Sensor class. 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..3bf099fa 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; +import android.hardware.GeomagneticField; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -9,30 +10,41 @@ import android.location.Location; import android.location.LocationListener; import android.os.Build; +import android.os.HandlerThread; import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; - +import com.openpositioning.PositionMe.Traj; import com.google.android.gms.maps.model.LatLng; +import com.openpositioning.PositionMe.data.remote.ServerCommunications; import com.openpositioning.PositionMe.presentation.activity.MainActivity; +import com.openpositioning.PositionMe.presentation.fragment.SettingsFragment; +import com.openpositioning.PositionMe.utils.CircularFloatBuffer; import com.openpositioning.PositionMe.utils.PathView; import com.openpositioning.PositionMe.utils.PdrProcessing; -import com.openpositioning.PositionMe.data.remote.ServerCommunications; -import com.openpositioning.PositionMe.Traj; -import com.openpositioning.PositionMe.presentation.fragment.SettingsFragment; +import com.openpositioning.PositionMe.utils.VenueSelectionHelper; import org.json.JSONException; import org.json.JSONObject; +import java.io.File; +import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -56,12 +68,18 @@ * @author Michal Dvorak * @author Mate Stodulka * @author Virginia Cangelosi + * + * New code guide: + * 1. Floor-plan state shared with map matching and indoor display. + * 2. PDR alignment, wall guidance, and zero-velocity correction. + * 3. Particle-filter fusion for GNSS/WiFi/PDR updates. + * 4. Automatic floor switching and floor-plan-aware display snapshots. */ public class SensorFusion implements SensorEventListener, Observer { // Store the last event timestamps for each sensor type - private HashMap lastEventTimestamps = new HashMap<>(); - private HashMap eventCounts = new HashMap<>(); + private final Map lastEventTimestamps = new ConcurrentHashMap<>(); + private final Map eventCounts = new ConcurrentHashMap<>(); long maxReportLatencyNs = 0; // Disable batching to deliver events immediately @@ -85,7 +103,10 @@ public class SensorFusion implements SensorEventListener, Observer { // Keep device awake while recording private PowerManager.WakeLock wakeLock; private Context appContext; - + private HandlerThread motionSensorThread; + private Handler motionSensorHandler; + private HandlerThread sensorCallbackThread; + private Handler sensorCallbackHandler; // Settings private SharedPreferences settings; @@ -98,6 +119,7 @@ public class SensorFusion implements SensorEventListener, Observer { private MovementSensor magnetometerSensor; private MovementSensor stepDetectionSensor; private MovementSensor rotationSensor; + private MovementSensor gameRotationSensor; private MovementSensor gravitySensor; private MovementSensor linearAccelerationSensor; // Other data recording @@ -110,9 +132,9 @@ public class SensorFusion implements SensorEventListener, Observer { private ServerCommunications serverCommunications; // Trajectory object containing all data private Traj.Trajectory.Builder trajectory; - // Settings private boolean saveRecording; + private String pendingRecordingName = ""; private float filter_coefficient; // Variables to help with timed events private long absoluteStartTime; @@ -132,6 +154,7 @@ public class SensorFusion implements SensorEventListener, Observer { private float[] angularVelocity; private float[] orientation; private float[] rotation; + private float[] gameRotation; private float pressure; private float light; private float proximity; @@ -147,17 +170,229 @@ public class SensorFusion implements SensorEventListener, Observer { // Wifi values private List wifiList; + // Assignment 2 fusion state in local XY coordinates and floor-plan space. + private com.openpositioning.PositionMe.utils.CoordinateUtils coordinateUtils; + private com.openpositioning.PositionMe.data.remote.FloorPlan currentFloorPlan; + private final Map availableFloorPlans = new HashMap<>(); + private volatile LatLng latestGnssLatLng; + private volatile LatLng latestWifiLatLng; + private volatile float[] latestGnssXY = null; + private volatile float[] latestWifiXY = null; + private volatile float[] latestRawPdrXY = new float[]{0f, 0f}; + private volatile float[] pdrAlignmentOffsetXY = new float[]{0f, 0f}; + private volatile float[] latestPdrXY = new float[]{0f, 0f}; + private volatile float[] latestPfXY = null; + private volatile float[] latestFusedXY = new float[]{0f, 0f}; + private volatile long lastDisplayTrajectoryUpdateMs = 0L; + private volatile float latestMotionHeadingRad = Float.NaN; + private volatile long lastMotionHeadingTimeMs = 0L; + private volatile float latestHorizontalAccelerationHeadingRad = Float.NaN; + private volatile float latestHorizontalAccelerationMagnitude = 0f; + private volatile long lastHorizontalAccelerationHeadingTimeMs = 0L; + private volatile float latestWallAlignedHeadingRad = Float.NaN; + private volatile long lastWallAlignedHeadingTimeMs = 0L; + private volatile long lastHeadingSensorUpdateMs = 0L; + private volatile long lastGameHeadingSensorUpdateMs = 0L; + private volatile boolean headingInitialized = false; + private volatile float filteredGameHeadingRad = Float.NaN; + private volatile float gameHeadingAlignmentOffsetRad = Float.NaN; + private volatile float latestMagneticFieldStrengthUt = Float.NaN; + private volatile long lastMagneticFieldUpdateMs = 0L; + private volatile long lastMagneticDisturbanceTimeMs = 0L; + private volatile boolean magneticDisturbanceActive = false; + private volatile float latestGnssAccuracyMeters = Float.NaN; + private volatile float latestGnssSpeedMps = Float.NaN; + private volatile Integer latestWifiObservedFloor = null; + private volatile long latestGnssFixTimeMs = 0L; + private volatile long latestWifiFixTimeMs = 0L; + private volatile long lastGnssEnqueueMs = 0L; + private volatile float[] lastAcceptedWifiXY = null; + private volatile float[] lastRejectedWifiXY = null; + private volatile long lastAcceptedWifiFixTimeMs = 0L; + private volatile int consecutiveRejectedWifiUpdates = 0; + private volatile int repeatedRejectedWifiClusterCount = 0; + private volatile boolean wifiPositionRequestInFlight = false; + private volatile boolean pendingWifiPositionRequest = false; + private volatile long lastWifiPositionRequestMs = 0L; + private volatile String lastWifiFingerprintSignature = ""; + private volatile long lastWifiFingerprintSignatureTimeMs = 0L; + private volatile int latestWifiApCount = 0; + private volatile float latestWifiAverageLevelDbm = Float.NaN; + private volatile float latestWifiQuality01 = 0f; + private volatile boolean latestWifiUsesCoarsePositioning = false; + private volatile long lastAppliedGnssMeasurementMs = 0L; + private volatile long lastFusedUpdateTimeMs = 0L; + private volatile long lastAbsoluteMeasurementTimeMs = 0L; + private volatile long lastZeroVelocityCorrectionTimeMs = 0L; + private volatile long lastWallGuidanceTimeMs = 0L; + private volatile int consecutiveWallGuidedSteps = 0; + private int pendingFloorDelta = 0; + public int currentFloor = 0; + private int initialFloorHint = 0; + private long lastFloorSwitchTimeMs = 0L; + private float confirmedFloorReferenceElevation = 0f; + private boolean confirmedFloorReferenceInitialized = false; + private boolean wifiAnchorEstablished = false; + private boolean userProvidedStartLocation = false; + private com.openpositioning.PositionMe.utils.MapMatchingEngine mapMatchingEngine; + private com.openpositioning.PositionMe.utils.ParticleFilter particleFilter; + + private static final long WIFI_FRESH_TIMEOUT_MS = 30000L; + private static final long GNSS_FRESH_TIMEOUT_MS = 8000L; + private static final long WIFI_INITIALIZATION_GRACE_MS = 7000L; + private static final float MAX_TRAJECTORY_STEP_METERS = 3.2f; + private static final float WALL_GUIDANCE_MARGIN_METERS = 0.22f; + private static final float WALL_GUIDANCE_HEADING_BLEND = 0.65f; // was 0.82f โ€” less wall heading lock, faster detach on turns + private static final float WALL_GUIDANCE_MIN_STEP_METERS = 0.08f; + private static final float WALL_GUIDANCE_MIN_CORRECTION_METERS = 0.03f; + private static final float WALL_GUIDANCE_COLLISION_INTENT_MIN_GAIN_METERS = 0.05f; + private static final float WALL_GUIDANCE_SENSOR_INTENT_BLEND = 0.28f; + private static final float WALL_GUIDANCE_MOTION_INTENT_BLEND = 0.18f; + private static final float WALL_GUIDANCE_ACCEL_INTENT_BLEND = 0.34f; + private static final long WALL_GUIDANCE_RESET_TIMEOUT_MS = 2200L; + private static final int WALL_LOCK_RECOVERY_REQUIRED_STEPS = 1; + private static final float WALL_LOCK_RECOVERY_BLEND = 0.42f; + private static final float WALL_LOCK_RECOVERY_MAX_SHIFT_METERS = 0.90f; + private static final float WALL_LOCK_RECOVERY_MIN_WIFI_GAIN_METERS = 0.35f; + private static final float AUTO_FLOOR_HEIGHT_STEP_METERS = 4.0f; + private static final long FLOOR_SWITCH_COOLDOWN_MS = 900L; + private static final float DEFAULT_WIFI_MEASUREMENT_STD_METERS = 3.0f; + private static final float WIFI_REANCHOR_STD_METERS = 1.8f; + private static final float DEFAULT_PARTICLE_INIT_STD_METERS = 1.2f; + private static final float FLOOR_TRANSITION_CONFIRM_RATIO = 0.65f; + private static final float FLOOR_TRANSITION_MIN_METERS = 2.0f; + private static final float WIFI_OUTLIER_BASE_METERS = 3.2f; + private static final float WIFI_OUTLIER_SPEED_LIMIT_MPS = 1.6f; + private static final float WIFI_OUTLIER_MAX_METERS = 16f; + private static final int WIFI_OUTLIER_REJECT_LIMIT = 4; + private static final long WIFI_POSITION_REQUEST_MIN_INTERVAL_MS = 1800L; + private static final long WIFI_DUPLICATE_FINGERPRINT_SUPPRESSION_MS = 2200L; + private static final int WIFI_FINGERPRINT_MAX_AP_COUNT = 12; + private static final int WIFI_MIN_AP_COUNT_FOR_POSITIONING = 4; + private static final int WIFI_MIN_AP_COUNT_FOR_FINE_POSITIONING = 6; + private static final int WIFI_MIN_AP_COUNT_FOR_STRONG_FIX = 8; + private static final int WIFI_STRONG_SIGNAL_LEVEL_DBM = -72; + private static final int WIFI_ACCEPTABLE_SIGNAL_LEVEL_DBM = -80; + private static final int WIFI_MIN_SIGNAL_LEVEL_DBM = -92; + private static final int WIFI_MIN_STRONG_APS_FOR_FINE = 2; + private static final long GNSS_ENQUEUE_MIN_INTERVAL_MS = 900L; + private static final float GNSS_MIN_STD_STANDALONE_METERS = 2.8f; + private static final float GNSS_WIFI_OUTLIER_ASSIST_MAX_ACCURACY_METERS = 6f; + private static final float GNSS_WIFI_OUTLIER_ASSIST_MAX_DISTANCE_METERS = 5f; + private static final float GNSS_HIGH_CONFIDENCE_MAX_ACCURACY_METERS = 5f; + private static final float GNSS_REDUCED_WEIGHT_MAX_ACCURACY_METERS = 10f; + private static final float GNSS_LOW_CONFIDENCE_MAX_ACCURACY_METERS = 15f; + private static final float GNSS_HIGH_CONFIDENCE_MAX_STD_METERS = 4.6f; + private static final float GNSS_LOW_CONFIDENCE_MAX_STD_METERS = 12f; + private static final float GNSS_DISCARD_ABSOLUTE_ACCURACY_METERS = 15f; + private static final float GNSS_FULL_FUSION_WEIGHT = 0.80f; + private static final float GNSS_REDUCED_FUSION_WEIGHT = 0.10f; + private static final float GNSS_MINIMAL_FUSION_WEIGHT = 0.02f; + private static final long GNSS_HIGH_CONFIDENCE_UPDATE_MIN_INTERVAL_MS = 1200L; + private static final long GNSS_LOW_CONFIDENCE_UPDATE_MIN_INTERVAL_MS = 2200L; + private static final float WIFI_START_ANCHOR_WEIGHT = 0.94f; + private static final float WIFI_MEASUREMENT_MAX_CORRECTION_METERS = 14.0f; + private static final float WIFI_OUTLIER_RELEASE_MARGIN_METERS = 18.0f; + private static final float WIFI_PDR_ALIGNMENT_MAX_SHIFT_METERS = 2.0f; + private static final float WIFI_OUTLIER_WEAK_UPDATE_STD_METERS = 12.5f; + private static final float WIFI_REJECTED_CLUSTER_RADIUS_METERS = 5.5f; + private static final int WIFI_REJECTED_CLUSTER_ACCEPT_COUNT = 2; + private static final long MANUAL_START_WIFI_ASSIST_WINDOW_MS = 15000L; + private static final float MANUAL_START_PARTICLE_INIT_STD_METERS = 0.9f; + private static final float WIFI_MOBILE_ASSIST_MAX_GNSS_ACCURACY_METERS = 12f; + private static final float WIFI_MOBILE_ASSIST_STRONG_GNSS_BLEND = 0.16f; + private static final float WIFI_MOBILE_ASSIST_WEAK_GNSS_BLEND = 0.10f; + private static final float WIFI_MOBILE_ASSIST_CLAMP_MARGIN_METERS = 6f; + private static final float WIFI_MANUAL_START_CLAMP_METERS = 8f; + private static final float WIFI_MANUAL_START_WITH_GNSS_CLAMP_METERS = 6f; + private static final float WIFI_COARSE_MEASUREMENT_STD_METERS = 7.8f; + private static final float WIFI_STRONG_FINE_MEASUREMENT_STD_METERS = 3.2f; + private static final float WIFI_MOTION_LATERAL_CLAMP_METERS = 1.1f; + private static final float WIFI_MOTION_LATERAL_RELAXED_CLAMP_METERS = 1.8f; + private static final float WIFI_MOTION_FORWARD_CLAMP_METERS = 5.5f; + private static final float WIFI_MOTION_FORWARD_RELAXED_CLAMP_METERS = 8.0f; + private static final float WIFI_MOTION_BACKTRACK_CLAMP_METERS = 0.65f; + private static final float WIFI_MOTION_BACKTRACK_RELAXED_CLAMP_METERS = 1.25f; + private static final int PARTICLE_COUNT = 140; + private static final float HEADING_SMOOTH_ALPHA = 0.58f; + private static final float GAME_HEADING_SMOOTH_ALPHA = 0.24f; + private static final float GAME_HEADING_ALIGNMENT_ALPHA = 0.08f; + private static final long MOTION_HEADING_FRESH_TIMEOUT_MS = 2200L; + private static final long HORIZONTAL_ACCEL_HEADING_FRESH_TIMEOUT_MS = 850L; + private static final float HORIZONTAL_ACCEL_HEADING_MIN_MAGNITUDE_MPS2 = 0.16f; + private static final float HORIZONTAL_ACCEL_HEADING_BLEND = 0.34f; + private static final long WALL_HEADING_OVERRIDE_TIMEOUT_MS = 2600L; + private static final float WALL_HEADING_OVERRIDE_BLEND = 0.82f; + private static final long PDR_WIFI_PULL_FRESH_TIMEOUT_MS = 9000L; + private static final float PDR_WIFI_PULL_STRONG_DRIFT_METERS = 4.8f; + private static final float PDR_WIFI_PULL_WEAK_DRIFT_METERS = 6.8f; + private static final float PDR_WIFI_PULL_COARSE_DRIFT_METERS = 8.5f; + private static final long GAME_HEADING_FRESH_TIMEOUT_MS = 2200L; + private static final long MAGNETIC_FIELD_FRESH_TIMEOUT_MS = 1800L; + private static final long MAGNETIC_DISTURBANCE_HOLD_MS = 2600L; + private static final float MAGNETIC_FIELD_STRENGTH_DELTA_UT = 8f; + private static final float MAGNETIC_FIELD_STRENGTH_RATIO_DELTA = 0.18f; + private static final float MAGNETIC_HEADING_DISAGREEMENT_RAD = (float) Math.toRadians(30.0); + private static final float DISPLAY_HEADING_LOW_MOTION_BLEND = 0.38f; + private static final float DISPLAY_HEADING_MEDIUM_MOTION_BLEND = 0.56f; + private static final float DISPLAY_HEADING_HIGH_MOTION_BLEND = 0.72f; + private static final float DISPLAY_HEADING_ACTIVE_MOTION_BLEND = 0.84f; + private static final float DISPLAY_HEADING_FORCE_MOTION_BLEND = 0.92f; + private static final long DISPLAY_HEADING_ACTIVE_STEP_TIMEOUT_MS = 1700L; + private static final float DISPLAY_HEADING_ACTIVE_GNSS_SPEED_MPS = 0.55f; + private static final float FUSED_MOTION_HEADING_MIN_DISTANCE_METERS = 0.32f; + private static final float FUSED_MOTION_HEADING_MAX_DISTANCE_METERS = 1.9f; + private static final long FLOOR_SWITCH_HEADING_FREEZE_MS = 1800L; + private static final float DISPLAY_HEADING_CLOSE_AGREEMENT_RAD = (float) Math.toRadians(18.0); + private static final float DISPLAY_HEADING_MEDIUM_AGREEMENT_RAD = (float) Math.toRadians(40.0); + private static final float DISPLAY_HEADING_FORCE_MOTION_DISAGREEMENT_RAD = (float) Math.toRadians(62.0); + private static final boolean WIFI_SIGNAL_FILTERING_ENABLED = true; + private static final long HEADING_SENSOR_FRESH_TIMEOUT_MS = 1500L; + private static final long MIN_VALID_STEP_INTERVAL_MS = 220L; + private static final float MOTION_PREDICTION_MIN_SPEED_MPS = 0.45f; + private static final float MOTION_PREDICTION_MAX_SPEED_MPS = 2.40f; + private static final float MOTION_PREDICTION_MIN_ACCEL_MPS2 = 0.55f; + private static final float MOTION_PREDICTION_MAX_ACCEL_MPS2 = 2.40f; + private static final int STATIONARY_ACCEL_WINDOW_SAMPLES = 40; + private static final long STATIONARY_STEP_TIMEOUT_MS = 3200L; + private static final float STATIONARY_LINEAR_ACCEL_AVG_THRESHOLD = 0.12f; + private static final float STATIONARY_LINEAR_ACCEL_PEAK_THRESHOLD = 0.48f; + private static final float STATIONARY_GNSS_SPEED_THRESHOLD_MPS = 0.28f; + private static final float STATIONARY_WIFI_SUPPRESSION_RADIUS_METERS = 1.8f; + private static final float STATIONARY_WIFI_WEAK_UPDATE_RADIUS_METERS = 4.0f; + private static final float STATIONARY_WIFI_WEAK_UPDATE_STD_METERS = 18f; + private static final float STATIONARY_WIFI_CLAMP_METERS = 0.45f; + private static final long ZERO_VELOCITY_CORRECTION_MIN_INTERVAL_MS = 700L; + private static final long ZERO_VELOCITY_ANCHOR_FRESH_TIMEOUT_MS = 5000L; + private static final float ZERO_VELOCITY_MIN_DRIFT_METERS = 0.18f; + private static final float ZERO_VELOCITY_ENTER_BLEND = 0.62f; + private static final float ZERO_VELOCITY_HOLD_BLEND = 0.28f; + private static final float ZERO_VELOCITY_MAX_SHIFT_METERS = 0.40f; // Over time accelerometer magnitude values since last step private List accelMagnitude; + private CircularFloatBuffer recentLinearAccelerationMagnitudes; + private boolean zeroVelocityConstraintActive = false; // PDR calculation class private PdrProcessing pdrProcessing; // Trajectory displaying class private PathView pathView; + private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + // Latest trajectory coordinates from step updates (x,y) + private volatile float[] lastCords = null; + // WiFi positioning object private WiFiPositioning wiFiPositioning; + private Timer timer; + private final ExecutorService fusionUpdateExecutor = Executors.newSingleThreadExecutor(); + // Record test points (Add Tag) + private final java.util.ArrayList testPoints = + new java.util.ArrayList<>(); + // Record recording start time (ms) + private long startTimestampMs = 0L; + //region Initialisation /** @@ -187,12 +422,18 @@ private SensorFusion() { this.angularVelocity = new float[3]; this.orientation = new float[3]; this.rotation = new float[4]; + this.gameRotation = new float[4]; this.rotation[3] = 1.0f; + this.gameRotation[3] = 1.0f; + this.orientation[0] = Float.NaN; this.R = new float[9]; + this.coordinateUtils = new com.openpositioning.PositionMe.utils.CoordinateUtils(); + this.mapMatchingEngine = new com.openpositioning.PositionMe.utils.MapMatchingEngine(this.coordinateUtils); + this.particleFilter = new com.openpositioning.PositionMe.utils.ParticleFilter(PARTICLE_COUNT); // GNSS initial Long-Lat array this.startLocation = new float[2]; - } + } /** * Static function to access singleton instance of SensorFusion. @@ -203,6 +444,530 @@ public static SensorFusion getInstance() { return sensorFusion; } + // Called by indoor map UI when downloaded floor metadata changes. + public synchronized void setCurrentFloorPlan(com.openpositioning.PositionMe.data.remote.FloorPlan floorPlan) { + this.currentFloorPlan = floorPlan; + } + + // Keeps fusion-side floor resolution consistent with the currently available building maps. + public synchronized void setAvailableFloorPlans(Map floorPlans) { + availableFloorPlans.clear(); + if (floorPlans != null) { + availableFloorPlans.putAll(floorPlans); + } + this.initialFloorHint = normalizeFloorToAvailable(this.initialFloorHint); + this.currentFloor = normalizeFloorToAvailable(this.currentFloor); + this.currentFloorPlan = resolveFloorPlan(currentFloor); + if (this.currentFloorPlan == null) { + this.currentFloorPlan = resolveFloorPlan(initialFloorHint); + } + } + + private synchronized com.openpositioning.PositionMe.data.remote.FloorPlan resolveFloorPlan(int floor) { + com.openpositioning.PositionMe.data.remote.FloorPlan floorPlan = availableFloorPlans.get(floor); + return floorPlan != null ? floorPlan : currentFloorPlan; + } + + private synchronized Map getFloorPlanMapForFilter() { + Map floorPlans = new HashMap<>(); + if (!availableFloorPlans.isEmpty()) { + floorPlans.putAll(availableFloorPlans); + return floorPlans; + } + if (currentFloorPlan != null) { + floorPlans.put(currentFloor, currentFloorPlan); + } + return floorPlans; + } + + private synchronized int normalizeFloorToAvailable(int floorCandidate) { + if (availableFloorPlans.isEmpty()) { + return floorCandidate; + } + if (availableFloorPlans.containsKey(floorCandidate)) { + return floorCandidate; + } + int nearestFloor = floorCandidate; + int nearestDistance = Integer.MAX_VALUE; + for (Integer knownFloor : availableFloorPlans.keySet()) { + if (knownFloor == null) { + continue; + } + int distance = Math.abs(knownFloor - floorCandidate); + if (distance < nearestDistance) { + nearestDistance = distance; + nearestFloor = knownFloor; + } + } + return nearestDistance == Integer.MAX_VALUE + ? availableFloorPlans.keySet().iterator().next() + : nearestFloor; + } + + private synchronized Integer resolvePreferredInitializationFloor(Integer observedFloor) { + if (observedFloor != null) { + return normalizeFloorToAvailable(observedFloor); + } + long nowMs = System.currentTimeMillis(); + if (latestWifiObservedFloor != null + && latestWifiFixTimeMs > 0L + && (nowMs - latestWifiFixTimeMs) <= WIFI_FRESH_TIMEOUT_MS) { + return normalizeFloorToAvailable(latestWifiObservedFloor); + } + if (!availableFloorPlans.isEmpty()) { + if (availableFloorPlans.containsKey(initialFloorHint)) { + return initialFloorHint; + } + if (availableFloorPlans.containsKey(currentFloor)) { + return currentFloor; + } + return normalizeFloorToAvailable(initialFloorHint); + } + return initialFloorHint; + } + + // Mirrors the locally stored test points into the trajectory builder before upload. + private synchronized void syncTestPointsIntoTrajectory() { + if (this.trajectory == null) { + return; + } + this.trajectory.clearTestPoints(); + if (!this.testPoints.isEmpty()) { + this.trajectory.addAllTestPoints(this.testPoints); + } + } + + // Converts step-relative PDR output into the shared global XY frame used by fusion. + private float[] alignRawPdrToGlobal(float[] rawPdrXY) { + if (!isValidXY(rawPdrXY)) { + return rawPdrXY; + } + float offsetX = (pdrAlignmentOffsetXY != null && pdrAlignmentOffsetXY.length >= 2) + ? pdrAlignmentOffsetXY[0] + : 0f; + float offsetY = (pdrAlignmentOffsetXY != null && pdrAlignmentOffsetXY.length >= 2) + ? pdrAlignmentOffsetXY[1] + : 0f; + return new float[]{rawPdrXY[0] + offsetX, rawPdrXY[1] + offsetY}; + } + + private synchronized void alignPdrToFusedAnchor() { + if (!isValidXY(latestRawPdrXY)) { + return; + } + float[] anchor = (lastFusedUpdateTimeMs > 0L && isValidXY(latestFusedXY)) + ? latestFusedXY + : (isValidXY(latestWifiXY) ? latestWifiXY : null); + alignPdrToAnchor(anchor, 1.0f); + } + + private synchronized void alignPdrToAnchor(float[] anchor, float blend) { + alignPdrToAnchor(anchor, blend, Float.POSITIVE_INFINITY); + } + + private synchronized void alignPdrToAnchor(float[] anchor, float blend, float maxAdjustmentMeters) { + if (!isValidXY(latestRawPdrXY) || !isValidXY(anchor)) { + return; + } + float blendClamped = Math.max(0f, Math.min(1f, blend)); + float targetOffsetX = anchor[0] - latestRawPdrXY[0]; + float targetOffsetY = anchor[1] - latestRawPdrXY[1]; + float currentOffsetX = (pdrAlignmentOffsetXY != null && pdrAlignmentOffsetXY.length >= 2) + ? pdrAlignmentOffsetXY[0] + : targetOffsetX; + float currentOffsetY = (pdrAlignmentOffsetXY != null && pdrAlignmentOffsetXY.length >= 2) + ? pdrAlignmentOffsetXY[1] + : targetOffsetY; + float nextOffsetX = currentOffsetX + blendClamped * (targetOffsetX - currentOffsetX); + float nextOffsetY = currentOffsetY + blendClamped * (targetOffsetY - currentOffsetY); + + if (!Float.isNaN(maxAdjustmentMeters) + && !Float.isInfinite(maxAdjustmentMeters) + && maxAdjustmentMeters > 0f) { + float deltaX = nextOffsetX - currentOffsetX; + float deltaY = nextOffsetY - currentOffsetY; + float deltaNorm = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (deltaNorm > maxAdjustmentMeters) { + float scale = maxAdjustmentMeters / deltaNorm; + nextOffsetX = currentOffsetX + deltaX * scale; + nextOffsetY = currentOffsetY + deltaY * scale; + } + } + + pdrAlignmentOffsetXY = new float[]{nextOffsetX, nextOffsetY}; + latestPdrXY = alignRawPdrToGlobal(latestRawPdrXY); + } + + private synchronized void applyWifiPositionCorrectionToPdr(float[] wifiMeasurementXY, + Integer measurementFloor, + long nowMs, + boolean weakWifiUpdate) { + if (!saveRecording + || !isValidXY(wifiMeasurementXY) + || !isValidXY(latestRawPdrXY) + || !isValidXY(latestPdrXY)) { + return; + } + + if (measurementFloor != null && normalizeFloorToAvailable(measurementFloor) != currentFloor) { + return; + } + + float[] constrainedWifiXY = constrainWifiCorrectionToMotion(wifiMeasurementXY, nowMs, weakWifiUpdate); + if (!isValidXY(constrainedWifiXY)) { + return; + } + + float driftMeters = distanceMeters(latestPdrXY, constrainedWifiXY); + float minCorrectionDriftMeters = weakWifiUpdate ? 0.50f : 0.22f; + if (driftMeters < minCorrectionDriftMeters) { + return; + } + + float blend; + float maxShiftMeters; + if (driftMeters >= 8f) { + blend = weakWifiUpdate ? 0.46f : 0.66f; + maxShiftMeters = weakWifiUpdate ? 1.35f : 1.90f; + } else if (driftMeters >= 4f) { + blend = weakWifiUpdate ? 0.38f : 0.58f; + maxShiftMeters = weakWifiUpdate ? 1.05f : 1.45f; + } else if (driftMeters >= 1.5f) { + blend = weakWifiUpdate ? 0.28f : 0.44f; + maxShiftMeters = weakWifiUpdate ? 0.75f : 1.10f; + } else { + blend = weakWifiUpdate ? 0.16f : 0.32f; + maxShiftMeters = weakWifiUpdate ? 0.40f : 0.65f; + } + + alignPdrToAnchor(constrainedWifiXY, blend, maxShiftMeters); + if (lastFusedUpdateTimeMs <= 0L || !isValidXY(latestFusedXY)) { + updateTrajectoryDisplayPoint(latestPdrXY); + } + } + + private synchronized void maybePullPdrTowardsWifi(long nowMs) { + if (!saveRecording + || !isValidXY(latestPdrXY) + || !isValidXY(latestRawPdrXY) + || !isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, PDR_WIFI_PULL_FRESH_TIMEOUT_MS) + || !isValidXY(latestWifiXY)) { + return; + } + + float driftMeters = distanceMeters(latestPdrXY, latestWifiXY); + float driftThresholdMeters = latestWifiUsesCoarsePositioning + ? PDR_WIFI_PULL_COARSE_DRIFT_METERS + : (latestWifiQuality01 >= 0.55f ? 4.2f : 6.0f); + if (driftMeters < driftThresholdMeters) { + return; + } + + boolean weakWifiUpdate = latestWifiUsesCoarsePositioning || latestWifiQuality01 < 0.45f; + float[] correctionTargetXY = constrainWifiCorrectionToMotion(latestWifiXY, nowMs, weakWifiUpdate); + if (!isValidXY(correctionTargetXY)) { + correctionTargetXY = latestWifiXY; + } + + float blend = latestWifiUsesCoarsePositioning + ? 0.14f + : (latestWifiQuality01 >= 0.55f ? 0.32f : 0.23f); + float maxShiftMeters = latestWifiUsesCoarsePositioning + ? 0.45f + : (latestWifiQuality01 >= 0.55f ? 1.05f : 0.78f); + alignPdrToAnchor(correctionTargetXY, blend, maxShiftMeters); + if (lastFusedUpdateTimeMs <= 0L || !isValidXY(latestFusedXY)) { + updateTrajectoryDisplayPoint(latestPdrXY); + } + } + + // Holds the result of one wall-aware step correction pass. + private static final class StepWallGuidanceResult { + final float[] correctedXY; + final float headingRad; + final boolean corrected; + final float correctionMeters; + + StepWallGuidanceResult(float[] correctedXY, float headingRad, boolean corrected, float correctionMeters) { + this.correctedXY = correctedXY; + this.headingRad = headingRad; + this.corrected = corrected; + this.correctionMeters = correctionMeters; + } + } + + // Pushes a predicted step back onto legal walkable space before fusion consumes it. + private synchronized StepWallGuidanceResult applyWallGuidanceToStep(float[] previousPdr, + float[] alignedPdr, + float sensorHeadingRad, + long nowMs) { + if (!isValidXY(previousPdr) || !isValidXY(alignedPdr) || mapMatchingEngine == null) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + com.openpositioning.PositionMe.data.remote.FloorPlan activeFloorPlan = resolveFloorPlan(currentFloor); + if (activeFloorPlan == null) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + float rawStepMeters = distanceMeters(previousPdr, alignedPdr); + if (rawStepMeters < WALL_GUIDANCE_MIN_STEP_METERS) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + float[] baselineCorrectedXY = mapMatchingEngine.correctMovementAgainstWalls( + previousPdr, + alignedPdr, + activeFloorPlan, + WALL_GUIDANCE_MARGIN_METERS + ); + if (!isValidXY(baselineCorrectedXY)) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + boolean wallCollision = !mapMatchingEngine.isMovementValid(previousPdr, alignedPdr, activeFloorPlan); + float baselineCorrectionMeters = distanceMeters(alignedPdr, baselineCorrectedXY); + if (!wallCollision && baselineCorrectionMeters < WALL_GUIDANCE_MIN_CORRECTION_METERS) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + float rawMoveHeadingRad = normalizeAngleRad( + (float) Math.atan2(alignedPdr[0] - previousPdr[0], alignedPdr[1] - previousPdr[1]) + ); + float intendedHeadingRad = resolveMotionIntentHeadingRad(rawMoveHeadingRad, sensorHeadingRad, nowMs); + float[] intentCandidateXY = new float[]{ + previousPdr[0] + rawStepMeters * (float) Math.sin(intendedHeadingRad), + previousPdr[1] + rawStepMeters * (float) Math.cos(intendedHeadingRad) + }; + float[] intentCorrectedXY = mapMatchingEngine.correctMovementAgainstWalls( + previousPdr, + intentCandidateXY, + activeFloorPlan, + WALL_GUIDANCE_MARGIN_METERS + ); + + float[] correctedXY = baselineCorrectedXY; + if (isValidXY(intentCorrectedXY)) { + float baselineScore = scoreWallGuidanceCandidate( + previousPdr, + baselineCorrectedXY, + intendedHeadingRad, + rawStepMeters, + nowMs + ); + float intentScore = scoreWallGuidanceCandidate( + previousPdr, + intentCorrectedXY, + intendedHeadingRad, + rawStepMeters, + nowMs + ); + if (intentScore > baselineScore + 0.02f + || distanceMeters(previousPdr, intentCorrectedXY) + > distanceMeters(previousPdr, baselineCorrectedXY) + WALL_GUIDANCE_COLLISION_INTENT_MIN_GAIN_METERS) { + correctedXY = intentCorrectedXY; + } + } + + float correctionMeters = distanceMeters(alignedPdr, correctedXY); + boolean wallCorrected = wallCollision || correctionMeters >= WALL_GUIDANCE_MIN_CORRECTION_METERS; + if (!wallCorrected) { + return new StepWallGuidanceResult(alignedPdr, sensorHeadingRad, false, 0f); + } + + float correctedHeadingRad = sensorHeadingRad; + float correctedStepMeters = distanceMeters(previousPdr, correctedXY); + if (correctedStepMeters >= WALL_GUIDANCE_MIN_STEP_METERS) { + float pathHeadingRad = normalizeAngleRad( + (float) Math.atan2(correctedXY[0] - previousPdr[0], correctedXY[1] - previousPdr[1]) + ); + correctedHeadingRad = blendAnglesRad(intendedHeadingRad, pathHeadingRad, WALL_GUIDANCE_HEADING_BLEND); + registerWallAlignedHeading(correctedHeadingRad, nowMs); + } + + return new StepWallGuidanceResult(correctedXY, correctedHeadingRad, true, correctionMeters); + } + + private float scoreWallGuidanceCandidate(float[] previousPdr, + float[] candidateXY, + float intendedHeadingRad, + float targetStepMeters, + long nowMs) { + if (!isValidXY(previousPdr) || !isValidXY(candidateXY)) { + return Float.NEGATIVE_INFINITY; + } + float candidateStepMeters = distanceMeters(previousPdr, candidateXY); + if (candidateStepMeters < WALL_GUIDANCE_MIN_STEP_METERS) { + return Float.NEGATIVE_INFINITY; + } + + float candidateHeadingRad = normalizeAngleRad( + (float) Math.atan2(candidateXY[0] - previousPdr[0], candidateXY[1] - previousPdr[1]) + ); + float headingDelta = Math.abs(normalizeAngleRad(candidateHeadingRad - intendedHeadingRad)); + float headingScore = 1f - clamp01(headingDelta / (float) Math.PI); + float progressScore = clamp01(candidateStepMeters / Math.max(targetStepMeters, WALL_GUIDANCE_MIN_STEP_METERS)); + + float wifiScore = 0.55f; + if (isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, PDR_WIFI_PULL_FRESH_TIMEOUT_MS) + && isValidXY(latestWifiXY)) { + float wifiDistance = distanceMeters(candidateXY, latestWifiXY); + wifiScore = 1f - clamp01(wifiDistance / 8f); + } + return 0.58f * headingScore + 0.27f * progressScore + 0.15f * wifiScore; + } + + private void updateWallGuidanceState(boolean corrected, long nowMs) { + if (lastWallGuidanceTimeMs > 0L && (nowMs - lastWallGuidanceTimeMs) > WALL_GUIDANCE_RESET_TIMEOUT_MS) { + consecutiveWallGuidedSteps = 0; + } + if (corrected) { + consecutiveWallGuidedSteps++; + lastWallGuidanceTimeMs = nowMs; + } else { + consecutiveWallGuidedSteps = 0; + lastWallGuidanceTimeMs = 0L; + } + } + + private synchronized float[] maybeRecoverFromWallLock(float[] previousPdr, + float[] originalAlignedPdr, + float[] wallGuidedXY, + long nowMs) { + if (consecutiveWallGuidedSteps < WALL_LOCK_RECOVERY_REQUIRED_STEPS + || !isValidXY(previousPdr) + || !isValidXY(originalAlignedPdr) + || !isValidXY(wallGuidedXY) + || !isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, WIFI_FRESH_TIMEOUT_MS) + || !isValidXY(latestWifiXY) + || isLikelyStationary(nowMs)) { + return wallGuidedXY; + } + + float guidedToWifi = distanceMeters(wallGuidedXY, latestWifiXY); + float originalToWifi = distanceMeters(originalAlignedPdr, latestWifiXY); + if (guidedToWifi >= originalToWifi - WALL_LOCK_RECOVERY_MIN_WIFI_GAIN_METERS) { + return wallGuidedXY; + } + + float[] recoveryCandidate = new float[]{ + wallGuidedXY[0] * (1f - WALL_LOCK_RECOVERY_BLEND) + latestWifiXY[0] * WALL_LOCK_RECOVERY_BLEND, + wallGuidedXY[1] * (1f - WALL_LOCK_RECOVERY_BLEND) + latestWifiXY[1] * WALL_LOCK_RECOVERY_BLEND + }; + float recoveryShift = distanceMeters(wallGuidedXY, recoveryCandidate); + if (recoveryShift > WALL_LOCK_RECOVERY_MAX_SHIFT_METERS && recoveryShift > 1e-5f) { + float scale = WALL_LOCK_RECOVERY_MAX_SHIFT_METERS / recoveryShift; + recoveryCandidate[0] = wallGuidedXY[0] + (recoveryCandidate[0] - wallGuidedXY[0]) * scale; + recoveryCandidate[1] = wallGuidedXY[1] + (recoveryCandidate[1] - wallGuidedXY[1]) * scale; + } + + com.openpositioning.PositionMe.data.remote.FloorPlan activeFloorPlan = resolveFloorPlan(currentFloor); + if (mapMatchingEngine != null && activeFloorPlan != null) { + recoveryCandidate = mapMatchingEngine.correctMovementAgainstWalls( + previousPdr, + recoveryCandidate, + activeFloorPlan, + WALL_GUIDANCE_MARGIN_METERS + ); + } + if (!isValidXY(recoveryCandidate)) { + return wallGuidedXY; + } + return distanceMeters(recoveryCandidate, latestWifiXY) + 0.15f < guidedToWifi + ? recoveryCandidate + : wallGuidedXY; + } + + private synchronized float[] constrainToCurrentFloorWalls(float[] candidateXY, float[] preferredReferenceXY) { + if (!isValidXY(candidateXY) || mapMatchingEngine == null) { + return candidateXY; + } + + com.openpositioning.PositionMe.data.remote.FloorPlan activeFloorPlan = resolveFloorPlan(currentFloor); + if (activeFloorPlan == null) { + return candidateXY; + } + + float[] referenceXY = isValidXY(preferredReferenceXY) ? preferredReferenceXY : null; + if (!isValidXY(referenceXY) && isValidXY(lastCords)) { + referenceXY = new float[]{lastCords[0], lastCords[1]}; + } + + if (isValidXY(referenceXY) && distanceMeters(referenceXY, candidateXY) >= 1e-4f) { + float[] correctedXY = mapMatchingEngine.correctMovementAgainstWalls( + referenceXY, + candidateXY, + activeFloorPlan, + WALL_GUIDANCE_MARGIN_METERS + ); + if (isValidXY(correctedXY)) { + return correctedXY; + } + } + return candidateXY; + } + + private synchronized void applyRelativeStepPrediction(float[] alignedPdr, + float[] previousPdr, + float headingRad, + List accelWindow, + long stepIntervalMs, + long nowMs) { + ensureParticleFilterInitialized(); + if (particleFilter != null + && particleFilter.isInitialized() + && isValidXY(previousPdr) + && isValidXY(alignedPdr)) { + float stepLen = distanceMeters(alignedPdr, previousPdr); + if (stepLen > 0.01f) { + MotionPredictionProfile predictionProfile = buildMotionPredictionProfile( + Math.min(stepLen, MAX_TRAJECTORY_STEP_METERS), + headingRad, + accelWindow, + stepIntervalMs, + nowMs + ); + particleFilter.predict( + predictionProfile.stepMeters, + predictionProfile.headingRad, + getFloorPlanMapForFilter(), + mapMatchingEngine, + predictionProfile.confidence01 + ); + } + } + syncFusedEstimateFromParticleFilter(nowMs); + } + + // Stores the floor-constrained trajectory point used by live map display and persistence. + private synchronized void updateTrajectoryDisplayPoint(float[] trajectoryXY) { + if (!isValidXY(trajectoryXY)) { + return; + } + float[] previousDisplayXY = isValidXY(lastCords) ? new float[]{lastCords[0], lastCords[1]} : null; + float[] displayXY = constrainToCurrentFloorWalls(trajectoryXY, previousDisplayXY); + lastCords = isValidXY(displayXY) + ? new float[]{displayXY[0], displayXY[1]} + : new float[]{trajectoryXY[0], trajectoryXY[1]}; + lastDisplayTrajectoryUpdateMs = System.currentTimeMillis(); + } + + private synchronized float[] currentTrajectoryXYForPersistence() { + if (isValidXY(lastCords)) { + return new float[]{lastCords[0], lastCords[1]}; + } + if (lastFusedUpdateTimeMs > 0L && isValidXY(latestFusedXY)) { + return new float[]{latestFusedXY[0], latestFusedXY[1]}; + } + if (isValidXY(latestPdrXY)) { + return new float[]{latestPdrXY[0], latestPdrXY[1]}; + } + if (isValidXY(latestRawPdrXY)) { + return new float[]{latestRawPdrXY[0], latestRawPdrXY[1]}; + } + return new float[]{0f, 0f}; + } + /** * Initialisation function for the SensorFusion instance. * @@ -219,138 +984,852 @@ public static SensorFusion getInstance() { */ public void setContext(Context context) { this.appContext = context.getApplicationContext(); // store app context for later use + ensureSensorCallbackThread(); // Initialise data collection devices (unchanged)... - this.accelerometerSensor = new MovementSensor(context, Sensor.TYPE_ACCELEROMETER); - this.barometerSensor = new MovementSensor(context, Sensor.TYPE_PRESSURE); - this.gyroscopeSensor = new MovementSensor(context, Sensor.TYPE_GYROSCOPE); - this.lightSensor = new MovementSensor(context, Sensor.TYPE_LIGHT); - 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); - this.rotationSensor = new MovementSensor(context, Sensor.TYPE_ROTATION_VECTOR); - this.gravitySensor = new MovementSensor(context, Sensor.TYPE_GRAVITY); - this.linearAccelerationSensor = new MovementSensor(context, Sensor.TYPE_LINEAR_ACCELERATION); - // Listener based devices - this.wifiProcessor = new WifiDataProcessor(context); + this.accelerometerSensor = new MovementSensor(appContext, Sensor.TYPE_ACCELEROMETER); + this.barometerSensor = new MovementSensor(appContext, Sensor.TYPE_PRESSURE); + this.gyroscopeSensor = new MovementSensor(appContext, Sensor.TYPE_GYROSCOPE); + this.lightSensor = new MovementSensor(appContext, Sensor.TYPE_LIGHT); + this.proximitySensor = new MovementSensor(appContext, Sensor.TYPE_PROXIMITY); + this.magnetometerSensor = new MovementSensor(appContext, Sensor.TYPE_MAGNETIC_FIELD); + this.stepDetectionSensor = new MovementSensor(appContext, Sensor.TYPE_STEP_DETECTOR); + this.rotationSensor = new MovementSensor(appContext, Sensor.TYPE_ROTATION_VECTOR); + this.gameRotationSensor = new MovementSensor(appContext, Sensor.TYPE_GAME_ROTATION_VECTOR); + this.gravitySensor = new MovementSensor(appContext, Sensor.TYPE_GRAVITY); + this.linearAccelerationSensor = new MovementSensor(appContext, Sensor.TYPE_LINEAR_ACCELERATION); + + this.wifiProcessor = new WifiDataProcessor(appContext); wifiProcessor.registerObserver(this); - this.gnssProcessor = new GNSSDataProcessor(context, locationListener); - // Create object handling HTTPS communication - this.serverCommunications = new ServerCommunications(context); - // Save absolute and relative start time - this.absoluteStartTime = System.currentTimeMillis(); - this.bootTime = SystemClock.uptimeMillis(); + this.gnssProcessor = new GNSSDataProcessor(appContext, locationListener); + this.serverCommunications = new ServerCommunications(appContext); + + // Do not reset recording time bases here; they are set in startRecording(). + // Resetting during recording can invalidate relative timestamps. + if (!saveRecording) { + this.absoluteStartTime = System.currentTimeMillis(); + this.bootTime = SystemClock.uptimeMillis(); + } + // Initialise saveRecording to false this.saveRecording = false; // Other initialisations... this.accelMagnitude = new ArrayList<>(); + this.recentLinearAccelerationMagnitudes = new CircularFloatBuffer(STATIONARY_ACCEL_WINDOW_SAMPLES); this.pdrProcessing = new PdrProcessing(context); this.settings = PreferenceManager.getDefaultSharedPreferences(context); this.pathView = new PathView(context, null); this.wiFiPositioning = new WiFiPositioning(context); - if(settings.getBoolean("overwrite_constants", false)) { - this.filter_coefficient = Float.parseFloat(settings.getString("accel_filter", "0.96")); - } else { - this.filter_coefficient = FILTER_COEFFICIENT; - } + this.filter_coefficient = resolveConfiguredFilterCoefficient(); // Keep app awake during the recording (using stored appContext) PowerManager powerManager = (PowerManager) this.appContext.getSystemService(Context.POWER_SERVICE); - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag"); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PositionMe::SensorWakeLock"); + } } - //endregion + private synchronized void ensureSensorCallbackThread() { + if (motionSensorThread == null || !motionSensorThread.isAlive() || motionSensorHandler == null) { + motionSensorThread = new HandlerThread( + "PositionMeMotionSensors", + android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE + ); + motionSensorThread.start(); + motionSensorHandler = new Handler(motionSensorThread.getLooper()); + } + if (sensorCallbackThread == null || !sensorCallbackThread.isAlive() || sensorCallbackHandler == null) { + sensorCallbackThread = new HandlerThread( + "PositionMeAuxSensors", + android.os.Process.THREAD_PRIORITY_DEFAULT + ); + sensorCallbackThread.start(); + sensorCallbackHandler = new Handler(sensorCallbackThread.getLooper()); + } + } - //region Sensor processing - /** - * {@inheritDoc} - * - * Called every time a Sensor value is updated. - * - * Checks originating sensor type, if the data is meaningful save it to a local variable. - * - * @param sensorEvent SensorEvent of sensor with values changed, includes types and values. - */ - @Override - public void onSensorChanged(SensorEvent sensorEvent) { - long currentTime = System.currentTimeMillis(); // Current time in milliseconds - int sensorType = sensorEvent.sensor.getType(); + private float resolveConfiguredFilterCoefficient() { + if (settings == null || !settings.getBoolean("overwrite_constants", false)) { + return FILTER_COEFFICIENT; + } + try { + String value = settings.getString("accel_filter", Float.toString(FILTER_COEFFICIENT)); + float parsed = Float.parseFloat(value); + return (parsed >= 0f && parsed <= 1f) ? parsed : FILTER_COEFFICIENT; + } catch (RuntimeException e) { + return FILTER_COEFFICIENT; + } + } - // Get the previous timestamp for this sensor type - Long lastTimestamp = lastEventTimestamps.get(sensorType); + public void setPathView(PathView view) { + this.pathView = view; + } - if (lastTimestamp != null) { - long timeGap = currentTime - lastTimestamp; + private void postPathViewTrajectory(@NonNull float[] trajectoryXY) { + if (pathView == null) { + return; + } + final float[] pointCopy = new float[]{trajectoryXY[0], trajectoryXY[1]}; + mainThreadHandler.post(() -> { + if (pathView == null) { + return; + } + pathView.drawTrajectory(pointCopy); + pathView.postInvalidate(); + }); + } -// // 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"); -// } + private void clearPathViewOnMainThread() { + if (pathView == null) { + return; } + mainThreadHandler.post(() -> { + if (pathView == null) { + return; + } + pathView.clearTrajectory(); + pathView.postInvalidate(); // was postInvalidateOnAnimation() โ€” consistent with trajectory update + }); + } - // Update timestamp and frequency counter for this sensor - lastEventTimestamps.put(sensorType, currentTime); - eventCounts.put(sensorType, eventCounts.getOrDefault(sensorType, 0) + 1); + private void updateHeadingFromRotationVector(@NonNull float[] rotationVectorValues) { + long nowMs = System.currentTimeMillis(); + this.rotation = rotationVectorValues.clone(); + float[] rotationVectorDCM = new float[9]; + SensorManager.getRotationMatrixFromVector(rotationVectorDCM, this.rotation); + float[] rotationOrientation = new float[3]; + // Use the world-frame rotation matrix directly so heading is not skewed by UI rotation. + SensorManager.getOrientation(rotationVectorDCM, rotationOrientation); + float trueNorthHeading = normalizeAngleRad(rotationOrientation[0] + resolveGeomagneticDeclinationRad()); + if (!headingInitialized || Float.isNaN(orientation[0]) || Float.isInfinite(orientation[0])) { + orientation[0] = trueNorthHeading; + headingInitialized = true; + } else { + orientation[0] = blendAnglesRad(orientation[0], trueNorthHeading, HEADING_SMOOTH_ALPHA); + } + orientation[1] = rotationOrientation[1]; + orientation[2] = rotationOrientation[2]; + lastHeadingSensorUpdateMs = nowMs; + refreshMagneticDisturbanceState(nowMs); + if (!magneticDisturbanceActive) { + maybeUpdateGameHeadingAlignment(nowMs); + } + } + private void updateHeadingFromGameRotationVector(@NonNull float[] rotationVectorValues) { + long nowMs = System.currentTimeMillis(); + this.gameRotation = rotationVectorValues.clone(); + float[] rotationVectorDCM = new float[9]; + SensorManager.getRotationMatrixFromVector(rotationVectorDCM, this.gameRotation); + float[] gameOrientation = new float[3]; + SensorManager.getOrientation(rotationVectorDCM, gameOrientation); - switch (sensorType) { - case Sensor.TYPE_ACCELEROMETER: - acceleration[0] = sensorEvent.values[0]; - acceleration[1] = sensorEvent.values[1]; - acceleration[2] = sensorEvent.values[2]; - break; + float relativeHeading = normalizeAngleRad(gameOrientation[0]); + if (Float.isNaN(filteredGameHeadingRad) || Float.isInfinite(filteredGameHeadingRad)) { + filteredGameHeadingRad = relativeHeading; + } else { + filteredGameHeadingRad = blendAnglesRad( + filteredGameHeadingRad, + relativeHeading, + GAME_HEADING_SMOOTH_ALPHA + ); + } + lastGameHeadingSensorUpdateMs = nowMs; + refreshMagneticDisturbanceState(nowMs); + if (!magneticDisturbanceActive) { + maybeUpdateGameHeadingAlignment(nowMs); + } + } - 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) - ); - } - break; + private float[] resolveMotionRotationMatrix() { + if (rotation != null && rotation.length >= 3) { + try { + float[] rotationVectorDCM = new float[9]; + SensorManager.getRotationMatrixFromVector(rotationVectorDCM, rotation); + return rotationVectorDCM; + } catch (RuntimeException ignored) { + // Fall through to orientation-based reconstruction. + } + } + if (!headingInitialized + || orientation == null + || orientation.length < 3 + || Float.isNaN(orientation[0]) + || Float.isInfinite(orientation[0])) { + return null; + } + try { + return getRotationMatrixFromOrientation(new float[]{ + orientation[0], + orientation[1], + orientation[2] + }); + } catch (RuntimeException ignored) { + return null; + } + } - case Sensor.TYPE_GYROSCOPE: - angularVelocity[0] = sensorEvent.values[0]; - angularVelocity[1] = sensorEvent.values[1]; - angularVelocity[2] = sensorEvent.values[2]; + private void updateHorizontalAccelerationHeading(@NonNull float[] linearAccelerationValues, long nowMs) { + float[] motionRotationMatrix = resolveMotionRotationMatrix(); + if (motionRotationMatrix == null || motionRotationMatrix.length < 9) { + return; + } - case Sensor.TYPE_LINEAR_ACCELERATION: - filteredAcc[0] = sensorEvent.values[0]; - filteredAcc[1] = sensorEvent.values[1]; - filteredAcc[2] = sensorEvent.values[2]; + float worldX = motionRotationMatrix[0] * linearAccelerationValues[0] + + motionRotationMatrix[1] * linearAccelerationValues[1] + + motionRotationMatrix[2] * linearAccelerationValues[2]; + float worldY = motionRotationMatrix[3] * linearAccelerationValues[0] + + motionRotationMatrix[4] * linearAccelerationValues[1] + + motionRotationMatrix[5] * linearAccelerationValues[2]; + float horizontalMagnitude = (float) Math.sqrt(worldX * worldX + worldY * worldY); + latestHorizontalAccelerationMagnitude = horizontalMagnitude; + if (horizontalMagnitude < HORIZONTAL_ACCEL_HEADING_MIN_MAGNITUDE_MPS2) { + return; + } - // Compute magnitude & add to accelMagnitude - double accelMagFiltered = Math.sqrt( - Math.pow(filteredAcc[0], 2) + - Math.pow(filteredAcc[1], 2) + - Math.pow(filteredAcc[2], 2) - ); - this.accelMagnitude.add(accelMagFiltered); + float accelerationHeadingRad = normalizeAngleRad((float) Math.atan2(worldX, worldY)); + if (Float.isNaN(latestHorizontalAccelerationHeadingRad) + || Float.isInfinite(latestHorizontalAccelerationHeadingRad)) { + latestHorizontalAccelerationHeadingRad = accelerationHeadingRad; + } else { + latestHorizontalAccelerationHeadingRad = blendAnglesRad( + latestHorizontalAccelerationHeadingRad, + accelerationHeadingRad, + HORIZONTAL_ACCEL_HEADING_BLEND + ); + } + lastHorizontalAccelerationHeadingTimeMs = nowMs; + } -// // Debug logging -// Log.v("SensorFusion", -// "Added new linear accel magnitude: " + accelMagFiltered -// + "; accelMagnitude size = " + accelMagnitude.size()); + private GeomagneticField buildGeomagneticField() { + float latitudeForDeclination = Float.NaN; + float longitudeForDeclination = Float.NaN; + float altitudeForDeclination = 0f; - elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); - break; + if (latestGnssLatLng != null) { + latitudeForDeclination = (float) latestGnssLatLng.latitude; + longitudeForDeclination = (float) latestGnssLatLng.longitude; + altitudeForDeclination = Float.isNaN(elevation) ? 0f : elevation; + } else if (startLocation != null + && startLocation.length >= 2 + && (startLocation[0] != 0f || startLocation[1] != 0f)) { + latitudeForDeclination = startLocation[0]; + longitudeForDeclination = startLocation[1]; + } - case Sensor.TYPE_GRAVITY: - gravity[0] = sensorEvent.values[0]; - gravity[1] = sensorEvent.values[1]; - gravity[2] = sensorEvent.values[2]; + if (Float.isNaN(latitudeForDeclination) || Float.isNaN(longitudeForDeclination)) { + return null; + } - // Possibly log gravity values if needed - //Log.v("SensorFusion", "Gravity: " + Arrays.toString(gravity)); + try { + return new GeomagneticField( + latitudeForDeclination, + longitudeForDeclination, + altitudeForDeclination, + System.currentTimeMillis() + ); + } catch (RuntimeException e) { + return null; + } + } - elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); - break; + private float resolveGeomagneticDeclinationRad() { + GeomagneticField geomagneticField = buildGeomagneticField(); + if (geomagneticField == null) { + return 0f; + } + return (float) Math.toRadians(geomagneticField.getDeclination()); + } - case Sensor.TYPE_LIGHT: + private float resolveExpectedGeomagneticFieldStrengthUt() { + GeomagneticField geomagneticField = buildGeomagneticField(); + if (geomagneticField == null) { + return Float.NaN; + } + return geomagneticField.getFieldStrength() / 1000f; + } + + private float blendAnglesRad(float currentAngleRad, float targetAngleRad, float alpha) { + if (Float.isNaN(currentAngleRad) || Float.isInfinite(currentAngleRad)) { + return normalizeAngleRad(targetAngleRad); + } + float safeAlpha = Math.max(0f, Math.min(1f, alpha)); + float delta = normalizeAngleRad(targetAngleRad - currentAngleRad); + return normalizeAngleRad(currentAngleRad + safeAlpha * delta); + } + + private void updateMotionHeadingFromTrajectory(float[] previousXY, float[] currentXY, long nowMs) { + if (!isValidXY(previousXY) || !isValidXY(currentXY)) { + return; + } + if (lastFloorSwitchTimeMs > 0L && (nowMs - lastFloorSwitchTimeMs) <= FLOOR_SWITCH_HEADING_FREEZE_MS) { + return; + } + float distance = distanceMeters(previousXY, currentXY); + if (distance < FUSED_MOTION_HEADING_MIN_DISTANCE_METERS) { + return; + } + if (distance > FUSED_MOTION_HEADING_MAX_DISTANCE_METERS) { + return; + } + if (!hasMotionEvidenceForHeadingUpdate(nowMs)) { + return; + } + latestMotionHeadingRad = normalizeAngleRad( + (float) Math.atan2(currentXY[0] - previousXY[0], currentXY[1] - previousXY[1]) + ); + lastMotionHeadingTimeMs = nowMs; + } + + private static final class MotionPredictionProfile { + final float stepMeters; + final float headingRad; + final float confidence01; + + MotionPredictionProfile(float stepMeters, float headingRad, float confidence01) { + this.stepMeters = stepMeters; + this.headingRad = headingRad; + this.confidence01 = confidence01; + } + } + + private MotionPredictionProfile buildMotionPredictionProfile(float rawStepMeters, + float sensorHeadingRad, + List accelWindow, + long stepIntervalMs, + long nowMs) { + float safeStepMeters = Math.max(0f, rawStepMeters); + float stepSpeedMps = Float.NaN; + if (stepIntervalMs >= 220L && stepIntervalMs <= 1800L && safeStepMeters > 0.05f) { + stepSpeedMps = safeStepMeters / (stepIntervalMs / 1000f); + } + + float speedReferenceMps = stepSpeedMps; + if (Float.isNaN(speedReferenceMps) || Float.isInfinite(speedReferenceMps) || speedReferenceMps <= 0f) { + speedReferenceMps = safeStepMeters > 0.01f ? safeStepMeters / 0.65f : 0.8f; + } + speedReferenceMps = Math.max(0f, Math.min(3.2f, speedReferenceMps)); + + float peakAccelMps2 = computePeakAccelerationMagnitude(accelWindow); + float speedConfidence = clamp01( + (speedReferenceMps - MOTION_PREDICTION_MIN_SPEED_MPS) + / (MOTION_PREDICTION_MAX_SPEED_MPS - MOTION_PREDICTION_MIN_SPEED_MPS) + ); + float accelConfidence = clamp01( + (peakAccelMps2 - MOTION_PREDICTION_MIN_ACCEL_MPS2) + / (MOTION_PREDICTION_MAX_ACCEL_MPS2 - MOTION_PREDICTION_MIN_ACCEL_MPS2) + ); + float predictionConfidence = clamp01(0.20f + 0.45f * speedConfidence + 0.35f * accelConfidence); + + float predictedHeadingRad = sensorHeadingRad; + + float speedStepScale = 0.94f + 0.12f * speedConfidence; + float accelStepScale = 0.96f + 0.08f * accelConfidence; + float predictedStepMeters = safeStepMeters * (0.55f * speedStepScale + 0.45f * accelStepScale); + if (stepIntervalMs > 0L && stepIntervalMs < 350L) { + predictedStepMeters *= 1.05f; + } else if (stepIntervalMs > 1300L) { + predictedStepMeters *= 0.96f; + } + predictedStepMeters = Math.max(0.05f, Math.min(MAX_TRAJECTORY_STEP_METERS, predictedStepMeters)); + + return new MotionPredictionProfile( + predictedStepMeters, + predictedHeadingRad, + predictionConfidence + ); + } + + private boolean isGnssSpeedFreshForPrediction(long nowMs) { + return latestGnssFixTimeMs > 0L + && (nowMs - latestGnssFixTimeMs) <= GNSS_FRESH_TIMEOUT_MS + && !Float.isNaN(latestGnssSpeedMps) + && !Float.isInfinite(latestGnssSpeedMps) + && latestGnssSpeedMps > 0f + && isGnssEligibleForFusion(latestGnssAccuracyMeters); + } + + private boolean hasMotionEvidenceForHeadingUpdate(long nowMs) { + boolean recentStep = lastStepTime > 0L + && (nowMs - lastStepTime) <= DISPLAY_HEADING_ACTIVE_STEP_TIMEOUT_MS; + return recentStep; + } + + private void invalidateHeadingState() { + latestMotionHeadingRad = Float.NaN; + lastMotionHeadingTimeMs = 0L; + latestHorizontalAccelerationHeadingRad = Float.NaN; + latestHorizontalAccelerationMagnitude = 0f; + lastHorizontalAccelerationHeadingTimeMs = 0L; + latestWallAlignedHeadingRad = Float.NaN; + lastWallAlignedHeadingTimeMs = 0L; + lastHeadingSensorUpdateMs = 0L; + lastGameHeadingSensorUpdateMs = 0L; + headingInitialized = false; + filteredGameHeadingRad = Float.NaN; + gameHeadingAlignmentOffsetRad = Float.NaN; + latestMagneticFieldStrengthUt = Float.NaN; + lastMagneticFieldUpdateMs = 0L; + lastMagneticDisturbanceTimeMs = 0L; + magneticDisturbanceActive = false; + if (orientation != null && orientation.length >= 3) { + orientation[0] = Float.NaN; + orientation[1] = 0f; + orientation[2] = 0f; + } + } + + private void updateMagneticFieldStrength(@NonNull float[] magneticFieldValues, long nowMs) { + latestMagneticFieldStrengthUt = computeVectorMagnitude(magneticFieldValues); + lastMagneticFieldUpdateMs = nowMs; + refreshMagneticDisturbanceState(nowMs); + } + + private float computeVectorMagnitude(@NonNull float[] vector) { + if (vector.length < 3) { + return Float.NaN; + } + return (float) Math.sqrt( + vector[0] * vector[0] + + vector[1] * vector[1] + + vector[2] * vector[2] + ); + } + + private boolean isAbsoluteHeadingFresh(long nowMs) { + return headingInitialized + && lastHeadingSensorUpdateMs > 0L + && (nowMs - lastHeadingSensorUpdateMs) <= HEADING_SENSOR_FRESH_TIMEOUT_MS; + } + + private boolean isGameHeadingFresh(long nowMs) { + return !Float.isNaN(filteredGameHeadingRad) + && !Float.isInfinite(filteredGameHeadingRad) + && lastGameHeadingSensorUpdateMs > 0L + && (nowMs - lastGameHeadingSensorUpdateMs) <= GAME_HEADING_FRESH_TIMEOUT_MS; + } + + private float getGameHeadingRadOrNaN() { + if (Float.isNaN(filteredGameHeadingRad) || Float.isInfinite(filteredGameHeadingRad)) { + return Float.NaN; + } + return normalizeAngleRad(filteredGameHeadingRad); + } + + private float getAlignedGameHeadingRadOrNaN() { + float gameHeading = getGameHeadingRadOrNaN(); + if (Float.isNaN(gameHeading) || Float.isInfinite(gameHeading)) { + return Float.NaN; + } + if (Float.isNaN(gameHeadingAlignmentOffsetRad) || Float.isInfinite(gameHeadingAlignmentOffsetRad)) { + return Float.NaN; + } + return normalizeAngleRad(gameHeading + gameHeadingAlignmentOffsetRad); + } + + private void maybeUpdateGameHeadingAlignment(long nowMs) { + if (!isAbsoluteHeadingFresh(nowMs) || !isGameHeadingFresh(nowMs)) { + return; + } + if (isMagneticFieldStrengthDisturbed(nowMs)) { + return; + } + float absoluteHeading = getSensorHeadingRadOrNaN(); + float gameHeading = getGameHeadingRadOrNaN(); + if (Float.isNaN(absoluteHeading) || Float.isInfinite(absoluteHeading) + || Float.isNaN(gameHeading) || Float.isInfinite(gameHeading)) { + return; + } + float targetOffset = normalizeAngleRad(absoluteHeading - gameHeading); + if (Float.isNaN(gameHeadingAlignmentOffsetRad) || Float.isInfinite(gameHeadingAlignmentOffsetRad)) { + gameHeadingAlignmentOffsetRad = targetOffset; + } else { + gameHeadingAlignmentOffsetRad = blendAnglesRad( + gameHeadingAlignmentOffsetRad, + targetOffset, + GAME_HEADING_ALIGNMENT_ALPHA + ); + } + } + + private boolean isMagneticFieldStrengthDisturbed(long nowMs) { + if (lastMagneticFieldUpdateMs <= 0L + || (nowMs - lastMagneticFieldUpdateMs) > MAGNETIC_FIELD_FRESH_TIMEOUT_MS + || Float.isNaN(latestMagneticFieldStrengthUt) + || Float.isInfinite(latestMagneticFieldStrengthUt)) { + return false; + } + float expectedFieldStrengthUt = resolveExpectedGeomagneticFieldStrengthUt(); + if (Float.isNaN(expectedFieldStrengthUt) + || Float.isInfinite(expectedFieldStrengthUt) + || expectedFieldStrengthUt < 20f) { + return false; + } + float deltaUt = Math.abs(latestMagneticFieldStrengthUt - expectedFieldStrengthUt); + float ratio = deltaUt / Math.max(expectedFieldStrengthUt, 1f); + return deltaUt >= MAGNETIC_FIELD_STRENGTH_DELTA_UT + && ratio >= MAGNETIC_FIELD_STRENGTH_RATIO_DELTA; + } + + private boolean isHeadingConsistencyDisturbed(long nowMs) { + if (!isAbsoluteHeadingFresh(nowMs) || !isGameHeadingFresh(nowMs)) { + return false; + } + if (!hasActiveMotionForDisplayHeading(nowMs)) { + return false; + } + float absoluteHeading = getSensorHeadingRadOrNaN(); + float alignedGameHeading = getAlignedGameHeadingRadOrNaN(); + if (Float.isNaN(absoluteHeading) || Float.isInfinite(absoluteHeading) + || Float.isNaN(alignedGameHeading) || Float.isInfinite(alignedGameHeading)) { + return false; + } + float disagreementRad = Math.abs(normalizeAngleRad(absoluteHeading - alignedGameHeading)); + return disagreementRad >= MAGNETIC_HEADING_DISAGREEMENT_RAD; + } + + private void refreshMagneticDisturbanceState(long nowMs) { + boolean anomalyDetected = isMagneticFieldStrengthDisturbed(nowMs) + || isHeadingConsistencyDisturbed(nowMs); + boolean previousState = magneticDisturbanceActive; + if (anomalyDetected) { + magneticDisturbanceActive = true; + lastMagneticDisturbanceTimeMs = nowMs; + } else if (magneticDisturbanceActive + && lastMagneticDisturbanceTimeMs > 0L + && (nowMs - lastMagneticDisturbanceTimeMs) <= MAGNETIC_DISTURBANCE_HOLD_MS) { + magneticDisturbanceActive = true; + } else { + magneticDisturbanceActive = false; + } + if (previousState != magneticDisturbanceActive) { + Log.i( + "SensorFusion", + magneticDisturbanceActive + ? "Magnetic disturbance detected, switching to game-rotation heading." + : "Magnetic disturbance cleared, restoring absolute heading." + ); + } + } + + private float getPreferredSensorHeadingRad(long nowMs) { + float absoluteHeading = getSensorHeadingRadOrNaN(); + float alignedGameHeading = getAlignedGameHeadingRadOrNaN(); + boolean absoluteFresh = isAbsoluteHeadingFresh(nowMs); + boolean gameFresh = isGameHeadingFresh(nowMs) + && !Float.isNaN(alignedGameHeading) + && !Float.isInfinite(alignedGameHeading); + + if (magneticDisturbanceActive) { + return gameFresh ? alignedGameHeading : Float.NaN; + } + if (absoluteFresh) { + return absoluteHeading; + } + if (gameFresh) { + return alignedGameHeading; + } + if (!Float.isNaN(absoluteHeading) && !Float.isInfinite(absoluteHeading)) { + return absoluteHeading; + } + if (!Float.isNaN(alignedGameHeading) && !Float.isInfinite(alignedGameHeading)) { + return alignedGameHeading; + } + return Float.NaN; + } + + private float resolveFreshHorizontalAccelerationHeadingRad(long nowMs) { + if (Float.isNaN(latestHorizontalAccelerationHeadingRad) + || Float.isInfinite(latestHorizontalAccelerationHeadingRad) + || lastHorizontalAccelerationHeadingTimeMs <= 0L + || (nowMs - lastHorizontalAccelerationHeadingTimeMs) > HORIZONTAL_ACCEL_HEADING_FRESH_TIMEOUT_MS + || latestHorizontalAccelerationMagnitude < HORIZONTAL_ACCEL_HEADING_MIN_MAGNITUDE_MPS2) { + return Float.NaN; + } + return latestHorizontalAccelerationHeadingRad; + } + + private float resolveFreshWallAlignedHeadingRad(long nowMs) { + if (Float.isNaN(latestWallAlignedHeadingRad) + || Float.isInfinite(latestWallAlignedHeadingRad) + || lastWallAlignedHeadingTimeMs <= 0L + || (nowMs - lastWallAlignedHeadingTimeMs) > WALL_HEADING_OVERRIDE_TIMEOUT_MS) { + return Float.NaN; + } + return latestWallAlignedHeadingRad; + } + + private void registerWallAlignedHeading(float headingRad, long nowMs) { + if (Float.isNaN(headingRad) || Float.isInfinite(headingRad)) { + return; + } + latestWallAlignedHeadingRad = normalizeAngleRad(headingRad); + lastWallAlignedHeadingTimeMs = nowMs; + latestMotionHeadingRad = latestWallAlignedHeadingRad; + lastMotionHeadingTimeMs = nowMs; + } + + private float resolveMotionIntentHeadingRad(float rawMoveHeadingRad, + float sensorHeadingRad, + long nowMs) { + float intendedHeadingRad = rawMoveHeadingRad; + if (Float.isNaN(intendedHeadingRad) || Float.isInfinite(intendedHeadingRad)) { + intendedHeadingRad = sensorHeadingRad; + } else if (!Float.isNaN(sensorHeadingRad) && !Float.isInfinite(sensorHeadingRad)) { + intendedHeadingRad = blendAnglesRad( + intendedHeadingRad, + sensorHeadingRad, + WALL_GUIDANCE_SENSOR_INTENT_BLEND + ); + } + + float motionHeadingRad = (!Float.isNaN(latestMotionHeadingRad) + && !Float.isInfinite(latestMotionHeadingRad) + && lastMotionHeadingTimeMs > 0L + && (nowMs - lastMotionHeadingTimeMs) <= MOTION_HEADING_FRESH_TIMEOUT_MS) + ? latestMotionHeadingRad + : Float.NaN; + if (!Float.isNaN(motionHeadingRad) && !Float.isInfinite(motionHeadingRad)) { + intendedHeadingRad = blendAnglesRad( + intendedHeadingRad, + motionHeadingRad, + WALL_GUIDANCE_MOTION_INTENT_BLEND + ); + } + + float accelerationHeadingRad = resolveFreshHorizontalAccelerationHeadingRad(nowMs); + if (!Float.isNaN(accelerationHeadingRad) && !Float.isInfinite(accelerationHeadingRad)) { + float headingDelta = Math.abs(normalizeAngleRad(accelerationHeadingRad - intendedHeadingRad)); + float accelBlend = headingDelta <= Math.toRadians(85.0) + ? WALL_GUIDANCE_ACCEL_INTENT_BLEND + : WALL_GUIDANCE_ACCEL_INTENT_BLEND * 0.45f; + intendedHeadingRad = blendAnglesRad( + intendedHeadingRad, + accelerationHeadingRad, + accelBlend + ); + } + + float wallHeadingRad = resolveFreshWallAlignedHeadingRad(nowMs); + if (!Float.isNaN(wallHeadingRad) && !Float.isInfinite(wallHeadingRad)) { + intendedHeadingRad = blendAnglesRad(intendedHeadingRad, wallHeadingRad, 0.22f); + } + return normalizeAngleRad(intendedHeadingRad); + } + + private float computePeakAccelerationMagnitude(List accelWindow) { + if (accelWindow == null || accelWindow.isEmpty()) { + return 0f; + } + double peak = 0.0; + for (Double sample : accelWindow) { + if (sample == null || Double.isNaN(sample) || Double.isInfinite(sample)) { + continue; + } + peak = Math.max(peak, Math.abs(sample)); + } + return (float) peak; + } + + private float clamp01(float value) { + if (value <= 0f) { + return 0f; + } + if (value >= 1f) { + return 1f; + } + return value; + } + + private boolean hasQuietLinearAccelerationWindow() { + if (recentLinearAccelerationMagnitudes == null) { + return false; + } + CircularFloatBuffer.SnapshotStats accelStats = recentLinearAccelerationMagnitudes.getSnapshotStats(); + if (accelStats.count < Math.max(10, STATIONARY_ACCEL_WINDOW_SAMPLES / 2)) { + return false; + } + return accelStats.averageAbs <= STATIONARY_LINEAR_ACCEL_AVG_THRESHOLD + && accelStats.peakAbs <= STATIONARY_LINEAR_ACCEL_PEAK_THRESHOLD; + } + + private boolean isLikelyStationary(long nowMs) { + boolean noRecentStep = lastStepTime <= 0L || (nowMs - lastStepTime) >= STATIONARY_STEP_TIMEOUT_MS; + boolean accelQuiet = hasQuietLinearAccelerationWindow(); + boolean gnssSlow = !isGnssSpeedFreshForPrediction(nowMs) + || latestGnssSpeedMps <= STATIONARY_GNSS_SPEED_THRESHOLD_MPS; + return noRecentStep && accelQuiet && gnssSlow; + } + + private float[] resolveZeroVelocityAnchor(long nowMs) { + if (lastAbsoluteMeasurementTimeMs > 0L + && (nowMs - lastAbsoluteMeasurementTimeMs) <= ZERO_VELOCITY_ANCHOR_FRESH_TIMEOUT_MS + && lastFusedUpdateTimeMs > 0L + && (nowMs - lastFusedUpdateTimeMs) <= ZERO_VELOCITY_ANCHOR_FRESH_TIMEOUT_MS + && isValidXY(latestFusedXY)) { + return latestFusedXY; + } + if (isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, WIFI_FRESH_TIMEOUT_MS)) { + return latestWifiXY; + } + if (isFreshSource(latestGnssXY, latestGnssFixTimeMs, nowMs, GNSS_FRESH_TIMEOUT_MS) + && isGnssEligibleForFusion(latestGnssAccuracyMeters)) { + return latestGnssXY; + } + return null; + } + + private synchronized void maybeApplyZeroVelocityConstraint(long nowMs) { + if (!saveRecording || !isLikelyStationary(nowMs)) { + zeroVelocityConstraintActive = false; + return; + } + if (lastZeroVelocityCorrectionTimeMs > 0L + && (nowMs - lastZeroVelocityCorrectionTimeMs) < ZERO_VELOCITY_CORRECTION_MIN_INTERVAL_MS) { + return; + } + if (!isValidXY(latestRawPdrXY) || !isValidXY(latestPdrXY)) { + return; + } + + float[] anchor = resolveZeroVelocityAnchor(nowMs); + if (!isValidXY(anchor)) { + return; + } + + float driftMeters = distanceMeters(latestPdrXY, anchor); + if (driftMeters < ZERO_VELOCITY_MIN_DRIFT_METERS) { + zeroVelocityConstraintActive = true; + return; + } + + float blend = zeroVelocityConstraintActive ? ZERO_VELOCITY_HOLD_BLEND : ZERO_VELOCITY_ENTER_BLEND; + alignPdrToAnchor(anchor, blend, ZERO_VELOCITY_MAX_SHIFT_METERS); + zeroVelocityConstraintActive = true; + lastZeroVelocityCorrectionTimeMs = nowMs; + } + + private void resetStationaryDetectionState() { + lastStepTime = 0L; + zeroVelocityConstraintActive = false; + lastZeroVelocityCorrectionTimeMs = 0L; + consecutiveWallGuidedSteps = 0; + lastWallGuidanceTimeMs = 0L; + if (accelMagnitude != null) { + accelMagnitude.clear(); + } + recentLinearAccelerationMagnitudes = new CircularFloatBuffer(STATIONARY_ACCEL_WINDOW_SAMPLES); + } + //endregion + + //region Sensor processing + /** + * {@inheritDoc} + * + * Called every time a Sensor value is updated. + * + * Checks originating sensor type, if the data is meaningful save it to a local variable. + * + * @param sensorEvent SensorEvent of sensor with values changed, includes types and values. + */ + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + + + long currentTime = System.currentTimeMillis(); + int sensorType = sensorEvent.sensor.getType(); + + // Update timestamp and frequency counter for this sensor + lastEventTimestamps.put(sensorType, currentTime); + eventCounts.merge(sensorType, 1, Integer::sum); + + switch (sensorType) { + case Sensor.TYPE_ACCELEROMETER: + + if (accelerometerSensor != null) { + accelerometerSensor.values = sensorEvent.values.clone(); + } + acceleration[0] = sensorEvent.values[0]; + acceleration[1] = sensorEvent.values[1]; + acceleration[2] = sensorEvent.values[2]; + break; + + case Sensor.TYPE_PRESSURE: + if (barometerSensor != null) { + barometerSensor.values = sensorEvent.values.clone(); + } + pressure = (1 - ALPHA) * pressure + ALPHA * sensorEvent.values[0]; + if (saveRecording) { + this.elevation = pdrProcessing.updateElevation( + SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure) + ); + updateConstrainedFloorFromElevation(); + } + break; + + case Sensor.TYPE_GYROSCOPE: + + if (gyroscopeSensor != null) { + gyroscopeSensor.values = sensorEvent.values.clone(); + } + angularVelocity[0] = sensorEvent.values[0]; + angularVelocity[1] = sensorEvent.values[1]; + angularVelocity[2] = sensorEvent.values[2]; + + if (saveRecording && trajectory != null) { + + long relativeTime = System.currentTimeMillis() - absoluteStartTime; + + + Traj.IMUReading imuReading = Traj.IMUReading.newBuilder() + .setRelativeTimestamp(relativeTime) + .setGyr(Traj.Vector3.newBuilder() + .setX(angularVelocity[0]) + .setY(angularVelocity[1]) + .setZ(angularVelocity[2]) + .build()) + .build(); + } + break; + + case Sensor.TYPE_LINEAR_ACCELERATION: + filteredAcc[0] = sensorEvent.values[0]; + filteredAcc[1] = sensorEvent.values[1]; + filteredAcc[2] = sensorEvent.values[2]; + + // Compute magnitude & add to accelMagnitude + double accelMagFiltered = Math.sqrt( + filteredAcc[0] * filteredAcc[0] + + filteredAcc[1] * filteredAcc[1] + + filteredAcc[2] * filteredAcc[2] + ); + this.accelMagnitude.add(accelMagFiltered); + if (recentLinearAccelerationMagnitudes != null) { + recentLinearAccelerationMagnitudes.putNewest((float) accelMagFiltered); + } + updateHorizontalAccelerationHeading(sensorEvent.values, currentTime); + if (saveRecording) { + maybeApplyZeroVelocityConstraint(currentTime); + } + + elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); + break; + + case Sensor.TYPE_GRAVITY: + gravity[0] = sensorEvent.values[0]; + gravity[1] = sensorEvent.values[1]; + gravity[2] = sensorEvent.values[2]; + + elevator = pdrProcessing.estimateElevator(gravity, filteredAcc); + break; + + case Sensor.TYPE_LIGHT: + if (lightSensor != null) { + lightSensor.values = sensorEvent.values.clone(); + } light = sensorEvent.values[0]; break; @@ -359,57 +1838,121 @@ public void onSensorChanged(SensorEvent sensorEvent) { break; case Sensor.TYPE_MAGNETIC_FIELD: + + if (magnetometerSensor != null) { + magnetometerSensor.values = sensorEvent.values.clone(); + } magneticField[0] = sensorEvent.values[0]; magneticField[1] = sensorEvent.values[1]; magneticField[2] = sensorEvent.values[2]; + updateMagneticFieldStrength(sensorEvent.values, currentTime); break; case Sensor.TYPE_ROTATION_VECTOR: - this.rotation = sensorEvent.values.clone(); - float[] rotationVectorDCM = new float[9]; - SensorManager.getRotationMatrixFromVector(rotationVectorDCM, this.rotation); - SensorManager.getOrientation(rotationVectorDCM, this.orientation); + updateHeadingFromRotationVector(sensorEvent.values); + break; + + case Sensor.TYPE_GAME_ROTATION_VECTOR: + updateHeadingFromGameRotationVector(sensorEvent.values); break; case Sensor.TYPE_STEP_DETECTOR: long stepTime = SystemClock.uptimeMillis() - bootTime; + long stepIntervalMs = lastStepTime > 0L ? (currentTime - lastStepTime) : 0L; - if (currentTime - lastStepTime < 20) { - Log.e("SensorFusion", "Ignoring step event, too soon after last step event:" + (currentTime - lastStepTime) + " ms"); + if (lastStepTime > 0L && stepIntervalMs < MIN_VALID_STEP_INTERVAL_MS) { + Log.w("SensorFusion", "Ignoring implausibly rapid step event after " + + stepIntervalMs + " ms"); // Ignore rapid successive step events break; } else { lastStepTime = currentTime; - // Log if accelMagnitude is empty + zeroVelocityConstraintActive = false; if (accelMagnitude.isEmpty()) { - 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()); + Log.w("SensorFusion", "Step detected without buffered linear acceleration samples."); } + List stepAccelWindow = new ArrayList<>(this.accelMagnitude); + float headingForPdr = getCurrentHeadingForParticleFilter(); + if (Float.isNaN(headingForPdr) || Float.isInfinite(headingForPdr)) { + headingForPdr = 0f; + } float[] newCords = this.pdrProcessing.updatePdr( stepTime, - this.accelMagnitude, - this.orientation[0] + stepAccelWindow, + headingForPdr, + stepIntervalMs ); // Clear the accelMagnitude after using it this.accelMagnitude.clear(); + if (isValidXY(newCords)) { + latestRawPdrXY = new float[]{newCords[0], newCords[1]}; + float[] alignedPdr = alignRawPdrToGlobal(newCords); + float[] previousPdr = isValidXY(latestPdrXY) + ? new float[]{latestPdrXY[0], latestPdrXY[1]} + : null; + StepWallGuidanceResult wallGuidance = applyWallGuidanceToStep( + previousPdr, + alignedPdr, + headingForPdr, + currentTime + ); + float predictionHeadingRad = wallGuidance.headingRad; + float[] correctedStepXY = alignedPdr; + if (wallGuidance.corrected && isValidXY(wallGuidance.correctedXY)) { + correctedStepXY = wallGuidance.correctedXY; + updateWallGuidanceState(true, currentTime); + } else { + updateWallGuidanceState(false, currentTime); + } + latestPdrXY = correctedStepXY; + applyRelativeStepPrediction( + correctedStepXY, + previousPdr, + predictionHeadingRad, + stepAccelWindow, + stepIntervalMs, + System.currentTimeMillis() + ); + maybePullPdrTowardsWifi(currentTime); + } + + if (this.pathView != null && isValidXY(lastCords)) { + postPathViewTrajectory(lastCords); + } + + + if (saveRecording && trajectory != null) { + float[] persistedXY = currentTrajectoryXYForPersistence(); + float x = persistedXY[0]; + float y = persistedXY[1]; + + // relative_timestamp is milliseconds from start_timestamp + long pdrTime = SystemClock.uptimeMillis() - bootTime; + if (pdrTime == 0) pdrTime = 1; + + + Traj.RelativePosition pdrPoint = Traj.RelativePosition.newBuilder() + .setRelativeTimestamp(pdrTime) + .setX(x) + .setY(y) + .build(); + + int pdrCount; + synchronized (trajectory) { + trajectory.addPdrData(pdrPoint); + pdrCount = trajectory.getPdrDataCount(); + } + + } if (saveRecording) { - this.pathView.drawTrajectory(newCords); stepCounter++; - trajectory.addPdrData(Traj.Pdr_Sample.newBuilder() - .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) - .setX(newCords[0]) - .setY(newCords[1])); } break; } @@ -435,29 +1978,39 @@ public void logSensorFrequencies() { * Passed to the {@link GNSSDataProcessor} to receive the location data in this class. Save the * values in instance variables. */ - class myLocationListener implements LocationListener{ + class myLocationListener implements LocationListener { @Override public void onLocationChanged(@NonNull Location location) { //Toast.makeText(context, "Location Changed", Toast.LENGTH_SHORT).show(); latitude = (float) location.getLatitude(); longitude = (float) location.getLongitude(); float altitude = (float) location.getAltitude(); - float accuracy = (float) location.getAccuracy(); - float speed = (float) location.getSpeed(); - String provider = location.getProvider(); - if(saveRecording) { - trajectory.addGnssData(Traj.GNSS_Sample.newBuilder() - .setAccuracy(accuracy) - .setAltitude(altitude) - .setLatitude(latitude) - .setLongitude(longitude) - .setSpeed(speed) - .setProvider(provider) - .setRelativeTimestamp(System.currentTimeMillis()-absoluteStartTime)); + latestGnssLatLng = new LatLng(latitude, longitude); + if (coordinateUtils != null && !coordinateUtils.isOriginSet()) { + coordinateUtils.setOrigin(latitude, longitude); + } + if (coordinateUtils != null && coordinateUtils.isOriginSet()) { + latestGnssXY = coordinateUtils.latLonToXY(latitude, longitude); + latestGnssFixTimeMs = System.currentTimeMillis(); + } + latestGnssAccuracyMeters = location.hasAccuracy() ? location.getAccuracy() : Float.NaN; + latestGnssSpeedMps = location.hasSpeed() ? location.getSpeed() : Float.NaN; + if (startLocation[0] == 0 && startLocation[1] == 0) { + startLocation[0] = latitude; + startLocation[1] = longitude; } + long nowMs = System.currentTimeMillis(); + if ((nowMs - lastGnssEnqueueMs) < GNSS_ENQUEUE_MIN_INTERVAL_MS) { + return; + } + lastGnssEnqueueMs = nowMs; + enqueueAbsoluteMeasurementUpdate( + latestGnssXY, + latestGnssAccuracyMeters, + com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.GNSS + ); } } - /** * {@inheritDoc} * @@ -468,76 +2021,1162 @@ public void onLocationChanged(@NonNull Location location) { @Override public void update(Object[] wifiList) { // Save newest wifi values to local variable - this.wifiList = Stream.of(wifiList).map(o -> (Wifi) o).collect(Collectors.toList()); + if (wifiList == null || wifiList.length == 0) { + this.wifiList = new ArrayList<>(); + return; + } + List sanitized = Stream.of(wifiList) + .filter(item -> item instanceof Wifi) + .map(item -> (Wifi) item) + .filter(item -> item != null && item.getBssid() != 0L) + .collect(Collectors.toList()); + if (sanitized.isEmpty()) { + this.wifiList = new ArrayList<>(); + return; + } + sanitized.sort((left, right) -> { + int levelCompare = Integer.compare(right.getLevel(), left.getLevel()); + if (levelCompare != 0) { + return levelCompare; + } + return Long.compare(right.getFrequency(), left.getFrequency()); + }); + this.wifiList = sanitized; + createWifiPositionRequestCallback(false); + } + + private static final class WifiFingerprintRequestData { + final List accessPoints; + final int apCount; + final int strongApCount; + final float averageLevelDbm; + final float quality01; + final WiFiPositioning.PositioningMode positioningMode; + + WifiFingerprintRequestData(List accessPoints, + int apCount, + int strongApCount, + float averageLevelDbm, + float quality01, + WiFiPositioning.PositioningMode positioningMode) { + this.accessPoints = accessPoints; + this.apCount = apCount; + this.strongApCount = strongApCount; + this.averageLevelDbm = averageLevelDbm; + this.quality01 = quality01; + this.positioningMode = positioningMode; + } + } + + private WifiFingerprintRequestData buildWifiFingerprintRequestData() { + if (this.wifiList == null || this.wifiList.isEmpty()) { + return null; + } + + List usableWifi = new ArrayList<>(); + for (Wifi wifi : this.wifiList) { + if (wifi == null || wifi.getBssid() == 0L) { + continue; + } + usableWifi.add(wifi); + } + + if (usableWifi.isEmpty()) { + return null; + } + + usableWifi.sort((left, right) -> { + int levelCompare = Integer.compare(right.getLevel(), left.getLevel()); + if (levelCompare != 0) { + return levelCompare; + } + return Long.compare(right.getFrequency(), left.getFrequency()); + }); + + int strongApCount = countStrongWifiAccessPoints(usableWifi); + float averageLevelDbm = Float.NaN; + if (!usableWifi.isEmpty()) { + float levelSum = 0f; + for (Wifi wifi : usableWifi) { + levelSum += wifi.getLevel(); + } + averageLevelDbm = levelSum / usableWifi.size(); + } + float measurementAccuracy = estimateWifiMeasurementAccuracyMeters(usableWifi, latestWifiObservedFloor); + float quality01 = estimateWifiQuality01(measurementAccuracy); + WiFiPositioning.PositioningMode positioningMode = WiFiPositioning.PositioningMode.FINE; + + return new WifiFingerprintRequestData( + usableWifi, + usableWifi.size(), + strongApCount, + averageLevelDbm, + quality01, + positioningMode + ); + } + + private int countStrongWifiAccessPoints(List accessPoints) { + if (accessPoints == null || accessPoints.isEmpty()) { + return 0; + } + int strongCount = 0; + for (Wifi wifi : accessPoints) { + if (wifi != null && wifi.getLevel() >= -82) { + strongCount++; + } + } + return strongCount; + } + + private float estimateWifiMeasurementAccuracyMeters(List accessPoints, Integer observedFloor) { + if (accessPoints == null || accessPoints.isEmpty()) { + return DEFAULT_WIFI_MEASUREMENT_STD_METERS; + } + + int strongCount = 0; + int totalCount = 0; + float strongestRssi = -120f; + float rssSum = 0f; + for (Wifi wifi : accessPoints) { + if (wifi == null) { + continue; + } + totalCount++; + float rssi = wifi.getLevel(); + rssSum += rssi; + if (rssi > strongestRssi) { + strongestRssi = rssi; + } + if (rssi >= -82f) { + strongCount++; + } + } + if (totalCount == 0) { + return DEFAULT_WIFI_MEASUREMENT_STD_METERS; + } + + float avgRssi = rssSum / totalCount; + float accuracy = DEFAULT_WIFI_MEASUREMENT_STD_METERS; + if (strongCount >= 8) { + accuracy -= 0.9f; + } else if (strongCount <= 3) { + accuracy += 1.0f; + } + if (totalCount <= 4) { + accuracy += 0.9f; + } else if (totalCount >= 10) { + accuracy -= 0.4f; + } + if (strongestRssi <= -75f) { + accuracy += 0.8f; + } else if (strongestRssi >= -60f) { + accuracy -= 0.4f; + } + if (avgRssi <= -78f) { + accuracy += 0.6f; + } else if (avgRssi >= -67f) { + accuracy -= 0.3f; + } + if (observedFloor != null && observedFloor != currentFloor) { + accuracy += 0.8f; + } + return Math.max(1.8f, Math.min(6.5f, accuracy)); + } + + private float estimateWifiQuality01(float measurementAccuracyMeters) { + return clamp01((6.5f - measurementAccuracyMeters) / (6.5f - 1.8f)); + } + + private JSONObject buildWifiFingerprintPayload(List prioritizedWifi) throws JSONException { + if (prioritizedWifi == null || prioritizedWifi.isEmpty()) { + return null; + } + + JSONObject wifiAccessPoints = new JSONObject(); + for (Wifi data : prioritizedWifi) { + wifiAccessPoints.put(String.valueOf(data.getBssid()), data.getLevel()); + } + JSONObject wifiFingerPrint = new JSONObject(); + wifiFingerPrint.put(WIFI_FINGERPRINT, wifiAccessPoints); + return wifiFingerPrint; + } + + private JSONObject buildWifiFingerprintPayload() throws JSONException { + WifiFingerprintRequestData requestData = buildWifiFingerprintRequestData(); + if (requestData == null) { + return null; + } + return buildWifiFingerprintPayload(requestData.accessPoints); + } + + /** + * Function to create a request to obtain a wifi location for the obtained wifi fingerprint + * + */ + private void createWifiPositioningRequest(){ + // Try catch block to catch any errors and prevent app crashing + try { + if (this.wiFiPositioning == null || this.wifiList == null || this.wifiList.isEmpty()) { + return; + } + JSONObject wifiFingerPrint = buildWifiFingerprintPayload(); + if (wifiFingerPrint == null) { + return; + } + this.wiFiPositioning.request(wifiFingerPrint); + } catch (JSONException e) { + // Catching error while making JSON object, to prevent crashes + // Error log to keep record of errors (for secure programming and maintainability) + 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 + * using Volley Callback + */ + private void createWifiPositionRequestCallback(boolean force){ + try { + if (this.wiFiPositioning == null) { + return; + } + if (this.wifiList == null || this.wifiList.isEmpty()) { + return; + } + WifiFingerprintRequestData requestData = buildWifiFingerprintRequestData(); + if (requestData == null || requestData.accessPoints.isEmpty()) { + latestWifiApCount = 0; + latestWifiAverageLevelDbm = Float.NaN; + latestWifiQuality01 = 0f; + latestWifiUsesCoarsePositioning = false; + return; + } + if (!force && countStrongWifiAccessPoints(requestData.accessPoints) < WIFI_MIN_AP_COUNT_FOR_POSITIONING) { + return; + } + long nowMs = System.currentTimeMillis(); + if (!force) { + if (wifiPositionRequestInFlight) { + return; + } + if (lastWifiPositionRequestMs > 0L + && (nowMs - lastWifiPositionRequestMs) < WIFI_POSITION_REQUEST_MIN_INTERVAL_MS) { + return; + } + } + JSONObject wifiFingerPrint = buildWifiFingerprintPayload(requestData.accessPoints); + if (wifiFingerPrint == null) { + return; + } + wifiPositionRequestInFlight = true; + lastWifiPositionRequestMs = nowMs; + final WifiFingerprintRequestData finalRequestData = requestData; + this.wiFiPositioning.request(wifiFingerPrint, new WiFiPositioning.VolleyCallback() { + @Override + public void onSuccess(LatLng wifiLocation, int floor) { + wifiPositionRequestInFlight = false; + if (wifiLocation == null) { + return; + } + if (coordinateUtils != null && !coordinateUtils.isOriginSet()) { + coordinateUtils.setOrigin(wifiLocation.latitude, wifiLocation.longitude); + } + float[] wifiCandidateXY = null; + if (coordinateUtils != null && coordinateUtils.isOriginSet()) { + wifiCandidateXY = coordinateUtils.latLonToXY(wifiLocation.latitude, wifiLocation.longitude); + } + + long nowMs = System.currentTimeMillis(); + boolean wifiOutlier = isWifiUpdateOutlier(wifiCandidateXY, nowMs); + if (wifiOutlier) { + consecutiveRejectedWifiUpdates++; + Log.w("SensorFusion", "Rejected WiFi update as outlier. candidate=" + + wifiLocation + " rejectedCount=" + consecutiveRejectedWifiUpdates); + return; + } + latestWifiLatLng = wifiLocation; + latestWifiObservedFloor = floor; + initialFloorHint = normalizeFloorToAvailable(floor); + latestWifiXY = wifiCandidateXY; + latestWifiFixTimeMs = nowMs; + latestWifiApCount = finalRequestData.apCount; + latestWifiAverageLevelDbm = finalRequestData.averageLevelDbm; + float wifiMeasurementStd = estimateWifiMeasurementAccuracyMeters( + finalRequestData.accessPoints, + floor + ); + latestWifiQuality01 = estimateWifiQuality01(wifiMeasurementStd); + latestWifiUsesCoarsePositioning = false; + lastAcceptedWifiXY = wifiCandidateXY != null ? wifiCandidateXY.clone() : null; + lastAcceptedWifiFixTimeMs = nowMs; + consecutiveRejectedWifiUpdates = 0; + resetRejectedWifiCluster(); + enqueueAbsoluteMeasurementUpdate( + latestWifiXY, + wifiMeasurementStd, + com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI + ); + } + + @Override + public void onError(String message) { + wifiPositionRequestInFlight = false; + Log.w("WiFiPositioning", "WiFi positioning request failed: " + message); + } + }); + } catch (RuntimeException e) { + wifiPositionRequestInFlight = false; + throw e; + } catch (JSONException e) { + wifiPositionRequestInFlight = false; + // Catching error while making JSON object, to prevent crashes + // Error log to keep record of errors (for secure programming and maintainability) + Log.e("jsonErrors","Error creating json object"+e.toString()); + } + + } + + public void requestImmediateWifiPositioning() { + createWifiPositionRequestCallback(true); + } + + private synchronized void ensureParticleFilterInitialized() { + if (particleFilter == null) { + return; + } + + float[] center = null; + float initStd = DEFAULT_PARTICLE_INIT_STD_METERS; + Integer observedFloor = null; + long now = System.currentTimeMillis(); + if (saveRecording && hasUserProvidedStartLocation()) { + center = new float[]{0f, 0f}; + initStd = MANUAL_START_PARTICLE_INIT_STD_METERS; + observedFloor = latestWifiObservedFloor; + } else if (isFreshSource(latestWifiXY, latestWifiFixTimeMs, now, WIFI_FRESH_TIMEOUT_MS)) { + center = new float[]{latestWifiXY[0], latestWifiXY[1]}; + initStd = DEFAULT_WIFI_MEASUREMENT_STD_METERS; + observedFloor = latestWifiObservedFloor; + wifiAnchorEstablished = true; + } else if (saveRecording + && absoluteStartTime > 0L + && (now - absoluteStartTime) < WIFI_INITIALIZATION_GRACE_MS + && !isFreshSource(latestGnssXY, latestGnssFixTimeMs, now, GNSS_FRESH_TIMEOUT_MS)) { + // During recording startup we prefer waiting briefly for WiFi instead of bootstrapping from raw PDR. + return; + } else if (isFreshSource(latestGnssXY, latestGnssFixTimeMs, now, GNSS_FRESH_TIMEOUT_MS) + && isGnssEligibleForFusion(latestGnssAccuracyMeters)) { + center = new float[]{latestGnssXY[0], latestGnssXY[1]}; + initStd = Float.isNaN(latestGnssAccuracyMeters) + ? GNSS_LOW_CONFIDENCE_MAX_STD_METERS + : Math.max(GNSS_MIN_STD_STANDALONE_METERS, latestGnssAccuracyMeters * 0.85f); + } else if (isValidXY(latestPdrXY)) { + center = new float[]{latestPdrXY[0], latestPdrXY[1]}; + } + + if (!isValidXY(center)) { + return; + } + + float headingRad = getCurrentHeadingForParticleFilter(); + if (!particleFilter.isInitialized()) { + Integer preferredFloor = resolvePreferredInitializationFloor(observedFloor); + particleFilter.initialize( + center[0], + center[1], + headingRad, + preferredFloor, + observedFloor, + getFloorPlanMapForFilter(), + initStd + ); + } + syncFusedEstimateFromParticleFilter(now); + } + + private boolean isGnssEligibleForFusion(float accuracyMeters) { + return Float.isNaN(accuracyMeters) + || accuracyMeters <= GNSS_DISCARD_ABSOLUTE_ACCURACY_METERS; + } + + private float sanitizeGnssAccuracy(float accuracyMeters) { + if (Float.isNaN(accuracyMeters) || Float.isInfinite(accuracyMeters) || accuracyMeters <= 0f) { + return Float.POSITIVE_INFINITY; + } + return accuracyMeters; + } + + private float resolveGnssFusionWeight(float accuracyMeters) { + float effectiveAccuracy = sanitizeGnssAccuracy(accuracyMeters); + if (effectiveAccuracy <= GNSS_HIGH_CONFIDENCE_MAX_ACCURACY_METERS) { + return GNSS_FULL_FUSION_WEIGHT; + } + if (effectiveAccuracy <= GNSS_REDUCED_WEIGHT_MAX_ACCURACY_METERS) { + return GNSS_REDUCED_FUSION_WEIGHT; + } + return GNSS_MINIMAL_FUSION_WEIGHT; + } + + private boolean isHighConfidenceGnssWeight(float gnssFusionWeight) { + return gnssFusionWeight >= (GNSS_FULL_FUSION_WEIGHT - 1e-3f); + } + + private float[] blendMeasurementTowardsReference(float[] measurementXY, + float[] referenceXY, + float measurementWeight) { + if (!isValidXY(measurementXY)) { + return measurementXY; + } + float clampedWeight = clamp01(measurementWeight); + if (clampedWeight >= 0.999f || !isValidXY(referenceXY)) { + return measurementXY.clone(); + } + return new float[]{ + referenceXY[0] * (1f - clampedWeight) + measurementXY[0] * clampedWeight, + referenceXY[1] * (1f - clampedWeight) + measurementXY[1] * clampedWeight + }; + } + + private long resolveGnssUpdateMinIntervalMs(float accuracyMeters) { + if (!Float.isNaN(accuracyMeters) && accuracyMeters <= GNSS_HIGH_CONFIDENCE_MAX_ACCURACY_METERS) { + return GNSS_HIGH_CONFIDENCE_UPDATE_MIN_INTERVAL_MS; + } + if (!Float.isNaN(accuracyMeters) && accuracyMeters > GNSS_REDUCED_WEIGHT_MAX_ACCURACY_METERS) { + return GNSS_LOW_CONFIDENCE_UPDATE_MIN_INTERVAL_MS + 1200L; + } + return GNSS_LOW_CONFIDENCE_UPDATE_MIN_INTERVAL_MS; + } + + private synchronized float resolveGnssMeasurementStd(float[] measurementXY, + float reportedAccuracyMeters, + boolean wifiFresh) { + float effectiveAccuracy = sanitizeGnssAccuracy(reportedAccuracyMeters); + if (!Float.isFinite(effectiveAccuracy)) { + effectiveAccuracy = GNSS_REDUCED_WEIGHT_MAX_ACCURACY_METERS + 2f; + } + float gnssFusionWeight = resolveGnssFusionWeight(effectiveAccuracy); + float std; + if (isHighConfidenceGnssWeight(gnssFusionWeight)) { + std = Math.max( + GNSS_MIN_STD_STANDALONE_METERS, + Math.min( + GNSS_HIGH_CONFIDENCE_MAX_STD_METERS, + 0.62f * effectiveAccuracy + 0.7f + ) + ); + } else if (gnssFusionWeight >= GNSS_REDUCED_FUSION_WEIGHT) { + std = Math.max( + GNSS_LOW_CONFIDENCE_MAX_STD_METERS, + Math.min(18f, effectiveAccuracy * 1.35f) + ); + } else { + std = Math.max( + GNSS_LOW_CONFIDENCE_MAX_STD_METERS + 10f, + Math.min(36f, effectiveAccuracy * 1.8f) + ); + } + + if (wifiFresh) { + std += isHighConfidenceGnssWeight(gnssFusionWeight) + ? 0.8f + : (gnssFusionWeight >= GNSS_REDUCED_FUSION_WEIGHT ? 2.0f : 4.0f); + } + + if (isValidXY(latestFusedXY) && isValidXY(measurementXY)) { + float innovation = distanceMeters(latestFusedXY, measurementXY); + if (innovation > 10f && !isHighConfidenceGnssWeight(gnssFusionWeight)) { + std = Math.max(std, 10.5f); + } + if (innovation > 18f && !isHighConfidenceGnssWeight(gnssFusionWeight)) { + std = Math.max(std, gnssFusionWeight >= GNSS_REDUCED_FUSION_WEIGHT + ? GNSS_LOW_CONFIDENCE_MAX_STD_METERS + : GNSS_LOW_CONFIDENCE_MAX_STD_METERS + 12f); + } + } + + return std; + } + + // Turns one absolute observation into a particle-filter update with tuned weighting. + private synchronized void applyAbsoluteMeasurement(float[] measurementXY, + float accuracyMeters, + com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType measurementType) { + if (!isValidXY(measurementXY) || particleFilter == null) { + return; + } + + ensureParticleFilterInitialized(); + if (!particleFilter.isInitialized()) { + return; + } + + Integer measurementFloor = measurementType == com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI + ? latestWifiObservedFloor + : null; + if (measurementType == com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI + && !wifiAnchorEstablished) { + if (saveRecording && hasUserProvidedStartLocation()) { + wifiAnchorEstablished = true; + } else { + Integer preferredFloor = resolvePreferredInitializationFloor(measurementFloor); + particleFilter.initialize( + measurementXY[0], + measurementXY[1], + getCurrentHeadingForParticleFilter(), + preferredFloor, + measurementFloor, + getFloorPlanMapForFilter(), + Math.max(DEFAULT_WIFI_MEASUREMENT_STD_METERS, WIFI_REANCHOR_STD_METERS) + ); + wifiAnchorEstablished = true; + syncFusedEstimateFromParticleFilter(System.currentTimeMillis()); + alignPdrToAnchor(measurementXY, 0.7f); + return; + } + } + + float[] effectiveMeasurementXY = measurementXY; + float effectiveAccuracy = accuracyMeters; + if (Float.isNaN(effectiveAccuracy) || effectiveAccuracy <= 0f) { + effectiveAccuracy = measurementType == com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI + ? DEFAULT_WIFI_MEASUREMENT_STD_METERS + : GNSS_MIN_STD_STANDALONE_METERS; + } + long nowMs = System.currentTimeMillis(); + boolean weakWifiUpdate = false; + if (measurementType == com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI) { + effectiveAccuracy = Math.max(DEFAULT_WIFI_MEASUREMENT_STD_METERS, effectiveAccuracy); + if (latestWifiUsesCoarsePositioning) { + effectiveAccuracy = Math.max(effectiveAccuracy, WIFI_COARSE_MEASUREMENT_STD_METERS); + } + boolean manualStartWarmupActive = isManualStartWarmupActive(nowMs); + if (manualStartWarmupActive) { + effectiveAccuracy = Math.max(effectiveAccuracy, 5.8f); + } + if (!latestWifiUsesCoarsePositioning + && !manualStartWarmupActive + && latestWifiQuality01 >= 0.55f + && latestWifiApCount >= WIFI_MIN_AP_COUNT_FOR_FINE_POSITIONING) { + float tightenedAccuracy = latestWifiQuality01 >= 0.72f + && latestWifiApCount >= WIFI_MIN_AP_COUNT_FOR_STRONG_FIX + ? Math.max(2.7f, effectiveAccuracy * 0.90f) + : Math.max(2.85f, effectiveAccuracy * 0.94f); + effectiveAccuracy = Math.min(effectiveAccuracy, tightenedAccuracy); + } + effectiveMeasurementXY = measurementXY.clone(); + weakWifiUpdate = latestWifiUsesCoarsePositioning || latestWifiQuality01 < 0.45f; + } else { + boolean wifiFresh = isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, WIFI_FRESH_TIMEOUT_MS); + float gnssFusionWeight = resolveGnssFusionWeight(accuracyMeters); + float[] gnssReferenceXY = isValidXY(latestFusedXY) + ? latestFusedXY + : (isValidXY(latestPdrXY) ? latestPdrXY : null); + if (!isGnssEligibleForFusion(accuracyMeters) && !isValidXY(gnssReferenceXY)) { + return; + } + effectiveMeasurementXY = blendMeasurementTowardsReference( + measurementXY, + gnssReferenceXY, + gnssFusionWeight + ); + effectiveAccuracy = resolveGnssMeasurementStd(effectiveMeasurementXY, accuracyMeters, wifiFresh); + if (Float.isNaN(effectiveAccuracy)) { + return; + } + long minUpdateIntervalMs = resolveGnssUpdateMinIntervalMs(accuracyMeters); + if (lastAppliedGnssMeasurementMs > 0L + && (nowMs - lastAppliedGnssMeasurementMs) < minUpdateIntervalMs) { + return; + } + + lastAppliedGnssMeasurementMs = nowMs; + } + particleFilter.update( + effectiveMeasurementXY[0], + effectiveMeasurementXY[1], + effectiveAccuracy, + measurementFloor, + measurementType, + getFloorPlanMapForFilter(), + mapMatchingEngine + ); + lastAbsoluteMeasurementTimeMs = nowMs; + syncFusedEstimateFromParticleFilter(nowMs); + if (measurementType == com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType.WIFI) { + applyWifiPositionCorrectionToPdr(effectiveMeasurementXY, measurementFloor, nowMs, weakWifiUpdate); + } + } + + // Pulls the latest particle cloud estimate back into shared fused state for the UI. + private synchronized void syncFusedEstimateFromParticleFilter(long nowMs) { + if (particleFilter == null || !particleFilter.isInitialized()) { + return; + } + + com.openpositioning.PositionMe.utils.ParticleFilter.Estimate estimate = particleFilter.getEstimatedState(); + if (estimate == null) { + return; + } + + float[] estimateXY = estimate.getPositionXY(); + if (!isValidXY(estimateXY)) { + return; + } + + int estimatedFloor = estimate.getFloor(); + float[] previousFusedXY = isValidXY(latestFusedXY) ? latestFusedXY.clone() : null; + latestPfXY = new float[]{estimateXY[0], estimateXY[1]}; + latestFusedXY = latestPfXY.clone(); + updateMotionHeadingFromTrajectory(previousFusedXY, latestFusedXY, nowMs); + + int previousFloor = currentFloor; + currentFloor = estimatedFloor; + currentFloorPlan = resolveFloorPlan(estimatedFloor); + updateTrajectoryDisplayPoint(latestFusedXY); + if (estimatedFloor != previousFloor) { + pendingFloorDelta += estimatedFloor - previousFloor; + lastFloorSwitchTimeMs = nowMs; + confirmedFloorReferenceElevation = elevation; + confirmedFloorReferenceInitialized = true; + } + lastFusedUpdateTimeMs = nowMs; + } + + private float getSensorHeadingRadOrNaN() { + if (!headingInitialized) { + return Float.NaN; + } + float heading = normalizeAngleRad(this.orientation[0]); + if (Float.isNaN(heading) || Float.isInfinite(heading)) { + return Float.NaN; + } + return heading; + } + + private float getCurrentHeadingForParticleFilter() { + long nowMs = System.currentTimeMillis(); + float sensorHeading = getPreferredSensorHeadingRad(nowMs); + float wallHeading = resolveFreshWallAlignedHeadingRad(nowMs); + if (!Float.isNaN(wallHeading) && !Float.isInfinite(wallHeading)) { + if (!Float.isNaN(sensorHeading) && !Float.isInfinite(sensorHeading)) { + return blendAnglesRad(sensorHeading, wallHeading, WALL_HEADING_OVERRIDE_BLEND); + } + return wallHeading; + } + if (!Float.isNaN(sensorHeading) && !Float.isInfinite(sensorHeading)) { + return sensorHeading; + } + if (!Float.isNaN(latestMotionHeadingRad) + && !Float.isInfinite(latestMotionHeadingRad) + && lastMotionHeadingTimeMs > 0L + && (nowMs - lastMotionHeadingTimeMs) <= MOTION_HEADING_FRESH_TIMEOUT_MS) { + return normalizeAngleRad(latestMotionHeadingRad); + } + if (!Float.isNaN(sensorHeading) && !Float.isInfinite(sensorHeading)) { + return sensorHeading; + } + return 0f; + } + + private float getDisplayHeadingRad() { + float sensorHeading = getPreferredSensorHeadingRad(System.currentTimeMillis()); + if (!Float.isNaN(sensorHeading) && !Float.isInfinite(sensorHeading)) { + return sensorHeading; + } + return getCurrentHeadingForParticleFilter(); + } + + private boolean hasActiveMotionForDisplayHeading(long nowMs) { + boolean recentStep = lastStepTime > 0L + && (nowMs - lastStepTime) <= DISPLAY_HEADING_ACTIVE_STEP_TIMEOUT_MS; + boolean gnssMoving = isGnssSpeedFreshForPrediction(nowMs) + && latestGnssSpeedMps >= DISPLAY_HEADING_ACTIVE_GNSS_SPEED_MPS; + return recentStep || gnssMoving; + } + + private float resolveFreshMotionHeadingRad(long nowMs) { + if (!Float.isNaN(latestMotionHeadingRad) + && lastMotionHeadingTimeMs > 0L + && (nowMs - lastMotionHeadingTimeMs) <= MOTION_HEADING_FRESH_TIMEOUT_MS) { + return latestMotionHeadingRad; + } + float heading = getPreferredSensorHeadingRad(nowMs); + if (Float.isNaN(heading) || Float.isInfinite(heading)) { + return Float.NaN; + } + return heading; + } + + private synchronized float[] constrainWifiCorrectionToMotion(float[] candidateXY, + long nowMs, + boolean weakWifiUpdate) { + if (!WIFI_SIGNAL_FILTERING_ENABLED) { + return candidateXY; + } + if (!isValidXY(candidateXY) || isLikelyStationary(nowMs)) { + return candidateXY; + } + float[] referenceXY = isValidXY(latestFusedXY) ? latestFusedXY : latestPdrXY; + if (!isValidXY(referenceXY)) { + return candidateXY; + } + float motionHeadingRad = resolveFreshMotionHeadingRad(nowMs); + if (Float.isNaN(motionHeadingRad) || Float.isInfinite(motionHeadingRad)) { + return candidateXY; + } + + float dx = candidateXY[0] - referenceXY[0]; + float dy = candidateXY[1] - referenceXY[1]; + float totalCorrection = (float) Math.sqrt(dx * dx + dy * dy); + if (totalCorrection <= 0.35f) { + return candidateXY; + } + + float dirX = (float) Math.sin(motionHeadingRad); + float dirY = (float) Math.cos(motionHeadingRad); + float parallel = dx * dirX + dy * dirY; + float lateral = -dx * dirY + dy * dirX; + float maxLateral = weakWifiUpdate + ? WIFI_MOTION_LATERAL_RELAXED_CLAMP_METERS + : WIFI_MOTION_LATERAL_CLAMP_METERS; + float maxForward = weakWifiUpdate + ? WIFI_MOTION_FORWARD_RELAXED_CLAMP_METERS + : WIFI_MOTION_FORWARD_CLAMP_METERS; + float maxBacktrack = weakWifiUpdate + ? WIFI_MOTION_BACKTRACK_RELAXED_CLAMP_METERS + : WIFI_MOTION_BACKTRACK_CLAMP_METERS; + if (totalCorrection > 8f) { + maxLateral = Math.max(maxLateral, 2.4f); + maxForward = Math.max(maxForward, 9f); + } + float clampedLateral = Math.max(-maxLateral, Math.min(maxLateral, lateral)); + float clampedParallel = Math.max(-maxBacktrack, Math.min(maxForward, parallel)); + if (Math.abs(lateral - clampedLateral) < 1e-4f + && Math.abs(parallel - clampedParallel) < 1e-4f) { + return candidateXY; + } + + return new float[]{ + referenceXY[0] + clampedParallel * dirX - clampedLateral * dirY, + referenceXY[1] + clampedParallel * dirY + clampedLateral * dirX + }; + } + + private boolean isFreshSource(float[] xy, long timestampMs, long nowMs, long timeoutMs) { + return isValidXY(xy) && timestampMs > 0 && (nowMs - timestampMs) <= timeoutMs; + } + + private synchronized boolean isWifiUpdateOutlier(float[] candidateXY, long nowMs) { + if (!WIFI_SIGNAL_FILTERING_ENABLED) { + return false; + } + if (!isValidXY(candidateXY)) { + return true; + } + if (!wifiAnchorEstablished || lastAcceptedWifiFixTimeMs <= 0L) { + resetRejectedWifiCluster(); + return false; + } + if (saveRecording + && absoluteStartTime > 0L + && (nowMs - absoluteStartTime) <= (WIFI_INITIALIZATION_GRACE_MS + 3000L)) { + resetRejectedWifiCluster(); + return false; + } + float[] reference = null; + if (isFreshSource(latestFusedXY, lastFusedUpdateTimeMs, nowMs, 8000L)) { + reference = latestFusedXY; + } else if (isValidXY(latestPdrXY)) { + reference = latestPdrXY; + } else if (isValidXY(lastAcceptedWifiXY)) { + reference = lastAcceptedWifiXY; + } + if (!isValidXY(reference)) { + resetRejectedWifiCluster(); + return false; + } + + float dtSecFromFused = Math.min(18f, Math.max(1f, (nowMs - Math.max(lastFusedUpdateTimeMs, 1L)) / 1000f)); + float allowedFromFused = Math.min( + WIFI_OUTLIER_MAX_METERS, + WIFI_OUTLIER_BASE_METERS + WIFI_OUTLIER_SPEED_LIMIT_MPS * dtSecFromFused + ); + boolean outlier = distanceMeters(candidateXY, reference) > allowedFromFused; + + if (outlier && isValidXY(lastAcceptedWifiXY) && lastAcceptedWifiFixTimeMs > 0L) { + float dtSecFromWifi = Math.min(22f, Math.max(1f, (nowMs - lastAcceptedWifiFixTimeMs) / 1000f)); + float allowedFromWifi = Math.min( + WIFI_OUTLIER_MAX_METERS, + WIFI_OUTLIER_BASE_METERS + WIFI_OUTLIER_SPEED_LIMIT_MPS * dtSecFromWifi + ); + outlier = distanceMeters(candidateXY, lastAcceptedWifiXY) > allowedFromWifi; + } + + if (outlier && isGnssReliableForAssistingWifiOutlier(nowMs)) { + float gnssDistance = distanceMeters(candidateXY, latestGnssXY); + if (gnssDistance <= GNSS_WIFI_OUTLIER_ASSIST_MAX_DISTANCE_METERS) { + outlier = false; + } + } + + if (outlier && consecutiveRejectedWifiUpdates >= WIFI_OUTLIER_REJECT_LIMIT) { + float relaxedAllowed = Math.min( + WIFI_OUTLIER_MAX_METERS, + allowedFromFused + WIFI_OUTLIER_RELEASE_MARGIN_METERS + ); + float[] releaseReference = isValidXY(latestPdrXY) ? latestPdrXY : latestFusedXY; + if (isValidXY(releaseReference) + && distanceMeters(candidateXY, releaseReference) <= relaxedAllowed) { + // Controlled release: allow if candidate is still close to predicted trajectory. + return false; + } + if (consecutiveRejectedWifiUpdates >= WIFI_OUTLIER_REJECT_LIMIT * 2) { + // Avoid freezing trajectory when WiFi fluctuates for an extended period. + resetRejectedWifiCluster(); + return false; + } + } + + if (outlier && shouldAcceptRepeatedRejectedWifiCluster(candidateXY)) { + resetRejectedWifiCluster(); + return false; + } + if (!outlier) { + resetRejectedWifiCluster(); + } + return outlier; + } + + private boolean isGnssReliableForAssistingWifiOutlier(long nowMs) { + return isFreshSource(latestGnssXY, latestGnssFixTimeMs, nowMs, GNSS_FRESH_TIMEOUT_MS) + && !Float.isNaN(latestGnssAccuracyMeters) + && latestGnssAccuracyMeters <= GNSS_WIFI_OUTLIER_ASSIST_MAX_ACCURACY_METERS; + } + + private boolean shouldAcceptRepeatedRejectedWifiCluster(float[] candidateXY) { + if (!isValidXY(candidateXY)) { + return false; + } + if (isValidXY(lastRejectedWifiXY) + && distanceMeters(candidateXY, lastRejectedWifiXY) <= WIFI_REJECTED_CLUSTER_RADIUS_METERS) { + repeatedRejectedWifiClusterCount++; + } else { + lastRejectedWifiXY = new float[]{candidateXY[0], candidateXY[1]}; + repeatedRejectedWifiClusterCount = 1; + } + return repeatedRejectedWifiClusterCount >= WIFI_REJECTED_CLUSTER_ACCEPT_COUNT; + } + + private void resetRejectedWifiCluster() { + lastRejectedWifiXY = null; + repeatedRejectedWifiClusterCount = 0; + } + + private boolean hasExplicitStartLocation() { + return startLocation != null + && startLocation.length >= 2 + && (startLocation[0] != 0f || startLocation[1] != 0f); + } + + private boolean hasUserProvidedStartLocation() { + return userProvidedStartLocation && hasExplicitStartLocation(); + } + + private boolean isManualStartWarmupActive(long nowMs) { + return saveRecording + && hasUserProvidedStartLocation() + && absoluteStartTime > 0L + && (nowMs - absoluteStartTime) <= MANUAL_START_WIFI_ASSIST_WINDOW_MS; + } + + private synchronized float[] applyMobileContextToWifiMeasurement(float[] candidateXY, long nowMs) { + if (!isValidXY(candidateXY)) { + return candidateXY; + } + + float[] adjustedXY = candidateXY.clone(); + boolean appliedGnssAssist = false; + if (isFreshSource(latestGnssXY, latestGnssFixTimeMs, nowMs, GNSS_FRESH_TIMEOUT_MS) + && !Float.isNaN(latestGnssAccuracyMeters) + && latestGnssAccuracyMeters <= WIFI_MOBILE_ASSIST_MAX_GNSS_ACCURACY_METERS) { + float gnssFusionWeight = resolveGnssFusionWeight(latestGnssAccuracyMeters); + float blend = isHighConfidenceGnssWeight(gnssFusionWeight) + ? WIFI_MOBILE_ASSIST_STRONG_GNSS_BLEND + : (gnssFusionWeight >= GNSS_REDUCED_FUSION_WEIGHT + ? WIFI_MOBILE_ASSIST_WEAK_GNSS_BLEND + : GNSS_MINIMAL_FUSION_WEIGHT); + adjustedXY = new float[]{ + adjustedXY[0] * (1f - blend) + latestGnssXY[0] * blend, + adjustedXY[1] * (1f - blend) + latestGnssXY[1] * blend + }; + adjustedXY = clampTowardsReference( + latestGnssXY, + adjustedXY, + Math.max( + WIFI_MANUAL_START_CLAMP_METERS, + latestGnssAccuracyMeters + WIFI_MOBILE_ASSIST_CLAMP_MARGIN_METERS + ) + ); + appliedGnssAssist = true; + } + + if (isManualStartWarmupActive(nowMs)) { + adjustedXY = clampTowardsReference( + new float[]{0f, 0f}, + adjustedXY, + appliedGnssAssist + ? WIFI_MANUAL_START_WITH_GNSS_CLAMP_METERS + : WIFI_MANUAL_START_CLAMP_METERS + ); + } + return adjustedXY; + } + + private synchronized void applyWifiDominantStartAnchorIfAvailable() { + if (hasExplicitStartLocation()) { + return; + } + long nowMs = System.currentTimeMillis(); + boolean wifiFresh = isFreshSource(latestWifiXY, latestWifiFixTimeMs, nowMs, WIFI_FRESH_TIMEOUT_MS); + boolean gnssFresh = isFreshSource(latestGnssXY, latestGnssFixTimeMs, nowMs, GNSS_FRESH_TIMEOUT_MS); + boolean gnssReliable = isGnssEligibleForFusion(latestGnssAccuracyMeters); + + LatLng anchorLatLng = null; + if (wifiFresh && latestWifiLatLng != null && gnssFresh && latestGnssLatLng != null && gnssReliable) { + final double wifiWeight = WIFI_START_ANCHOR_WEIGHT; + anchorLatLng = new LatLng( + wifiWeight * latestWifiLatLng.latitude + (1.0 - wifiWeight) * latestGnssLatLng.latitude, + wifiWeight * latestWifiLatLng.longitude + (1.0 - wifiWeight) * latestGnssLatLng.longitude + ); + } else if (wifiFresh && latestWifiLatLng != null) { + anchorLatLng = latestWifiLatLng; + } else if (gnssFresh && latestGnssLatLng != null && gnssReliable) { + anchorLatLng = latestGnssLatLng; + } + + if (anchorLatLng == null || coordinateUtils == null) { + return; + } + + startLocation[0] = (float) anchorLatLng.latitude; + startLocation[1] = (float) anchorLatLng.longitude; + userProvidedStartLocation = false; + coordinateUtils.resetOrigin(); + coordinateUtils.setOrigin(anchorLatLng.latitude, anchorLatLng.longitude); + + latestGnssXY = toXY(latestGnssLatLng); + latestWifiXY = toXY(latestWifiLatLng); + latestRawPdrXY = new float[]{0f, 0f}; + pdrAlignmentOffsetXY = new float[]{0f, 0f}; + latestPdrXY = new float[]{0f, 0f}; + latestPfXY = null; + latestFusedXY = new float[]{0f, 0f}; + invalidateHeadingState(); + lastCords = new float[]{0f, 0f}; + lastDisplayTrajectoryUpdateMs = 0L; + lastFusedUpdateTimeMs = nowMs; + lastAbsoluteMeasurementTimeMs = 0L; + lastAcceptedWifiXY = isValidXY(latestWifiXY) ? latestWifiXY.clone() : null; + lastAcceptedWifiFixTimeMs = latestWifiFixTimeMs; + consecutiveRejectedWifiUpdates = 0; + resetRejectedWifiCluster(); + resetStationaryDetectionState(); + } + + private float[] toXY(LatLng latLng) { + if (latLng == null || coordinateUtils == null || !coordinateUtils.isOriginSet()) { + return null; + } + return coordinateUtils.latLonToXY(latLng.latitude, latLng.longitude); + } + + private LatLng toLatLng(float[] xy) { + if (!isValidXY(xy) || coordinateUtils == null || !coordinateUtils.isOriginSet()) { + return null; + } + double[] latLon = coordinateUtils.xyToLatLon(xy[0], xy[1]); + if (latLon == null || latLon.length < 2) { + return null; + } + return new LatLng(latLon[0], latLon[1]); + } + + private synchronized float[] stabilizeWifiMeasurement(float[] candidateXY) { + if (!WIFI_SIGNAL_FILTERING_ENABLED) { + return candidateXY != null ? candidateXY.clone() : null; + } + if (!isValidXY(candidateXY)) { + return candidateXY; + } + if (!wifiAnchorEstablished || !saveRecording || !isValidXY(latestFusedXY)) { + return candidateXY.clone(); + } + float maxCorrectionMeters = WIFI_MEASUREMENT_MAX_CORRECTION_METERS; + long nowMs = System.currentTimeMillis(); + if (lastFusedUpdateTimeMs <= 0L || (nowMs - lastFusedUpdateTimeMs) > 2200L) { + maxCorrectionMeters = Math.max(maxCorrectionMeters, 12f); + } + if (!Float.isNaN(latestGnssAccuracyMeters)) { + if (latestGnssAccuracyMeters <= GNSS_HIGH_CONFIDENCE_MAX_ACCURACY_METERS) { + maxCorrectionMeters = Math.max(maxCorrectionMeters, 14f); + } else if (latestGnssAccuracyMeters <= GNSS_LOW_CONFIDENCE_MAX_ACCURACY_METERS) { + maxCorrectionMeters = Math.max(maxCorrectionMeters, 11f); + } + } + if (consecutiveRejectedWifiUpdates > 0) { + maxCorrectionMeters = Math.min( + 16f, + maxCorrectionMeters + consecutiveRejectedWifiUpdates * 1.5f + ); + } + return clampTowardsReference(latestFusedXY, candidateXY, maxCorrectionMeters); + } + + private float[] clampTowardsReference(float[] referenceXY, float[] targetXY, float maxDistanceMeters) { + if (!isValidXY(referenceXY) || !isValidXY(targetXY) || maxDistanceMeters <= 0f) { + return targetXY; + } + float dx = targetXY[0] - referenceXY[0]; + float dy = targetXY[1] - referenceXY[1]; + float distance = (float) Math.sqrt(dx * dx + dy * dy); + if (distance <= maxDistanceMeters || distance <= 1e-5f) { + return targetXY.clone(); + } + float ratio = maxDistanceMeters / distance; + return new float[]{ + referenceXY[0] + dx * ratio, + referenceXY[1] + dy * ratio + }; + } + + private synchronized float estimateWifiMeasurementStd(float[] wifiMeasurementXY, + boolean isOutlier, + float fingerprintQuality01, + int apCount, + boolean usedCoarsePositioning) { + float std = usedCoarsePositioning + ? WIFI_COARSE_MEASUREMENT_STD_METERS + : DEFAULT_WIFI_MEASUREMENT_STD_METERS; + if (!usedCoarsePositioning && fingerprintQuality01 >= 0.72f && apCount >= WIFI_MIN_AP_COUNT_FOR_STRONG_FIX) { + std = Math.min(std, WIFI_STRONG_FINE_MEASUREMENT_STD_METERS); + } + if (fingerprintQuality01 < 0.35f) { + std = Math.max(std, usedCoarsePositioning ? 9.0f : 6.2f); + } else if (fingerprintQuality01 < 0.55f) { + std = Math.max(std, usedCoarsePositioning ? 8.2f : 4.8f); + } + if (apCount < WIFI_MIN_AP_COUNT_FOR_FINE_POSITIONING) { + std = Math.max(std, usedCoarsePositioning ? 8.8f : 5.6f); + } + float[] reference = isValidXY(latestFusedXY) ? latestFusedXY : latestPdrXY; + if (isValidXY(reference) && isValidXY(wifiMeasurementXY)) { + float innovation = distanceMeters(reference, wifiMeasurementXY); + if (innovation > 4f) { + std = Math.max(std, 5f); + } + if (innovation > 7f) { + std = Math.max(std, 8f); + } + if (innovation > 10f) { + std = Math.max(std, 11f); + } + } + if (isOutlier) { + std = Math.max(std, WIFI_OUTLIER_WEAK_UPDATE_STD_METERS); + } + return std; + } + + private void enqueueAbsoluteMeasurementUpdate(float[] measurementXY, + float accuracyMeters, + com.openpositioning.PositionMe.utils.ParticleFilter.MeasurementType measurementType) { + if (!isValidXY(measurementXY)) { + return; + } + final float[] measurementCopy = new float[]{measurementXY[0], measurementXY[1]}; + fusionUpdateExecutor.execute(() -> + applyAbsoluteMeasurement(measurementCopy, accuracyMeters, measurementType) + ); + } + + private boolean isValidXY(float[] xy) { + return xy != null + && xy.length >= 2 + && !Float.isNaN(xy[0]) + && !Float.isNaN(xy[1]) + && !Float.isInfinite(xy[0]) + && !Float.isInfinite(xy[1]); + } + + private float distanceMeters(float[] a, float[] b) { + float dx = a[0] - b[0]; + float dy = a[1] - b[1]; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + private float normalizeAngleRad(float angleRad) { + while (angleRad > Math.PI) angleRad -= (float) (2.0 * Math.PI); + while (angleRad < -Math.PI) angleRad += (float) (2.0 * Math.PI); + return angleRad; + } + + // Restricts floor changes to valid stairs/lift zones and confirmed height transitions. + private synchronized void updateConstrainedFloorFromElevation() { + com.openpositioning.PositionMe.data.remote.FloorPlan activeFloorPlan = resolveFloorPlan(currentFloor); + if (activeFloorPlan == null || mapMatchingEngine == null || particleFilter == null || !particleFilter.isInitialized()) { + return; + } - if(this.saveRecording) { - Traj.WiFi_Sample.Builder wifiData = Traj.WiFi_Sample.newBuilder() - .setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime); - for (Wifi data : this.wifiList) { - wifiData.addMacScans(Traj.Mac_Scan.newBuilder() - .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) - .setMac(data.getBssid()).setRssi(data.getLevel())); - } - // Adding WiFi data to Trajectory - this.trajectory.addWifiData(wifiData); + if (!confirmedFloorReferenceInitialized) { + confirmedFloorReferenceElevation = elevation; + confirmedFloorReferenceInitialized = true; + return; } - createWifiPositioningRequest(); - } - /** - * Function to create a request to obtain a wifi location for the obtained wifi fingerprint - * - */ - private void createWifiPositioningRequest(){ - // Try catch block to catch any errors and prevent app crashing - try { - // Creating a JSON object to store the WiFi access points - JSONObject wifiAccessPoints=new JSONObject(); - for (Wifi data : this.wifiList){ - wifiAccessPoints.put(String.valueOf(data.getBssid()), data.getLevel()); - } - // Creating POST Request - JSONObject wifiFingerPrint = new JSONObject(); - wifiFingerPrint.put(WIFI_FINGERPRINT, wifiAccessPoints); - this.wiFiPositioning.request(wifiFingerPrint); - } catch (JSONException e) { - // Catching error while making JSON object, to prevent crashes - // Error log to keep record of errors (for secure programming and maintainability) - Log.e("jsonErrors","Error creating json object"+e.toString()); + long now = System.currentTimeMillis(); + if (now - lastFloorSwitchTimeMs < FLOOR_SWITCH_COOLDOWN_MS) { + return; } - } - // Callback Example Function - /** - * Function to create a request to obtain a wifi location for the obtained wifi fingerprint - * using Volley Callback - */ - private void createWifiPositionRequestCallback(){ - try { - // Creating a JSON object to store the WiFi access points - JSONObject wifiAccessPoints=new JSONObject(); - for (Wifi data : this.wifiList){ - wifiAccessPoints.put(String.valueOf(data.getBssid()), data.getLevel()); - } - // Creating POST Request - JSONObject wifiFingerPrint = new JSONObject(); - wifiFingerPrint.put(WIFI_FINGERPRINT, wifiAccessPoints); - this.wiFiPositioning.request(wifiFingerPrint, new WiFiPositioning.VolleyCallback() { - @Override - public void onSuccess(LatLng wifiLocation, int floor) { - // Handle the success response - } - @Override - public void onError(String message) { - // Handle the error response - } - }); - } catch (JSONException e) { - // Catching error while making JSON object, to prevent crashes - // Error log to keep record of errors (for secure programming and maintainability) - Log.e("jsonErrors","Error creating json object"+e.toString()); + float floorHeightMeters = Math.max(AUTO_FLOOR_HEIGHT_STEP_METERS, settings != null + ? settings.getInt("floor_height", (int) AUTO_FLOOR_HEIGHT_STEP_METERS) + : AUTO_FLOOR_HEIGHT_STEP_METERS); + float transitionThreshold = Math.max(FLOOR_TRANSITION_MIN_METERS, floorHeightMeters * FLOOR_TRANSITION_CONFIRM_RATIO); + float relativeHeightDelta = elevation - confirmedFloorReferenceElevation; + if (Math.abs(relativeHeightDelta) < transitionThreshold) { + return; + } + + float[] referenceXY = isValidXY(latestFusedXY) ? latestFusedXY : latestPdrXY; + if (!isValidXY(referenceXY)) { + return; + } + + if (!mapMatchingEngine.isInsideVerticalAccess(referenceXY, elevator, activeFloorPlan)) { + return; + } + + float verticalTransitionScore = mapMatchingEngine.verticalTransitionScore(referenceXY, elevator, activeFloorPlan); + if (verticalTransitionScore < 0.6f) { + return; + } + + int oneStepDelta = relativeHeightDelta > 0f ? 1 : -1; + boolean accepted = particleFilter.applyFloorChange( + oneStepDelta, + elevator, + getFloorPlanMapForFilter(), + mapMatchingEngine + ); + if (!accepted) { + return; } + lastFloorSwitchTimeMs = now; + confirmedFloorReferenceElevation = elevation; + confirmedFloorReferenceInitialized = true; + syncFusedEstimateFromParticleFilter(now); } /** @@ -545,7 +3184,9 @@ public void onError(String message) { * * @return {@link LatLng} corresponding to user's position. */ - public LatLng getLatLngWifiPositioning(){return this.wiFiPositioning.getWifiLocation();} + public LatLng getLatLngWifiPositioning() { + return this.wiFiPositioning != null ? this.wiFiPositioning.getWifiLocation() : null; + } /** * Method to get current floor the user is at, obtained using WiFiPositioning @@ -553,7 +3194,7 @@ public void onError(String message) { * @return Current floor user is at using WiFiPositioning */ public int getWifiFloor(){ - return this.wiFiPositioning.getFloor(); + return this.wiFiPositioning != null ? this.wiFiPositioning.getFloor() : 0; } /** @@ -628,6 +3269,34 @@ public void onAccuracyChanged(Sensor sensor, int i) {} //endregion //region Getters/Setters + public void setStartTimestampMs(long ts) { + this.startTimestampMs = ts; + } + + public long getStartTimestampMs() { + return this.startTimestampMs; + } + + public synchronized void setTestPoints(java.util.List points) { + this.testPoints.clear(); + if (points != null) this.testPoints.addAll(points); + syncTestPointsIntoTrajectory(); + } + + public synchronized void appendTestPoint(com.openpositioning.PositionMe.Traj.GNSSPosition point) { + if (point == null) { + return; + } + // Only add to the canonical testPoints list. syncTestPointsIntoTrajectory() will + // clear + re-add all points into trajectory before upload, so writing directly to + // trajectory here would cause duplicates when sync runs. + this.testPoints.add(point); + } + + public synchronized java.util.List getTestPoints() { + return new java.util.ArrayList<>(this.testPoints); + } + /** * Getter function for core location data. * @@ -652,7 +3321,70 @@ public float[] getGNSSLatitude(boolean start) { * @param startPosition contains the initial location set by the user */ public void setStartGNSSLatitude(float[] startPosition){ - startLocation = startPosition; + LatLng preservedGnssLatLng = this.latestGnssLatLng; + LatLng preservedWifiLatLng = this.latestWifiLatLng; + float preservedGnssAccuracy = this.latestGnssAccuracyMeters; + float preservedGnssSpeed = this.latestGnssSpeedMps; + long preservedGnssFixTime = this.latestGnssFixTimeMs; + long preservedWifiFixTime = this.latestWifiFixTimeMs; + Integer preservedWifiFloor = this.latestWifiObservedFloor; + + if (startPosition != null && startPosition.length >= 2) { + startLocation = startPosition.clone(); + this.userProvidedStartLocation = (startPosition[0] != 0f || startPosition[1] != 0f); + } else { + startLocation = new float[2]; + this.userProvidedStartLocation = false; + } + if (coordinateUtils != null) { + coordinateUtils.resetOrigin(); + if (startLocation.length >= 2 && (startLocation[0] != 0f || startLocation[1] != 0f)) { + coordinateUtils.setOrigin(startLocation[0], startLocation[1]); + } + } + this.latestGnssLatLng = preservedGnssLatLng; + this.latestWifiLatLng = preservedWifiLatLng; + this.latestGnssXY = toXY(preservedGnssLatLng); + this.latestWifiXY = toXY(preservedWifiLatLng); + this.latestGnssAccuracyMeters = preservedGnssAccuracy; + this.latestGnssSpeedMps = preservedGnssSpeed; + this.latestGnssFixTimeMs = preservedGnssFixTime; + this.latestWifiFixTimeMs = preservedWifiFixTime; + this.latestWifiObservedFloor = preservedWifiFloor; + this.lastAcceptedWifiXY = null; + this.lastAcceptedWifiFixTimeMs = 0L; + this.consecutiveRejectedWifiUpdates = 0; + resetRejectedWifiCluster(); + long nowMs = System.currentTimeMillis(); + if (preservedWifiFloor != null + && preservedWifiFixTime > 0L + && (nowMs - preservedWifiFixTime) <= WIFI_FRESH_TIMEOUT_MS) { + this.initialFloorHint = normalizeFloorToAvailable(preservedWifiFloor); + } else { + this.initialFloorHint = normalizeFloorToAvailable(this.currentFloor); + } + this.latestRawPdrXY = new float[]{0f, 0f}; + this.pdrAlignmentOffsetXY = new float[]{0f, 0f}; + this.latestPdrXY = new float[]{0f, 0f}; + this.latestPfXY = null; + this.latestFusedXY = new float[]{0f, 0f}; + invalidateHeadingState(); + this.lastFusedUpdateTimeMs = 0L; + this.lastAppliedGnssMeasurementMs = 0L; + this.lastAbsoluteMeasurementTimeMs = 0L; + this.lastCords = null; + this.lastDisplayTrajectoryUpdateMs = 0L; + this.currentFloor = normalizeFloorToAvailable(initialFloorHint); + this.currentFloorPlan = resolveFloorPlan(currentFloor); + this.pendingFloorDelta = 0; + this.lastFloorSwitchTimeMs = 0L; + this.confirmedFloorReferenceElevation = 0f; + this.confirmedFloorReferenceInitialized = false; + this.wifiAnchorEstablished = false; + resetStationaryDetectionState(); + if (this.particleFilter != null) { + this.particleFilter.reset(); + } } @@ -662,7 +3394,16 @@ public void setStartGNSSLatitude(float[] startPosition){ * @param scalingRatio new size of path due to updated step length */ public void redrawPath(float scalingRatio){ - pathView.redraw(scalingRatio); + if (pathView == null) { + return; + } + mainThreadHandler.post(() -> { + if (pathView == null) { + return; + } + pathView.redraw(scalingRatio); + pathView.postInvalidateOnAnimation(); + }); } /** @@ -682,7 +3423,96 @@ public float passAverageStepLength(){ * @return orientation of device. */ public float passOrientation(){ - return orientation[0]; + return getDisplayHeadingRad(); + } + + public float getLatestGnssAccuracyMeters() { + return latestGnssAccuracyMeters; + } + + public boolean isUserStationary() { + return isLikelyStationary(System.currentTimeMillis()); + } + + public static final class DisplaySnapshot { + public final float[] wifiXY = new float[2]; + public final float[] pdrXY = new float[2]; + public final float[] fusedXY = new float[2]; + public final float[] displayTrajectoryXY = new float[2]; + public final double[] originLatLon = new double[2]; + public LatLng gnssLatLng; + public LatLng wifiLatLng; + public LatLng wifiPositioningLatLng; + public boolean hasWifiXY; + public boolean hasPdrXY; + public boolean hasFusedXY; + public boolean hasDisplayTrajectoryXY; + public boolean hasOrigin; + public boolean wifiFresh; + public boolean gnssFresh; + public boolean stationary; + public boolean wifiUsesCoarsePositioning; + public int wifiApCount; + public float wifiQuality01 = 0f; + public float gnssAccuracyMeters = Float.NaN; + public float orientationRad = Float.NaN; + public long wifiFixTimeMs; + public long gnssFixTimeMs; + public long frameTimeMs; + + public void reset() { + gnssLatLng = null; + wifiLatLng = null; + wifiPositioningLatLng = null; + hasWifiXY = false; + hasPdrXY = false; + hasFusedXY = false; + hasDisplayTrajectoryXY = false; + hasOrigin = false; + wifiFresh = false; + gnssFresh = false; + stationary = false; + wifiUsesCoarsePositioning = false; + wifiApCount = 0; + wifiQuality01 = 0f; + gnssAccuracyMeters = Float.NaN; + orientationRad = Float.NaN; + wifiFixTimeMs = 0L; + gnssFixTimeMs = 0L; + frameTimeMs = 0L; + } + } + + // Packs only the display-facing fusion state needed by map fragments and overlays. + public void fillDisplaySnapshot(@NonNull DisplaySnapshot snapshot, long fusedTimeoutMs) { + if (fusedTimeoutMs <= 0L) { + fusedTimeoutMs = 1500L; + } + long now = System.currentTimeMillis(); + snapshot.reset(); + snapshot.frameTimeMs = now; + snapshot.gnssLatLng = latestGnssLatLng; + snapshot.wifiLatLng = latestWifiLatLng; + snapshot.wifiPositioningLatLng = wiFiPositioning != null ? wiFiPositioning.getWifiLocation() : null; + snapshot.hasWifiXY = copyXYInto(latestWifiXY, snapshot.wifiXY); + snapshot.hasPdrXY = copyXYInto(latestPdrXY, snapshot.pdrXY); + snapshot.hasFusedXY = lastFusedUpdateTimeMs > 0L + && (now - lastFusedUpdateTimeMs) <= fusedTimeoutMs + && copyXYInto(latestFusedXY, snapshot.fusedXY); + snapshot.hasDisplayTrajectoryXY = lastDisplayTrajectoryUpdateMs > 0L + && (now - lastDisplayTrajectoryUpdateMs) <= fusedTimeoutMs + && copyXYInto(lastCords, snapshot.displayTrajectoryXY); + snapshot.wifiFresh = isFreshSource(latestWifiXY, latestWifiFixTimeMs, now, WIFI_FRESH_TIMEOUT_MS); + snapshot.gnssFresh = isFreshSource(latestGnssXY, latestGnssFixTimeMs, now, GNSS_FRESH_TIMEOUT_MS); + snapshot.stationary = isLikelyStationary(now); + snapshot.wifiUsesCoarsePositioning = latestWifiUsesCoarsePositioning; + snapshot.wifiApCount = latestWifiApCount; + snapshot.wifiQuality01 = latestWifiQuality01; + snapshot.gnssAccuracyMeters = latestGnssAccuracyMeters; + snapshot.orientationRad = getDisplayHeadingRad(); + snapshot.wifiFixTimeMs = latestWifiFixTimeMs; + snapshot.gnssFixTimeMs = latestGnssFixTimeMs; + snapshot.hasOrigin = copyDisplayOriginLatLon(snapshot.originLatLon); } /** @@ -703,7 +3533,7 @@ public Map getSensorValueMap() { sensorValueMap.put(SensorTypes.PRESSURE, new float[]{pressure}); sensorValueMap.put(SensorTypes.PROXIMITY, new float[]{proximity}); sensorValueMap.put(SensorTypes.GNSSLATLONG, getGNSSLatitude(false)); - sensorValueMap.put(SensorTypes.PDR, pdrProcessing.getPDRMovement()); + sensorValueMap.put(SensorTypes.PDR, getLatestPdrPositionXY()); return sensorValueMap; } @@ -756,6 +3586,148 @@ public float getElevation() { return this.elevation; } + + public LatLng getLatestGnssLatLng() { + return latestGnssLatLng; + } + + + public LatLng getLatestWifiLatLng() { + return latestWifiLatLng; + } + + public boolean copyLatestWifiPositionXY(@NonNull float[] out) { + return copyXYInto(latestWifiXY, out); + } + + public float[] getLatestWifiPositionXY() { + return latestWifiXY == null ? null : latestWifiXY.clone(); + } + + public boolean isLatestWifiFresh() { + long now = System.currentTimeMillis(); + return isFreshSource(latestWifiXY, latestWifiFixTimeMs, now, WIFI_FRESH_TIMEOUT_MS); + } + + public boolean isLatestGnssFresh() { + long now = System.currentTimeMillis(); + return isFreshSource(latestGnssXY, latestGnssFixTimeMs, now, GNSS_FRESH_TIMEOUT_MS); + } + + public boolean copyLatestPdrPositionXY(@NonNull float[] out) { + return copyXYInto(latestPdrXY, out); + } + + public float[] getLatestPdrPositionXY() { + return latestPdrXY == null ? null : latestPdrXY.clone(); + } + + public boolean copyLatestFusedPositionXY(@NonNull float[] out) { + return lastFusedUpdateTimeMs > 0L && copyXYInto(latestFusedXY, out); + } + + public float[] getLatestFusedPositionXY() { + if (lastFusedUpdateTimeMs <= 0L) { + return null; + } + return latestFusedXY == null ? null : latestFusedXY.clone(); + } + + public boolean isLatestFusedFresh(long timeoutMs) { + if (timeoutMs <= 0L) { + timeoutMs = 1500L; + } + long now = System.currentTimeMillis(); + return lastFusedUpdateTimeMs > 0L + && (now - lastFusedUpdateTimeMs) <= timeoutMs + && isValidXY(latestFusedXY); + } + + public boolean copyDisplayOriginLatLon(@NonNull double[] out) { + if (out.length < 2) { + return false; + } + if (coordinateUtils != null && coordinateUtils.copyOriginTo(out)) { + return true; + } + if (startLocation != null && startLocation.length >= 2 + && (startLocation[0] != 0f || startLocation[1] != 0f)) { + out[0] = startLocation[0]; + out[1] = startLocation[1]; + return true; + } + if (latestGnssLatLng != null) { + out[0] = latestGnssLatLng.latitude; + out[1] = latestGnssLatLng.longitude; + return true; + } + if (latestWifiLatLng != null) { + out[0] = latestWifiLatLng.latitude; + out[1] = latestWifiLatLng.longitude; + return true; + } + return false; + } + + public LatLng getDisplayOriginLatLng() { + double[] origin = new double[2]; + if (copyDisplayOriginLatLon(origin)) { + return new LatLng(origin[0], origin[1]); + } + return null; + } + + + // Exposes the map-matching floor delta once so the UI can consume it safely. + public synchronized int consumePendingFloorDelta() { + int delta = pendingFloorDelta; + pendingFloorDelta = 0; + return delta; + } + + private boolean copyXYInto(float[] source, @NonNull float[] out) { + if (source == null || out.length < 2 || !isValidXY(source)) { + return false; + } + out[0] = source[0]; + out[1] = source[1]; + return true; + } + + + // Lets the map UI override the active floor after a deliberate manual selection. + public synchronized void setCurrentFloorByMapMatching(int floor) { + int targetFloor = floor; + long nowMs = System.currentTimeMillis(); + boolean wifiFloorFresh = latestWifiObservedFloor != null + && latestWifiFixTimeMs > 0L + && (nowMs - latestWifiFixTimeMs) <= WIFI_FRESH_TIMEOUT_MS; + if (wifiFloorFresh && (this.particleFilter == null || !this.particleFilter.isInitialized())) { + targetFloor = latestWifiObservedFloor; + this.initialFloorHint = latestWifiObservedFloor; + } else if (!saveRecording && availableFloorPlans.size() <= 1) { + this.initialFloorHint = floor; + } else if (saveRecording && (this.particleFilter == null || !this.particleFilter.isInitialized())) { + // Avoid injecting UI default floor into initialization before WiFi/GNSS fixes stabilize. + targetFloor = this.currentFloor; + } + targetFloor = normalizeFloorToAvailable(targetFloor); + this.initialFloorHint = normalizeFloorToAvailable(this.initialFloorHint); + + this.currentFloorPlan = resolveFloorPlan(targetFloor); + this.pendingFloorDelta = 0; + this.lastFloorSwitchTimeMs = System.currentTimeMillis(); + this.confirmedFloorReferenceElevation = this.elevation; + this.confirmedFloorReferenceInitialized = true; + if (this.particleFilter == null || !this.particleFilter.isInitialized()) { + this.currentFloor = targetFloor; + } + } + // Returns the floor currently selected by the fusion and map-matching pipeline. + public int getCurrentFloorByMapMatching() { + return currentFloor; + } + /** * Get an estimate by the PDR class whether it estimates the user is currently taking an elevator. * @@ -784,6 +3756,41 @@ public int getHoldMode(){ //region Start/Stop + private void registerSensorListener(MovementSensor movementSensor, int samplingPeriodUs) { + registerSensorListener(movementSensor, samplingPeriodUs, motionSensorHandler); + } + + private void registerSensorListener(MovementSensor movementSensor, int samplingPeriodUs, Handler targetHandler) { + if (movementSensor == null || movementSensor.sensorManager == null || movementSensor.sensor == null) { + return; + } + ensureSensorCallbackThread(); + Handler callbackHandler = targetHandler != null ? targetHandler : motionSensorHandler; + movementSensor.sensorManager.registerListener(this, movementSensor.sensor, samplingPeriodUs, callbackHandler); + } + + private void registerSensorListener(MovementSensor movementSensor, int samplingPeriodUs, int maxLatencyUs) { + registerSensorListener(movementSensor, samplingPeriodUs, maxLatencyUs, motionSensorHandler); + } + + private void registerSensorListener(MovementSensor movementSensor, + int samplingPeriodUs, + int maxLatencyUs, + Handler targetHandler) { + if (movementSensor == null || movementSensor.sensorManager == null || movementSensor.sensor == null) { + return; + } + ensureSensorCallbackThread(); + Handler callbackHandler = targetHandler != null ? targetHandler : motionSensorHandler; + movementSensor.sensorManager.registerListener( + this, + movementSensor.sensor, + samplingPeriodUs, + maxLatencyUs, + callbackHandler + ); + } + /** * Registers all device listeners and enables updates with the specified sampling rate. * @@ -795,18 +3802,29 @@ public int getHoldMode(){ * @see GNSSDataProcessor handles location data. */ public void resumeListening() { - 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); - barometerSensor.sensorManager.registerListener(this, barometerSensor.sensor, (int) 1e6); - gyroscopeSensor.sensorManager.registerListener(this, gyroscopeSensor.sensor, 10000, (int) maxReportLatencyNs); - lightSensor.sensorManager.registerListener(this, lightSensor.sensor, (int) 1e6); - 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); - wifiProcessor.startListening(); - gnssProcessor.startLocationUpdates(); + ensureSensorCallbackThread(); + + // Keep motion-critical sensors on a dedicated high-priority looper so step callbacks do not + // sit behind the rest of the IMU traffic while the user is moving. + registerSensorListener(linearAccelerationSensor, 20000, (int) maxReportLatencyNs, motionSensorHandler); + registerSensorListener(gravitySensor, 20000, (int) maxReportLatencyNs, motionSensorHandler); + registerSensorListener(stepDetectionSensor, SensorManager.SENSOR_DELAY_NORMAL, motionSensorHandler); + registerSensorListener(rotationSensor, 20000, (int) maxReportLatencyNs, motionSensorHandler); + registerSensorListener(gameRotationSensor, 20000, (int) maxReportLatencyNs, motionSensorHandler); + + // Lower the rate of auxiliary sensors to reduce callback backlog and runtime jank. + registerSensorListener(accelerometerSensor, 20000, (int) maxReportLatencyNs, sensorCallbackHandler); + registerSensorListener(gyroscopeSensor, 20000, (int) maxReportLatencyNs, sensorCallbackHandler); + registerSensorListener(magnetometerSensor, 20000, (int) maxReportLatencyNs, sensorCallbackHandler); + registerSensorListener(barometerSensor, (int) 1e6, sensorCallbackHandler); + registerSensorListener(lightSensor, (int) 1e6, sensorCallbackHandler); + registerSensorListener(proximitySensor, (int) 1e6, sensorCallbackHandler); + if (wifiProcessor != null) { + wifiProcessor.startListening(); + } + if (gnssProcessor != null) { + gnssProcessor.startLocationUpdates(); + } } /** @@ -821,25 +3839,56 @@ public void resumeListening() { public void stopListening() { if(!saveRecording) { // Unregister sensor-manager based devices - accelerometerSensor.sensorManager.unregisterListener(this); - barometerSensor.sensorManager.unregisterListener(this); - gyroscopeSensor.sensorManager.unregisterListener(this); - lightSensor.sensorManager.unregisterListener(this); - proximitySensor.sensorManager.unregisterListener(this); - magnetometerSensor.sensorManager.unregisterListener(this); - stepDetectionSensor.sensorManager.unregisterListener(this); - rotationSensor.sensorManager.unregisterListener(this); - linearAccelerationSensor.sensorManager.unregisterListener(this); - gravitySensor.sensorManager.unregisterListener(this); + if (accelerometerSensor != null && accelerometerSensor.sensorManager != null) { + accelerometerSensor.sensorManager.unregisterListener(this); + } + if (barometerSensor != null && barometerSensor.sensorManager != null) { + barometerSensor.sensorManager.unregisterListener(this); + } + if (gyroscopeSensor != null && gyroscopeSensor.sensorManager != null) { + gyroscopeSensor.sensorManager.unregisterListener(this); + } + if (lightSensor != null && lightSensor.sensorManager != null) { + lightSensor.sensorManager.unregisterListener(this); + } + if (proximitySensor != null && proximitySensor.sensorManager != null) { + proximitySensor.sensorManager.unregisterListener(this); + } + if (magnetometerSensor != null && magnetometerSensor.sensorManager != null) { + magnetometerSensor.sensorManager.unregisterListener(this); + } + if (stepDetectionSensor != null && stepDetectionSensor.sensorManager != null) { + stepDetectionSensor.sensorManager.unregisterListener(this); + } + if (rotationSensor != null && rotationSensor.sensorManager != null) { + rotationSensor.sensorManager.unregisterListener(this); + } + if (gameRotationSensor != null && gameRotationSensor.sensorManager != null) { + gameRotationSensor.sensorManager.unregisterListener(this); + } + if (linearAccelerationSensor != null && linearAccelerationSensor.sensorManager != null) { + linearAccelerationSensor.sensorManager.unregisterListener(this); + } + if (gravitySensor != null && gravitySensor.sensorManager != null) { + gravitySensor.sensorManager.unregisterListener(this); + } //The app often crashes here because the scan receiver stops after it has found the list. // It will only unregister one if there is to unregister try { - this.wifiProcessor.stopListening(); //error here? + if (this.wifiProcessor != null) { + this.wifiProcessor.stopListening(); //error here? + } } catch (Exception e) { System.err.println("Wifi resumed before existing"); } + this.wifiPositionRequestInFlight = false; + this.pendingWifiPositionRequest = false; + this.lastWifiFingerprintSignatureTimeMs = 0L; + this.lastAppliedGnssMeasurementMs = 0L; // Stop receiving location updates - this.gnssProcessor.stopUpdating(); + if (this.gnssProcessor != null) { + this.gnssProcessor.stopUpdating(); + } } } @@ -853,36 +3902,117 @@ public void stopListening() { */ public void startRecording() { // If wakeLock is null (e.g. not initialized or was cleared), reinitialize it. - if (wakeLock == null) { + if (wakeLock == null && appContext != null) { PowerManager powerManager = (PowerManager) this.appContext.getSystemService(Context.POWER_SERVICE); - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag"); + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PositionMe::WakeLock"); + } + if (wakeLock != null && !wakeLock.isHeld()) { + wakeLock.acquire(31 * 60 * 1000L); } - wakeLock.acquire(31 * 60 * 1000L /*31 minutes*/); this.saveRecording = true; + this.wifiPositionRequestInFlight = false; + this.pendingWifiPositionRequest = false; + this.lastWifiPositionRequestMs = 0L; + this.lastWifiFingerprintSignatureTimeMs = 0L; + this.lastAppliedGnssMeasurementMs = 0L; this.stepCounter = 0; this.absoluteStartTime = System.currentTimeMillis(); this.bootTime = SystemClock.uptimeMillis(); - // Protobuf trajectory class for sending sensor data to restful API - this.trajectory = Traj.Trajectory.newBuilder() - .setAndroidVersion(Build.VERSION.RELEASE) - .setStartTimestamp(absoluteStartTime) - .setAccelerometerInfo(createInfoBuilder(accelerometerSensor)) - .setGyroscopeInfo(createInfoBuilder(gyroscopeSensor)) - .setMagnetometerInfo(createInfoBuilder(magnetometerSensor)) - .setBarometerInfo(createInfoBuilder(barometerSensor)) - .setLightSensorInfo(createInfoBuilder(lightSensor)); - + this.startTimestampMs = this.absoluteStartTime; + long nowMs = System.currentTimeMillis(); + if (this.latestWifiObservedFloor != null + && this.latestWifiFixTimeMs > 0L + && (nowMs - this.latestWifiFixTimeMs) <= WIFI_FRESH_TIMEOUT_MS) { + this.initialFloorHint = normalizeFloorToAvailable(this.latestWifiObservedFloor); + } else { + this.initialFloorHint = normalizeFloorToAvailable(this.currentFloor); + } + // Reset PDR and PF state for the new recording session. + this.latestRawPdrXY = new float[]{0f, 0f}; + this.pdrAlignmentOffsetXY = new float[]{0f, 0f}; + this.latestPdrXY = new float[]{0f, 0f}; + this.latestPfXY = null; + this.latestFusedXY = new float[]{0f, 0f}; + invalidateHeadingState(); + this.lastFusedUpdateTimeMs = 0L; + this.lastAbsoluteMeasurementTimeMs = 0L; + this.pendingFloorDelta = 0; + this.currentFloor = normalizeFloorToAvailable(initialFloorHint); + this.currentFloorPlan = resolveFloorPlan(currentFloor); + this.lastFloorSwitchTimeMs = 0L; + this.confirmedFloorReferenceElevation = 0f; + this.confirmedFloorReferenceInitialized = false; + this.wifiAnchorEstablished = false; + resetStationaryDetectionState(); + if (this.particleFilter != null) { + this.particleFilter.reset(); + } + // Reset counters + this.counter = 0; + this.secondCounter = 0; - this.storeTrajectoryTimer = new Timer(); - this.storeTrajectoryTimer.schedule(new storeDataInTrajectory(), 0, TIME_CONST); + clearPathViewOnMainThread(); this.pdrProcessing.resetPDR(); - if(settings.getBoolean("overwrite_constants", false)) { - this.filter_coefficient = Float.parseFloat(settings.getString("accel_filter", "0.96")); - } else { - this.filter_coefficient = FILTER_COEFFICIENT; + applyWifiDominantStartAnchorIfAvailable(); + this.lastAcceptedWifiXY = null; + this.lastAcceptedWifiFixTimeMs = 0L; + this.consecutiveRejectedWifiUpdates = 0; + resetRejectedWifiCluster(); + synchronized (this) { + this.testPoints.clear(); + } + + // Initialize Traj Builder + this.trajectory = Traj.Trajectory.newBuilder(); + + // Attach Add-Tag test points to protobuf + syncTestPointsIntoTrajectory(); + // Set Metadata + String currentTrajectoryIdtrajectoryId = "Traj_" + absoluteStartTime; + this.trajectory.setTrajectoryId(currentTrajectoryIdtrajectoryId); + this.trajectory.setAndroidVersion(Build.VERSION.RELEASE); + this.trajectory.setStartTimestamp(absoluteStartTime); + // Seed PDR with non-default values so it is serialized in proto3 (server needs a valid start time) + this.trajectory.addPdrData( + Traj.RelativePosition.newBuilder() + .setRelativeTimestamp(1) // must be non-zero, otherwise it can be omitted in serialization + .setX(1e-4f) // tiny epsilon to avoid default 0 being omitted/filtered + .setY(0f) + .build() + ); + + // Initialize last known coordinates so 1Hz PDR logging can run even before the first step event + lastCords = new float[]{0f, 0f}; + lastDisplayTrajectoryUpdateMs = 0L; + ensureParticleFilterInitialized(); + syncFusedEstimateFromParticleFilter(System.currentTimeMillis()); + + + if (accelerometerSensor != null) this.trajectory.setAccelerometerInfo(createSensorInfo(accelerometerSensor)); + if (gyroscopeSensor != null) this.trajectory.setGyroscopeInfo(createSensorInfo(gyroscopeSensor)); + if (rotationSensor != null) this.trajectory.setRotationVectorInfo(createSensorInfo(rotationSensor)); + if (magnetometerSensor != null) this.trajectory.setMagnetometerInfo(createSensorInfo(magnetometerSensor)); + if (barometerSensor != null) this.trajectory.setBarometerInfo(createSensorInfo(barometerSensor)); + if (lightSensor != null) this.trajectory.setLightSensorInfo(createSensorInfo(lightSensor)); + + // 4. Set Initial Position (Assignment 1 Requirement) + if (startLocation != null && (startLocation[0] != 0 || startLocation[1] != 0)) { + this.trajectory.setInitialPosition(Traj.GNSSPosition.newBuilder() + .setLatitude(startLocation[0]) + .setLongitude(startLocation[1]) + .build()); + } + + // 5. Schedule Data Recording Task + if (storeTrajectoryTimer != null) { + storeTrajectoryTimer.cancel(); } + this.storeTrajectoryTimer = new Timer(); + this.storeTrajectoryTimer.scheduleAtFixedRate(new storeDataInTrajectory(), 0, TIME_CONST); + + this.filter_coefficient = resolveConfiguredFilterCoefficient(); } /** @@ -895,16 +4025,135 @@ public void startRecording() { * @see SettingsFragment navigation that might cancel recording. */ public void stopRecording() { - // Only cancel if we are running - if(this.saveRecording) { + if (saveRecording) { + if (storeTrajectoryTimer != null) { + storeTrajectoryTimer.cancel(); + storeTrajectoryTimer = null; + } + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } this.saveRecording = false; - storeTrajectoryTimer.cancel(); + this.wifiPositionRequestInFlight = false; + this.pendingWifiPositionRequest = false; + this.lastAppliedGnssMeasurementMs = 0L; + + if (appContext != null && trajectory != null) { + try { + // Ensure startTimestamp is non-zero before building/uploading (server validates duration using it) + if (this.trajectory.getStartTimestamp() == 0L) { + this.trajectory.setStartTimestamp(absoluteStartTime); + } + + syncTestPointsIntoTrajectory(); + if (pendingRecordingName != null && !pendingRecordingName.trim().isEmpty()) { + this.trajectory.setTrajectoryId(pendingRecordingName.trim()); + } + Traj.Trajectory finalTrajectory = this.trajectory.build(); + Log.i("SensorFusion", "Finalizing trajectory with test_points=" + finalTrajectory.getTestPointsCount()); + + if (serverCommunications != null) { + final double minLocalUploadDurationSec = 5.0; + final double minServerUploadDurationSec = 30.0; + boolean shouldUploadToServer = true; + try { + if (finalTrajectory.getImuDataCount() > 1) { + long t0 = finalTrajectory.getImuData(0).getRelativeTimestamp(); + long t1 = finalTrajectory.getImuData(finalTrajectory.getImuDataCount() - 1).getRelativeTimestamp(); + double durationSec = (t1 - t0) / 1000.0; + + if (durationSec < minLocalUploadDurationSec) { + Log.w("SensorFusion", "Upload skipped: recording is shorter than " + + minLocalUploadDurationSec + " seconds."); + + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(appContext, + "Recording too short (" + String.format(java.util.Locale.US, "%.1f", durationSec) + + "s). Please record at least " + + String.format(java.util.Locale.US, "%.0f", minLocalUploadDurationSec) + + "s before uploading.", + Toast.LENGTH_LONG).show() + ); + this.trajectory = null; // clear stale trajectory so it cannot be re-uploaded + return; // Stop here: do not upload or save + } + + if (durationSec < minServerUploadDurationSec) { + shouldUploadToServer = false; + Log.w("SensorFusion", "Server upload skipped: recording is shorter than " + + minServerUploadDurationSec + " seconds."); + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(appContext, + "Recording saved locally. Server upload requires at least " + + String.format(java.util.Locale.US, "%.0f", minServerUploadDurationSec) + + "s of data.", + Toast.LENGTH_LONG).show() + ); + } + } + } catch (Exception e) { + Log.w("SensorFusion", "IMU duration check failed", e); + } + + if (shouldUploadToServer) { + String selectedCampaign = VenueSelectionHelper.getSelectedCampaign(appContext); + Log.d("SensorFusion", "Uploading trajectory to campaign=" + selectedCampaign); + serverCommunications.sendTrajectory(finalTrajectory, selectedCampaign); + } + } + String fileName = buildRecordingFileName(absoluteStartTime); + + + File file = new File(appContext.getExternalFilesDir(null), fileName); + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(finalTrajectory.toByteArray()); + fileOutputStream.close(); + + Log.d("SensorFusion", "Trajectory saved: " + file.getAbsolutePath()); + } catch (Exception e) { + Log.e("SensorFusion", "Error saving trajectory", e); + } finally { + pendingRecordingName = ""; + } + } + } + + } + + public void setPendingRecordingName(String recordingName) { + if (recordingName == null) { + pendingRecordingName = ""; + } else { + pendingRecordingName = recordingName.trim(); } - if(wakeLock.isHeld()) { - this.wakeLock.release(); + if (trajectory != null && pendingRecordingName != null && !pendingRecordingName.isEmpty()) { + trajectory.setTrajectoryId(pendingRecordingName); + } + } + + private String buildRecordingFileName(long startTimeMs) { + String safeName = sanitizeRecordingNameForFile(pendingRecordingName); + if (safeName.isEmpty()) { + return "term_project_trajectory_" + startTimeMs + ".proto"; } + return "term_project_trajectory_" + safeName + "_" + startTimeMs + ".proto"; } + private String sanitizeRecordingNameForFile(String rawName) { + if (rawName == null) { + return ""; + } + String trimmed = rawName.trim(); + if (trimmed.isEmpty()) { + return ""; + } + String sanitized = Pattern.compile("[^\\p{L}\\p{N}._-]+") + .matcher(trimmed) + .replaceAll("_"); + sanitized = sanitized.replaceAll("_+", "_"); + sanitized = sanitized.replaceAll("^[_ .-]+|[_ .-]+$", ""); + return sanitized; + } //endregion //region Trajectory object @@ -915,14 +4164,12 @@ public void stopRecording() { * @see ServerCommunications for sending and receiving data via HTTPS. */ public void sendTrajectoryToCloud() { - // Build object - Traj.Trajectory sentTrajectory = trajectory.build(); - // Pass object to communications object - this.serverCommunications.sendTrajectory(sentTrajectory); + // Upload is handled in stopRecording() to avoid duplicate / inconsistent uploads. + return; } /** - * 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,14 +4177,15 @@ 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 createSensorInfo(MovementSensor sensor) { + return Traj.SensorInfo.newBuilder() .setName(sensor.sensorInfo.getName()) .setVendor(sensor.sensorInfo.getVendor()) .setResolution(sensor.sensorInfo.getResolution()) .setPower(sensor.sensorInfo.getPower()) .setVersion(sensor.sensorInfo.getVersion()) - .setType(sensor.sensorInfo.getType()); + .setType(sensor.sensorInfo.getType()) + .build(); } /** @@ -947,65 +4195,204 @@ private Traj.Sensor_Info.Builder createInfoBuilder(MovementSensor sensor) { * destroyed in {@link SensorFusion#stopRecording()}. */ private class storeDataInTrajectory extends TimerTask { + @Override public void run() { - // 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]) - .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)) - ; - - // Divide timer with a counter for storing data every 1 second - if (counter == 99) { + if (!saveRecording || trajectory == null) return; + + // relative_timestamp is milliseconds from start_timestamp + long relativeTime = System.currentTimeMillis() - absoluteStartTime; + + // Combine Acc, Gyro, Rotation, Steps into one IMUReading message + Traj.IMUReading.Builder imuBuilder = Traj.IMUReading.newBuilder() + .setStepCount(stepCounter); + imuBuilder.setRelativeTimestamp(relativeTime); + + + if (accelerometerSensor != null && accelerometerSensor.values != null) { + imuBuilder.setAcc(Traj.Vector3.newBuilder() + .setX(accelerometerSensor.values[0]) + .setY(accelerometerSensor.values[1]) + .setZ(accelerometerSensor.values[2]).build()); + } + + if (gyroscopeSensor != null && gyroscopeSensor.values != null) { + imuBuilder.setGyr(Traj.Vector3.newBuilder() + .setX(gyroscopeSensor.values[0]) + .setY(gyroscopeSensor.values[1]) + .setZ(gyroscopeSensor.values[2]).build()); + } + + // Handle Rotation Vector (x,y,z,w) + if (rotation != null && rotation.length >= 3) { + + float x = rotation[0]; + float y = rotation[1]; + float z = rotation[2]; + float w = (rotation.length > 3) ? rotation[3] : 1.0f; + + // Normalize quaternion to satisfy server tolerance (norm ~ 1) + double norm = Math.sqrt((double) x * x + (double) y * y + (double) z * z + (double) w * w); + if (norm > 1e-9) { + x /= norm; + y /= norm; + z /= norm; + w /= norm; + + imuBuilder.setRotationVector( + Traj.Quaternion.newBuilder() + .setX(x) + .setY(y) + .setZ(z) + .setW(w) + .build() + ); + } + } + + + // Add IMU reading to trajectory + synchronized (trajectory) { + trajectory.addImuData(imuBuilder.build()); + } + + // Magnetometer Data + if (magnetometerSensor != null && magnetometerSensor.values != null) { + trajectory.addMagnetometerData(Traj.MagnetometerReading.newBuilder() + .setRelativeTimestamp(relativeTime) // timestamp for server duration + .setMag(Traj.Vector3.newBuilder() + .setX(magnetometerSensor.values[0]) + .setY(magnetometerSensor.values[1]) + .setZ(magnetometerSensor.values[2]).build()) + .build()); + } + + // Low Frequency Data (1Hz or slower) + if (counter >= 100) { // 100 * 10ms = 1000ms = 1s 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() - .setLight(light) - .setRelativeTimestamp(SystemClock.uptimeMillis() - bootTime) - .build()); + // Record a 1Hz position point into pdr_data (write every second to guarantee >=30s duration) + if (saveRecording && trajectory != null) { + + float x = 0f; + float y = 0f; + + // Prefer the latest coordinates produced by step-based PDR + if (lastCords != null && lastCords.length >= 2) { + x = lastCords[0]; + y = lastCords[1]; + } else { + // Fallback: ask PDR module for its current movement estimate + try { + if (pdrProcessing != null) { + float[] pdrMove = pdrProcessing.getPDRMovement(); + if (pdrMove != null && pdrMove.length >= 2) { + x = pdrMove[0]; + y = pdrMove[1]; + } + } + } catch (Exception e) { + Log.w("SensorFusion", "Failed to read PDR movement", e); + } + } + + // Use monotonic time since start (consistent with IMU relative timestamps) + long pdrTime = SystemClock.uptimeMillis() - bootTime; + + // Avoid an all-default point (some servers may ignore/filter it) + if (pdrTime == 0) pdrTime = 1; + if (x == 0f && y == 0f) x = 1e-4f; + + Traj.RelativePosition pdrPoint = Traj.RelativePosition.newBuilder() + .setRelativeTimestamp(pdrTime) + .setX(x) + .setY(y) + .build(); + + int pdrCount; + synchronized (trajectory) { + trajectory.addPdrData(pdrPoint); + pdrCount = trajectory.getPdrDataCount(); + } + } - // Divide the timer for storing AP data every 5 seconds - if (secondCounter == 4) { - secondCounter = 0; - //Current Wifi Object - Wifi currentWifi = wifiProcessor.getCurrentWifiData(); - trajectory.addApsData(Traj.AP_Data.newBuilder() - .setMac(currentWifi.getBssid()) - .setSsid(currentWifi.getSsid()) - .setFrequency(currentWifi.getFrequency())); + + + // Record Barometric Pressure + if (barometerSensor != null && barometerSensor.values != null) { + trajectory.addPressureData(Traj.BarometerReading.newBuilder() + .setRelativeTimestamp(relativeTime) // timestamp for server duration + .setPressure(barometerSensor.values[0]) + .build()); } - else { + + // Record Ambient Light + if (lightSensor != null && lightSensor.values != null) { + trajectory.addLightData(Traj.LightReading.newBuilder() + .setRelativeTimestamp(relativeTime) // timestamp for server duration + .setLight(lightSensor.values[0]) + .build()); + } + + // Record GNSS (Every 1s) + if (latitude != 0 && longitude != 0) { + trajectory.addGnssData(Traj.GNSSReading.newBuilder() + .setPosition(Traj.GNSSPosition.newBuilder() + .setRelativeTimestamp(relativeTime) // timestamp for server duration alignment + .setLatitude(latitude) + .setLongitude(longitude) + .setAltitude(elevation) + .build()) + .build()); + + } + + // Sub-cycle for WiFi AP Data (Every 5s approx) + if (secondCounter >= 4) { // 5 * 1s = 5s + secondCounter = 0; + + if (wifiList != null && !wifiList.isEmpty()) { + + // Build Fingerprint (wifi_fingerprints type in Traj) + Traj.Fingerprint.Builder fpBuilder = Traj.Fingerprint.newBuilder() + .setRelativeTimestamp(relativeTime); + + synchronized (trajectory) { + for (Wifi wifi : wifiList) { + + // Keep existing aps_data + Traj.WiFiAPData.Builder wifiBuilder = Traj.WiFiAPData.newBuilder() + .setMac(wifi.getBssid()) + .setSsid(wifi.getSsid()) + .setFrequency(wifi.getFrequency()); + if (wifi.getRttFlag()) { + wifiBuilder.setRttEnabled(true); + } + trajectory.addApsData(wifiBuilder.build()); + + // Write rf_scans -> wifi_fingerprints + Traj.RFScan.Builder scanBuilder = Traj.RFScan.newBuilder() + .setRelativeTimestamp(relativeTime) + .setMac(wifi.getBssid()) + .setRssi(wifi.getLevel()); + fpBuilder.addRfScans(scanBuilder.build()); + } + + if (fpBuilder.getRfScansCount() > 0) { + trajectory.addWifiFingerprints(fpBuilder.build()); + } + + } + + } else { + } + + } else { secondCounter++; } - } - else { + + } else { counter++; } - } } 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..44eab89e 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/WiFiPositioning.java @@ -28,10 +28,25 @@ * @author Arun Gopalakrishnan */ public class WiFiPositioning { + private static final String FINE_POSITIONING_URL = "https://openpositioning.org/api/position/fine"; + + public enum PositioningMode { + FINE(FINE_POSITIONING_URL), + COARSE(FINE_POSITIONING_URL); + + private final String endpointUrl; + + PositioningMode(String endpointUrl) { + this.endpointUrl = endpointUrl; + } + + public String getEndpointUrl() { + return endpointUrl; + } + } + // Queue for storing the POST requests made private RequestQueue requestQueue; - // URL for WiFi positioning API - private static final String url="https://openpositioning.org/api/position/fine"; /** * Getter for the WiFi positioning coordinates obtained using openpositioning API @@ -81,9 +96,15 @@ public WiFiPositioning(Context context){ * @param jsonWifiFeatures WiFi Fingerprint from device */ public void request(JSONObject jsonWifiFeatures) { + request(jsonWifiFeatures, PositioningMode.FINE); + } + + public void request(JSONObject jsonWifiFeatures, PositioningMode positioningMode) { // Creating the POST request using WiFi fingerprint (a JSON object) JsonObjectRequest jsonObjectRequest = new JsonObjectRequest( - Request.Method.POST, url, jsonWifiFeatures, + Request.Method.POST, + resolveEndpoint(positioningMode), + jsonWifiFeatures, // Parses the response to obtain the WiFi location and WiFi floor response -> { try { @@ -132,9 +153,17 @@ public void request(JSONObject jsonWifiFeatures) { * @param callback callback function to allow user to use location when ready */ public void request( JSONObject jsonWifiFeatures, final VolleyCallback callback) { + request(jsonWifiFeatures, PositioningMode.FINE, callback); + } + + public void request(JSONObject jsonWifiFeatures, + PositioningMode positioningMode, + final VolleyCallback callback) { // Creating the POST request using WiFi fingerprint (a JSON object) JsonObjectRequest jsonObjectRequest = new JsonObjectRequest( - Request.Method.POST, url, jsonWifiFeatures, + Request.Method.POST, + resolveEndpoint(positioningMode), + jsonWifiFeatures, response -> { try { Log.d("jsonObject",response.toString()); @@ -170,6 +199,10 @@ public void request( JSONObject jsonWifiFeatures, final VolleyCallback callback) requestQueue.add(jsonObjectRequest); } + private String resolveEndpoint(PositioningMode positioningMode) { + return FINE_POSITIONING_URL; + } + /** * Interface defined for the callback to access response obtained after POST request */ @@ -178,4 +211,4 @@ public interface VolleyCallback { void onError(String message); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/Wifi.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/Wifi.java index d2e981cb..f0c4e041 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/Wifi.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/Wifi.java @@ -18,6 +18,11 @@ public class Wifi { private int level; private long frequency; + // Add RTT support flag for IEEE 802.11mc capability + private boolean rttFlag; + + // Add unique identifier for scan traceability + private String uuid; /** * Empty public default constructor of the Wifi object. */ @@ -27,10 +32,17 @@ public Wifi(){} * Getters for each property */ public String getSsid() { return ssid; } - public long getBssid() { return bssid; } + public long getBssid() { return bssid; } public int getLevel() { return level; } public long getFrequency() { return frequency; } + // Getter for RTT flag + public boolean getRttFlag() { return rttFlag; } + + // Getter for unique ID + public String getUuid() { return uuid; } + + /** * Setters for each property */ @@ -39,6 +51,11 @@ public Wifi(){} public void setLevel(int level) { this.level = level; } public void setFrequency(long frequency) { this.frequency = frequency; } + // Setter for RTT flag + public void setRttFlag(boolean rttFlag) { this.rttFlag = rttFlag; } + + // Setter for unique ID + public void setUuid(String uuid) { this.uuid = uuid; } /** * Generates a string containing mac address and rssi of Wifi. * @@ -47,6 +64,13 @@ public Wifi(){} */ @Override public String toString() { - return "bssid: " + bssid +", level: " + level; + String macStr = String.format("%012X", bssid); + StringBuilder formattedMac = new StringBuilder(); + for (int i = 0; i < macStr.length(); i += 2) { + if (i > 0) formattedMac.append(":"); + formattedMac.append(macStr.substring(i, i + 2)); + } + + return "MAC: " + formattedMac.toString() + ", Level: " + level + "dBm, RTT: " + rttFlag; } } 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..3483a48b 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java +++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/WifiDataProcessor.java @@ -11,14 +11,20 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.provider.Settings; +import android.util.Log; import android.widget.Toast; import androidx.core.app.ActivityCompat; +import androidx.preference.PreferenceManager; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Timer; import java.util.TimerTask; +import java.util.UUID; + /** * The WifiDataProcessor class is the Wi-Fi data gathering and processing class of the application. * It implements the wifi scanning and broadcasting design to identify a list of nearby Wi-Fis as @@ -40,8 +46,11 @@ */ public class WifiDataProcessor implements Observable { - //Time over which a new scan will be initiated - private static final long scanInterval = 5000; + private static final long DEFAULT_SCAN_INTERVAL_MS = 5000L; + private static final long MIN_SCAN_INTERVAL_MS = 2500L; + private static final long MAX_SCAN_INTERVAL_MS = 30000L; + private static final long DUPLICATE_SCAN_SUPPRESSION_MS = 2000L; + private static final String TAG = "WifiDataProcessor"; // Application context for handling permissions and WifiManager instances private final Context context; @@ -56,6 +65,11 @@ public class WifiDataProcessor implements Observable { // Timer object private Timer scanWifiDataTimer; + private boolean receiverRegistered = false; + private final Object receiverLock = new Object(); + private long configuredScanIntervalMs = DEFAULT_SCAN_INTERVAL_MS; + private String lastPublishedScanSignature = ""; + private long lastPublishedScanTimeMs = 0L; /** * Public default constructor of the WifiDataProcessor class. @@ -79,6 +93,7 @@ public WifiDataProcessor(Context context) { this.wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); this.scanWifiDataTimer = new Timer(); this.observers = new ArrayList<>(); + this.configuredScanIntervalMs = resolveScanIntervalMs(); // Decreapted method after API 29 // Turn on wifi if it is currently disabled @@ -89,7 +104,12 @@ public WifiDataProcessor(Context context) { // Start wifi scan and return results via broadcast if(permissionsGranted) { - this.scanWifiDataTimer.schedule(new scheduledWifiScan(), 0, scanInterval); + registerReceiverIfNeeded(); + this.scanWifiDataTimer.scheduleAtFixedRate( + new scheduledWifiScan(), + 0, + configuredScanIntervalMs + ); } //Inform the user if wifi throttling is enabled on their device @@ -101,48 +121,22 @@ public WifiDataProcessor(Context context) { * Receives updates when a wifi scan is complete. Observers are notified when the broadcast is * received to update the list of wifis */ + BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() { - /** - * Updates the list of nearby wifis when the broadcast is received. - * Ensures wifi scans are not enabled if permissions are not granted. The list of wifis is - * then passed to store the Mac Address and strength and observers of the WifiDataProcessor - * class are notified of the updated wifi list. - * - * - * @param context Application Context to be used for permissions and device accesses. - * @param intent ???. - */ @Override public void onReceive(Context context, Intent intent) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - // Unregister this listener - stopListening(); return; } - //Collect the list of nearby wifis - List wifiScanList = wifiManager.getScanResults(); - //Stop receiver as scan is complete - context.unregisterReceiver(this); - - //Loop though each item in wifi list - wifiData = new Wifi[wifiScanList.size()]; - for(int i = 0; i < wifiScanList.size(); i++) { - wifiData[i] = new Wifi(); - //Convert String mac address to an integer - String wifiMacAddress = wifiScanList.get(i).BSSID; - long intMacAddress = convertBssidToLong(wifiMacAddress); - //store mac address and rssi of wifi - wifiData[i].setBssid(intMacAddress); - wifiData[i].setLevel(wifiScanList.get(i).level); + boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); + if (!success) { + Log.d(TAG, "SCAN_RESULTS_AVAILABLE received without updated flag; publishing latest cached scan."); } - - //Notify observers of change in wifiData variable - notifyObservers(0); + publishLatestScanResults(); } }; - /** * Converts mac address from string to integer. * Removes semicolons from mac address and converts each hex byte to a hex integer. @@ -153,30 +147,126 @@ public void onReceive(Context context, Intent intent) { * @return Long variable with decimal conversion of the mac address */ private long convertBssidToLong(String wifiMacAddress){ - long intMacAddress =0; - int colonCount =5; - //Loop through each character - for(int j =0; j<17; j++){ - //Identify character - char macByte = wifiMacAddress.charAt(j); - //convert string hex mac address with colons to decimal long integer - if(macByte != ':'){ - //For characters 0-9 subtract 48 from ASCII code and multiply by 16^position - if((int) macByte >= 48 && (int) macByte <= 57){ - intMacAddress = intMacAddress + (((int)macByte-48)*((long)Math.pow(16,16-j-colonCount))); + if (wifiMacAddress == null || wifiMacAddress.isEmpty()) return 0; + try { + String hex = wifiMacAddress.replace(":", ""); + return Long.parseLong(hex, 16); + } catch (NumberFormatException e) { + return 0; + } + } + + private void publishLatestScanResults() { + if (!checkWifiPermissions()) { + return; + } + + List wifiScanList; + try { + wifiScanList = wifiManager.getScanResults(); + } catch (SecurityException e) { + Log.w(TAG, "No permission to access scan results.", e); + return; + } catch (RuntimeException e) { + Log.w(TAG, "Failed to read Wi-Fi scan results.", e); + return; + } + + Map uniqueWifiMap = new HashMap<>(); + if (wifiScanList != null) { + for (ScanResult result : wifiScanList) { + if (result == null) { + continue; + } + long bssidLong = convertBssidToLong(result.BSSID); + if (!uniqueWifiMap.containsKey(bssidLong)) { + Wifi wifi = new Wifi(); + wifi.setSsid(result.SSID); + wifi.setBssid(bssidLong); + wifi.setLevel(result.level); + wifi.setFrequency(result.frequency); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + wifi.setRttFlag(result.is80211mcResponder()); + } else { + wifi.setRttFlag(false); + } + wifi.setUuid(UUID.randomUUID().toString()); + uniqueWifiMap.put(bssidLong, wifi); } + } + } - //For characters a-f subtract 87 (=97-10) from ASCII code and multiply by 16^index - else if ((int) macByte >= 97 && (int) macByte <= 102){ - intMacAddress = intMacAddress + (((int)macByte-87)*((long)Math.pow(16,16-j-colonCount))); + Wifi currentConnectedWifi = getCurrentWifiData(); + if (currentConnectedWifi.getBssid() != 0) { + long currentBssid = currentConnectedWifi.getBssid(); + if (!uniqueWifiMap.containsKey(currentBssid)) { + if (currentConnectedWifi.getUuid() == null) { + currentConnectedWifi.setUuid(UUID.randomUUID().toString()); } + uniqueWifiMap.put(currentBssid, currentConnectedWifi); + } + } + + List orderedWifi = new ArrayList<>(uniqueWifiMap.values()); + orderedWifi.sort((left, right) -> { + int levelCompare = Integer.compare(right.getLevel(), left.getLevel()); + if (levelCompare != 0) { + return levelCompare; + } + return Long.compare(right.getFrequency(), left.getFrequency()); + }); + long nowMs = System.currentTimeMillis(); + String scanSignature = buildScanSignature(orderedWifi); + if (!scanSignature.isEmpty() + && scanSignature.equals(lastPublishedScanSignature) + && (nowMs - lastPublishedScanTimeMs) < DUPLICATE_SCAN_SUPPRESSION_MS) { + return; + } + + lastPublishedScanSignature = scanSignature; + lastPublishedScanTimeMs = nowMs; + wifiData = orderedWifi.toArray(new Wifi[0]); + notifyObservers(0); + } + + private String buildScanSignature(List orderedWifi) { + if (orderedWifi == null || orderedWifi.isEmpty()) { + return ""; + } + StringBuilder signature = new StringBuilder(orderedWifi.size() * 24); + for (Wifi wifi : orderedWifi) { + if (wifi == null) { + continue; } - else - //coloncount is used to obtain the index of each character - colonCount --; + signature.append(wifi.getBssid()) + .append(':') + .append(wifi.getLevel()) + .append(':') + .append(wifi.getFrequency()) + .append(';'); } + return signature.toString(); + } - return intMacAddress; + private long resolveScanIntervalMs() { + long intervalMs = DEFAULT_SCAN_INTERVAL_MS; + try { + boolean overwriteConstants = PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean("overwrite_constants", false); + if (overwriteConstants) { + String value = PreferenceManager + .getDefaultSharedPreferences(context) + .getString("wifi_interval", "5"); + long seconds = Long.parseLong(value); + intervalMs = seconds * 1000L; + } + } catch (RuntimeException e) { + Log.w(TAG, "Invalid Wi-Fi scan interval preference. Falling back to default.", e); + intervalMs = DEFAULT_SCAN_INTERVAL_MS; + } + return Math.max(MIN_SCAN_INTERVAL_MS, Math.min(MAX_SCAN_INTERVAL_MS, intervalMs)); } /** @@ -211,12 +301,18 @@ private boolean checkWifiPermissions() { private void startWifiScan() { //Check settings for wifi permissions if(checkWifiPermissions()) { - //if(sharedPreferences.getBoolean("wifi", false)) { - //Register broadcast receiver for wifi scans - context.registerReceiver(wifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); - wifiManager.startScan(); - - //} + try { + boolean started = wifiManager.startScan(); + if (!started) { + // startScan can be throttled; still publish the latest known scan list. + Log.d(TAG, "Wi-Fi scan request throttled; using latest cached scan results."); + publishLatestScanResults(); + } + } catch (SecurityException e) { + Log.w(TAG, "Wi-Fi scan start failed due to permission.", e); + } catch (RuntimeException e) { + Log.w(TAG, "Wi-Fi scan start failed.", e); + } } } @@ -225,8 +321,21 @@ private void startWifiScan() { * The method declares a new timer instance to schedule a scan for nearby wifis every 5 seconds. */ public void startListening() { + if (!checkWifiPermissions()) { + return; + } + configuredScanIntervalMs = resolveScanIntervalMs(); + registerReceiverIfNeeded(); + if (this.scanWifiDataTimer != null) { + this.scanWifiDataTimer.cancel(); + } this.scanWifiDataTimer = new Timer(); - this.scanWifiDataTimer.scheduleAtFixedRate(new scheduledWifiScan(), 0, scanInterval); + this.scanWifiDataTimer.scheduleAtFixedRate( + new scheduledWifiScan(), + 0, + configuredScanIntervalMs + ); + publishLatestScanResults(); } /** @@ -235,8 +344,43 @@ public void startListening() { * timer so that new scans are not initiated. */ public void stopListening() { - context.unregisterReceiver(wifiScanReceiver); - this.scanWifiDataTimer.cancel(); + unregisterReceiverIfNeeded(); + if (this.scanWifiDataTimer != null) { + this.scanWifiDataTimer.cancel(); + this.scanWifiDataTimer = null; + } + } + + private void registerReceiverIfNeeded() { + synchronized (receiverLock) { + if (receiverRegistered) { + return; + } + try { + context.registerReceiver( + wifiScanReceiver, + new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + ); + receiverRegistered = true; + } catch (RuntimeException e) { + Log.w(TAG, "Failed to register Wi-Fi scan receiver.", e); + } + } + } + + private void unregisterReceiverIfNeeded() { + synchronized (receiverLock) { + if (!receiverRegistered) { + return; + } + try { + context.unregisterReceiver(wifiScanReceiver); + } catch (IllegalArgumentException e) { + // Ignore stale unregister requests. + } finally { + receiverRegistered = false; + } + } } /** @@ -313,13 +457,17 @@ public Wifi getCurrentWifiData(){ //Only obtain wifi data if the device is connected //Wifi in which the device is currently connected to Wifi currentWifi = new Wifi(); - if(networkInfo.isConnected()) { + if(networkInfo != null && networkInfo.isConnected()) { //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); currentWifi.setFrequency(wifiManager.getConnectionInfo().getFrequency()); + currentWifi.setLevel(wifiManager.getConnectionInfo().getRssi()); + } else{ //Store standard information if not connected diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/BuildingMapController.java b/app/src/main/java/com/openpositioning/PositionMe/utils/BuildingMapController.java new file mode 100644 index 00000000..0e0a9036 --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/BuildingMapController.java @@ -0,0 +1,457 @@ +package com.openpositioning.PositionMe.utils; + +import android.content.Context; +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.LatLng; +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.data.remote.Building; +import com.openpositioning.PositionMe.data.remote.FloorPlan; +import com.openpositioning.PositionMe.data.remote.ServerCommunications; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper class to handle indoor map logic. + * + * New code guide: + * 1. Nearby-building download and outline rendering. + * 2. Building selection persistence and floor normalization. + * 3. Debounced floor redraws for walls, stairs, and lifts. + * 4. Floor-plan export for SensorFusion map matching. + */ +public class BuildingMapController { + + private static final String TAG = "BuildingController"; + private static final boolean DRAW_DEBUG_LOGS = false; + + private final Context context; + private final GoogleMap gMap; + private final ServerCommunications serverCommunications; + // Posts floor rendering work to the main thread. + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private Runnable pendingFloorRenderTask; + private int floorRenderSeq = 0; + private static final long FLOOR_RENDER_DEBOUNCE_MS = 80L; + + private Building selectedBuilding; + private int currentFloorIndex = 0; + // Tracks active floor overlays and loaded building outlines. + private final List currentFloorLines = new ArrayList<>(); + private final List currentFloorPolygons = new ArrayList<>(); + private final Set loadedBuildingNames = new HashSet<>(); + + public interface BuildingSelectionListener { + void onBuildingSelected(String buildingName, String floorCode); + } + + private BuildingSelectionListener selectionListener; + + public BuildingMapController(Context context, GoogleMap map) { + this.context = context; + this.gMap = map; + this.serverCommunications = new ServerCommunications(context); + } + + public void setSelectionListener(BuildingSelectionListener listener) { + this.selectionListener = listener; + } + + // Loads nearby buildings, draws their outlines, and auto-selects the main venue. + public void downloadNearbyBuildings(LatLng center) { + if (center == null) return; + Log.d(TAG, "Downloading buildings near: " + center); + + serverCommunications.getNearbyBuildings(center.latitude, center.longitude, new ServerCommunications.BuildingCallback() { + @Override + public void onBuildingsReceived(List buildings) { + // Keep all map operations on the main thread. + mainHandler.post(() -> { + if (gMap == null) return; + if (buildings == null || buildings.isEmpty()) { + Log.d(TAG, "No buildings found nearby."); + return; + } + + Building targetBuilding = buildings.get(0); + for (Building b : buildings) { + String name = (b.getName() != null) ? b.getName() : "Unknown"; + if (!loadedBuildingNames.contains(name)) { + loadedBuildingNames.add(name); + drawSingleBuildingOutline(b); + } + if (name.toLowerCase().contains("nucleus")) { + targetBuilding = b; + } + } + onBuildingSelected(targetBuilding); + }); + } + + @Override + public void onError(String message) { + Log.e(TAG, "Error: " + message); + } + }); + } + + // Draws only the outer building footprint used for venue selection. + private void drawSingleBuildingOutline(Building building) { + if (building == null || building.getOutline() == null || building.getOutline().isEmpty()) return; + + List points = new ArrayList<>(); + for (List point : building.getOutline()) { + if (point != null && point.size() >= 2) { + points.add(new LatLng(point.get(0), point.get(1))); + } + } + if (points.isEmpty()) return; + + int strokeColor = Color.RED; + int fillColor = Color.argb(50, 255, 0, 0); + String name = (building.getName() != null) ? building.getName().toLowerCase() : ""; + + if (name.contains("nucleus")) { + strokeColor = Color.rgb(255, 191, 0); + fillColor = Color.argb(50, 255, 191, 0); + } else if (name.contains("fleeming") || name.contains("jenkin")) { + strokeColor = Color.BLUE; + fillColor = Color.argb(50, 0, 0, 255); + } + + Polygon polygon = gMap.addPolygon(new PolygonOptions() + .addAll(points) + .strokeColor(strokeColor) + .fillColor(fillColor) + .strokeWidth(5f) + .clickable(true)); + polygon.setTag(building); + } + + public void onPolygonClick(Polygon polygon) { + if (polygon == null) return; + Object tag = polygon.getTag(); + if (tag instanceof Building) { + onBuildingSelected((Building) tag); + } + } + + private void onBuildingSelected(Building building) { + if (building == null) return; + this.selectedBuilding = building; + VenueSelectionHelper.persistSelectedBuilding(context, building.getName()); + Toast.makeText(context, "Selected: " + building.getName(), Toast.LENGTH_SHORT).show(); + + if (building.getFloors() != null && !building.getFloors().isEmpty()) { + Collections.sort(building.getFloors(), new Comparator() { + @Override + public int compare(FloorPlan f1, FloorPlan f2) { + return Integer.compare(parseFloorCode(f1.getFloorCode()), parseFloorCode(f2.getFloorCode())); + } + }); + } + + this.currentFloorIndex = 0; + if (building.getFloors() != null) { + for (int i = 0; i < building.getFloors().size(); i++) { + if (parseFloorCode(building.getFloors().get(i).getFloorCode()) == 0) { + this.currentFloorIndex = i; + break; + } + } + } + + notifyCurrentFloorSelection(); + scheduleFloorRender(); + } + + // Normalizes mixed API floor labels into one integer floor ordering. + private int parseFloorCode(String code) { + if (code == null) return 0; + String raw = code.toUpperCase().trim(); + + if (raw.equals("BF") || raw.equals("B")) return -1; + if (raw.equals("GF") || raw.equals("G") || raw.equals("0") || raw.contains("GROUND")) return 0; + if (raw.contains("BASEMENT")) return -1; + + if (raw.startsWith("B")) { + String digits = raw.replaceAll("[^0-9]", ""); + if (!digits.isEmpty()) { + try { + return -Integer.parseInt(digits); + } catch (Exception ignored) { + } + } + return -1; + } + + try { + Matcher matcher = Pattern.compile("-?\\d+").matcher(raw); + if (matcher.find()) { + return Integer.parseInt(matcher.group()); + } + } catch (Exception ignored) { + } + + return 0; + } + + public void changeFloor(int delta) { + if (selectedBuilding == null || selectedBuilding.getFloors() == null || selectedBuilding.getFloors().isEmpty()) { + return; + } + + int maxIndex = selectedBuilding.getFloors().size() - 1; + int newIndex = Math.max(0, Math.min(currentFloorIndex + delta, maxIndex)); + if (newIndex != currentFloorIndex) { + currentFloorIndex = newIndex; + notifyCurrentFloorSelection(); + scheduleFloorRender(); + } + } + // Coalesces repeated floor redraw requests. + private void scheduleFloorRender() { + floorRenderSeq++; + final int renderSeq = floorRenderSeq; + if (pendingFloorRenderTask != null) { + mainHandler.removeCallbacks(pendingFloorRenderTask); + } + pendingFloorRenderTask = () -> { + if (renderSeq != floorRenderSeq) return; + drawCurrentFloorWalls(); + }; + mainHandler.postDelayed(pendingFloorRenderTask, FLOOR_RENDER_DEBOUNCE_MS); + } + + public boolean setFloorByValue(int floorValue) { + if (selectedBuilding == null || selectedBuilding.getFloors() == null || selectedBuilding.getFloors().isEmpty()) { + return false; + } + + int bestIndex = -1; + int bestDistance = Integer.MAX_VALUE; + for (int i = 0; i < selectedBuilding.getFloors().size(); i++) { + FloorPlan floorPlan = selectedBuilding.getFloors().get(i); + if (floorPlan == null) { + continue; + } + int parsedValue = parseFloorCode(floorPlan.getFloorCode()); + if (parsedValue == floorValue) { + bestIndex = i; + break; + } + int distance = Math.abs(parsedValue - floorValue); + if (distance < bestDistance) { + bestDistance = distance; + bestIndex = i; + } + } + + if (bestIndex < 0 || bestIndex == currentFloorIndex) { + return false; + } + + currentFloorIndex = bestIndex; + notifyCurrentFloorSelection(); + scheduleFloorRender(); + return true; + } + + public int getCurrentFloorValue() { + if (selectedBuilding == null || selectedBuilding.getFloors() == null || selectedBuilding.getFloors().isEmpty()) { + return 0; + } + if (currentFloorIndex < 0) currentFloorIndex = 0; + if (currentFloorIndex >= selectedBuilding.getFloors().size()) { + currentFloorIndex = selectedBuilding.getFloors().size() - 1; + } + return parseFloorCode(selectedBuilding.getFloors().get(currentFloorIndex).getFloorCode()); + } + // Returns the currently displayed floor plan. + public FloorPlan getCurrentFloorPlan() { + if (selectedBuilding == null || selectedBuilding.getFloors() == null || selectedBuilding.getFloors().isEmpty()) { + return null; + } + if (currentFloorIndex < 0 || currentFloorIndex >= selectedBuilding.getFloors().size()) { + return null; + } + return selectedBuilding.getFloors().get(currentFloorIndex); + } + + public Map getSelectedFloorPlanMap() { + Map floorPlanMap = new LinkedHashMap<>(); + if (selectedBuilding == null || selectedBuilding.getFloors() == null) { + return floorPlanMap; + } + for (FloorPlan floor : selectedBuilding.getFloors()) { + if (floor == null) { + continue; + } + floorPlanMap.put(parseFloorCode(floor.getFloorCode()), floor); + } + return floorPlanMap; + } + + // Redraws the selected floor with separate styles for walls, stairs, and lifts. + private void drawCurrentFloorWalls() { + // Re-dispatch if the draw request arrives off the UI thread. + if (Looper.myLooper() != Looper.getMainLooper()) { + mainHandler.post(this::drawCurrentFloorWalls); + return; + } + if (gMap == null) { + return; + } + // Clear overlays from the previous floor before drawing the next one. + for (Polyline line : currentFloorLines) { + try { + line.remove(); + } catch (Exception ignored) { + } + } + currentFloorLines.clear(); + + for (Polygon poly : currentFloorPolygons) { + try { + poly.remove(); + } catch (Exception ignored) { + } + } + currentFloorPolygons.clear(); + + if (selectedBuilding == null || selectedBuilding.getFloors() == null || selectedBuilding.getFloors().isEmpty()) { + return; + } + + if (currentFloorIndex < 0) currentFloorIndex = 0; + if (currentFloorIndex >= selectedBuilding.getFloors().size()) { + currentFloorIndex = selectedBuilding.getFloors().size() - 1; + } + + FloorPlan floor = selectedBuilding.getFloors().get(currentFloorIndex); + notifyCurrentFloorSelection(); + + int wallCount = (floor.getWalls() != null) ? floor.getWalls().size() : 0; + int stairCount = (floor.getStairs() != null) ? floor.getStairs().size() : 0; + int liftCount = (floor.getLifts() != null) ? floor.getLifts().size() : 0; + if (DRAW_DEBUG_LOGS) { + Log.d("MapDrawDebug", "Prepare floor draw: " + floor.getFloorCode() + + " | walls: " + wallCount + + " | stairs: " + stairCount + + " | lifts: " + liftCount); + } + + if (floor.getWalls() != null) { + for (List> wallPath : floor.getWalls()) { + List points = toLatLngList(wallPath); + if (!points.isEmpty()) { + currentFloorLines.add(gMap.addPolyline(new PolylineOptions() + .addAll(points) + .color(Color.YELLOW) + .width(6f) + .zIndex(100f))); + } + } + } + + if (floor.getStairs() != null) { + int i = 0; + for (List> stairPath : floor.getStairs()) { + List points = toLatLngList(stairPath); + if (points.isEmpty()) continue; + if (DRAW_DEBUG_LOGS && i == 0) { + Log.d("MapDrawDebug", "First stair area points=" + points.size() + ", sample=" + points.get(0)); + } + i++; + if (points.size() >= 3) { + currentFloorPolygons.add(gMap.addPolygon(new PolygonOptions() + .addAll(points) + .strokeColor(Color.GREEN) + .strokeWidth(3f) + .fillColor(Color.argb(120, 0, 255, 0)) + .zIndex(105f))); + } else { + currentFloorLines.add(gMap.addPolyline(new PolylineOptions() + .addAll(points) + .color(Color.GREEN) + .width(20f) + .zIndex(105f))); + if (DRAW_DEBUG_LOGS) { + Log.d("MapDrawDebug", "Stair polygon has <3 points, using a fallback polyline."); + } + } + } + } + + if (floor.getLifts() != null) { + int i = 0; + for (List> liftPath : floor.getLifts()) { + List points = toLatLngList(liftPath); + if (points.isEmpty()) continue; + if (DRAW_DEBUG_LOGS && i == 0) { + Log.d("MapDrawDebug", "First lift area points=" + points.size() + ", sample=" + points.get(0)); + } + i++; + if (points.size() >= 3) { + currentFloorPolygons.add(gMap.addPolygon(new PolygonOptions() + .addAll(points) + .strokeColor(Color.BLUE) + .strokeWidth(3f) + .fillColor(Color.argb(120, 0, 0, 255)) + .zIndex(105f))); + } else { + currentFloorLines.add(gMap.addPolyline(new PolylineOptions() + .addAll(points) + .color(Color.BLUE) + .width(20f) + .zIndex(105f))); + if (DRAW_DEBUG_LOGS) { + Log.d("MapDrawDebug", "Lift polygon has <3 points, using a fallback polyline."); + } + } + } + } + } + // Reports the active building and floor to listeners. + private void notifyCurrentFloorSelection() { + if (selectionListener == null || selectedBuilding == null + || selectedBuilding.getFloors() == null + || selectedBuilding.getFloors().isEmpty()) { + return; + } + if (currentFloorIndex < 0) currentFloorIndex = 0; + if (currentFloorIndex >= selectedBuilding.getFloors().size()) { + currentFloorIndex = selectedBuilding.getFloors().size() - 1; + } + FloorPlan floor = selectedBuilding.getFloors().get(currentFloorIndex); + selectionListener.onBuildingSelected(selectedBuilding.getName(), floor.getFloorCode()); + } + + private List toLatLngList(List> geoPath) { + List points = new ArrayList<>(); + if (geoPath == null) return points; + for (List point : geoPath) { + if (point != null && point.size() >= 2) { + points.add(new LatLng(point.get(0), point.get(1))); + } + } + return points; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/CircularFloatBuffer.java b/app/src/main/java/com/openpositioning/PositionMe/utils/CircularFloatBuffer.java index 73abb674..06769140 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/utils/CircularFloatBuffer.java +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/CircularFloatBuffer.java @@ -1,9 +1,8 @@ package com.openpositioning.PositionMe.utils; +import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * Ring buffer for floats that can constantly update values in a fixed sized array. @@ -19,14 +18,29 @@ public class CircularFloatBuffer { private final float[] data; private volatile int writeSequence, readSequence; + public static final class SnapshotStats { + public static final SnapshotStats EMPTY = new SnapshotStats(0, 0f, 0f); + + public final int count; + public final float averageAbs; + public final float peakAbs; + + private SnapshotStats(int count, float averageAbs, float peakAbs) { + this.count = count; + this.averageAbs = averageAbs; + this.peakAbs = peakAbs; + } + } + /** * Default constructor for a Circular Float Buffer with a given capacity. * * @param capacity size of the array. */ public CircularFloatBuffer(int capacity) { - this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; - this.data = new float[capacity]; + int safeCapacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; + this.capacity = safeCapacity; + this.data = new float[safeCapacity]; this.readSequence = 0; this.writeSequence = -1; } @@ -106,9 +120,66 @@ public boolean isFull() { */ public List getListCopy() { if(!isFull()) return null; - return IntStream.range(readSequence, readSequence + capacity) - .mapToObj(i -> this.data[i % capacity]) - .collect(Collectors.toList()); + ArrayList snapshot = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + snapshot.add(this.data[(readSequence + i) % capacity]); + } + return snapshot; + } + + /** + * Get a snapshot of currently buffered values from oldest to newest. + * Unlike {@link #getListCopy()}, this also works before the buffer reaches full capacity. + * + * @return List of floats currently held in the buffer, or an empty list when no values exist. + */ + public List getSnapshot() { + int size = getCurrentSize(); + if (size <= 0) { + return java.util.Collections.emptyList(); + } + int safeSize = Math.min(size, capacity); + int start = Math.max(readSequence, writeSequence - safeSize + 1); + ArrayList snapshot = new ArrayList<>(safeSize); + for (int i = 0; i < safeSize; i++) { + snapshot.add(this.data[(start + i) % capacity]); + } + return snapshot; + } + + /** + * Summarise the current snapshot without allocating intermediate boxed collections. + * + * @return Count, average absolute magnitude, and peak absolute magnitude. + */ + public SnapshotStats getSnapshotStats() { + int size = getCurrentSize(); + if (size <= 0) { + return SnapshotStats.EMPTY; + } + + int safeSize = Math.min(size, capacity); + int start = Math.max(readSequence, writeSequence - safeSize + 1); + float sumAbs = 0f; + float peakAbs = 0f; + int validCount = 0; + + for (int i = 0; i < safeSize; i++) { + float sample = this.data[(start + i) % capacity]; + if (Float.isNaN(sample) || Float.isInfinite(sample)) { + continue; + } + float magnitude = Math.abs(sample); + sumAbs += magnitude; + peakAbs = Math.max(peakAbs, magnitude); + validCount++; + } + + if (validCount == 0) { + return SnapshotStats.EMPTY; + } + + return new SnapshotStats(validCount, sumAbs / validCount, peakAbs); } } diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateUtils.java b/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateUtils.java new file mode 100644 index 00000000..4a7cd5fd --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/CoordinateUtils.java @@ -0,0 +1,73 @@ +package com.openpositioning.PositionMe.utils; + +/** + * Coordinate conversion helper. + * Converts WGS84 lat/lon into local XY around a selected origin. + */ +public class CoordinateUtils { + + private static final double EARTH_RADIUS = 6371000.0; + + private Double originLat = null; + private Double originLon = null; + + // Sets the local origin the first time it becomes available. + public void setOrigin(double lat, double lon) { + if (originLat == null && originLon == null) { + originLat = lat; + originLon = lon; + } + } + // Clears the local XY origin. + public void resetOrigin() { + originLat = null; + originLon = null; + } + + // Checks whether the conversion origin has been stored. + public boolean isOriginSet() { + return originLat != null && originLon != null; + } + + // Copies the saved origin values into an output array. + public boolean copyOriginTo(double[] out) { + if (out == null || out.length < 2 || !isOriginSet()) { + return false; + } + out[0] = originLat; + out[1] = originLon; + return true; + } + // Returns the stored origin in latitude and longitude. + public double[] getOriginLatLon() { + double[] origin = new double[2]; + return copyOriginTo(origin) ? origin : null; + } + + // Converts latitude and longitude to local x and y meters. + public float[] latLonToXY(double lat, double lon) { + if (!isOriginSet()) { + return new float[]{0f, 0f}; + } + + double latRad = Math.toRadians(lat); + double lonRad = Math.toRadians(lon); + double originLatRad = Math.toRadians(originLat); + double originLonRad = Math.toRadians(originLon); + + float x = (float) (EARTH_RADIUS * Math.cos(originLatRad) * (lonRad - originLonRad)); + float y = (float) (EARTH_RADIUS * (latRad - originLatRad)); + return new float[]{x, y}; + } + + // Converts local x and y meters back to latitude and longitude. + public double[] xyToLatLon(float xMeters, float yMeters) { + if (!isOriginSet()) { + return null; + } + double originLatRad = Math.toRadians(originLat); + double lat = originLat + Math.toDegrees(yMeters / EARTH_RADIUS); + double lon = originLon + Math.toDegrees(xMeters / (EARTH_RADIUS * Math.cos(originLatRad))); + return new double[]{lat, lon}; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatchingEngine.java b/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatchingEngine.java new file mode 100644 index 00000000..2ecdf22a --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/MapMatchingEngine.java @@ -0,0 +1,887 @@ +package com.openpositioning.PositionMe.utils; + +import com.openpositioning.PositionMe.data.remote.FloorPlan; + +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +/** + * New code guide: + * 1. Transition scoring for wall and floor-change legality. + * 2. Wall-aware correction for step motion. + * 3. Vertical access detection and snapping. + * 4. Shared geometry helpers for floor-plan features. + */ +public class MapMatchingEngine { + + // Distinguishes ordinary areas from legal floor-change areas. + public enum VerticalZoneType { + NONE, + STAIRS, + LIFT + } + + private static final float VERTICAL_ZONE_STAIR_NEAR_METERS = 1.8f; + private static final float VERTICAL_ZONE_LIFT_NEAR_METERS = 2.2f; + private static final float FEATURE_DISTANCE_BIAS_METERS = 0.2f; + private static final float MIN_SAFE_DISTANCE_METERS = 0.05f; + private static final float WALL_CROSSING_PENALTY = 0.03f; + private static final float WRONG_VERTICAL_TRANSITION_PENALTY = 0.04f; + private static final float NEAR_VERTICAL_TRANSITION_SCORE = 0.68f; + private static final float GOOD_WALL_CLEARANCE_METERS = 0.35f; + private static final float POOR_WALL_CLEARANCE_METERS = 0.05f; + private static final float WALL_GUIDANCE_TRIGGER_METERS = 0.24f; + private static final float WALL_GUIDANCE_MIN_PROGRESS_METERS = 0.08f; + private static final float WALL_GUIDANCE_MIN_APPROACH_DOT = 0.18f; + private static final float WALL_GUIDANCE_JUNCTION_RADIUS_METERS = 0.70f; // was 0.52f โ€” wider corner exclusion zone, prevents guidance lock at junctions + private static final float WALL_GUIDANCE_JUNCTION_COS_THRESHOLD = 0.84f; + + private final CoordinateUtils coordinateUtils; + private final Map, float[]> pointXYCache = new IdentityHashMap<>(); + private double cacheOriginLat = Double.NaN; + private double cacheOriginLon = Double.NaN; + + private static final class IntersectionHit { + private final float t; + private final float[] wallStart; + private final float[] wallEnd; + + // Stores the first wall intersection found for a motion segment. + private IntersectionHit(float t, float[] wallStart, float[] wallEnd) { + this.t = t; + this.wallStart = wallStart; + this.wallEnd = wallEnd; + } + } + + private static final class NearestWallInfo { + private final float distanceMeters; + private final float[] nearestPoint; + private final float[] wallStart; + private final float[] wallEnd; + + // Stores the nearest wall segment and projected point. + private NearestWallInfo(float distanceMeters, float[] nearestPoint, float[] wallStart, float[] wallEnd) { + this.distanceMeters = distanceMeters; + this.nearestPoint = nearestPoint; + this.wallStart = wallStart; + this.wallEnd = wallEnd; + } + } + + // Keeps the shared coordinate converter for floor-plan geometry. + public MapMatchingEngine(CoordinateUtils coordinateUtils) { + this.coordinateUtils = coordinateUtils; + } + + // Checks whether a straight movement would cross any wall. + public boolean isMovementValid(float[] startXY, float[] endXY, FloorPlan floor) { + if (!isValidXY(startXY) || !isValidXY(endXY) || floor == null || floor.getWalls() == null) { + return true; + } + return findFirstIntersection(startXY, endXY, floor) == null; + } + + // Scores whether a particle transition is plausible on the current floor. + public float transitionScore(float[] startXY, + float[] endXY, + FloorPlan floor, + boolean floorChange, + boolean elevatorMode) { + if (!isValidXY(endXY) || floor == null) { + return 1f; + } + + float score = stateSupportScore(endXY, floor); + if (isValidXY(startXY) && findFirstIntersection(startXY, endXY, floor) != null) { + score *= WALL_CROSSING_PENALTY; + } + if (floorChange) { + float[] reference = isValidXY(startXY) ? startXY : endXY; + score *= verticalTransitionScore(reference, elevatorMode, floor); + } + return clamp(score, 0f, 1f); + } + + // Scores how safe one position is with respect to nearby walls. + public float stateSupportScore(float[] pointXY, FloorPlan floor) { + if (!isValidXY(pointXY) || floor == null || floor.getWalls() == null || floor.getWalls().isEmpty()) { + return 1f; + } + + float minWallDistance = minDistanceToWalls(pointXY, floor); + if (minWallDistance <= POOR_WALL_CLEARANCE_METERS) { + return 0.18f; + } + if (minWallDistance >= GOOD_WALL_CLEARANCE_METERS) { + return 1f; + } + + float ratio = (minWallDistance - POOR_WALL_CLEARANCE_METERS) + / Math.max(1e-5f, GOOD_WALL_CLEARANCE_METERS - POOR_WALL_CLEARANCE_METERS); + return clamp(0.18f + 0.82f * ratio, 0.18f, 1f); + } + + // Scores whether a floor change is reasonable at the current place. + public float verticalTransitionScore(float[] currentXY, boolean elevatorMode, FloorPlan floor) { + if (!isValidXY(currentXY) || floor == null) { + return 1f; + } + + VerticalZoneType zoneType = detectVerticalZone(currentXY, floor); + if (elevatorMode) { + if (zoneType == VerticalZoneType.LIFT) { + return 1f; + } + float nearestLift = nearestDistanceToFeatures(currentXY, floor.getLifts()); + if (nearestLift <= VERTICAL_ZONE_LIFT_NEAR_METERS) { + return NEAR_VERTICAL_TRANSITION_SCORE; + } + return WRONG_VERTICAL_TRANSITION_PENALTY; + } + + if (zoneType == VerticalZoneType.STAIRS) { + return 1f; + } + float nearestStairs = nearestDistanceToFeatures(currentXY, floor.getStairs()); + if (nearestStairs <= VERTICAL_ZONE_STAIR_NEAR_METERS) { + return NEAR_VERTICAL_TRANSITION_SCORE; + } + return WRONG_VERTICAL_TRANSITION_PENALTY; + } + + // Checks whether the user is inside a valid stairs or lift area. + public boolean isInsideVerticalAccess(float[] currentXY, boolean elevatorMode, FloorPlan floor) { + if (!isValidXY(currentXY) || floor == null) { + return false; + } + return elevatorMode + ? isPointInAnyFeature(currentXY, floor.getLifts()) + : isPointInAnyFeature(currentXY, floor.getStairs()); + } + + // Stops motion at walls, then tries to keep the remaining movement sliding along them. + public float[] correctMovementAgainstWalls(float[] startXY, + float[] endXY, + FloorPlan floor, + float safetyMarginMeters) { + if (!isValidXY(endXY)) { + return endXY; + } + if (!isValidXY(startXY) || floor == null || floor.getWalls() == null || floor.getWalls().isEmpty()) { + return new float[]{endXY[0], endXY[1]}; + } + + float[] corrected = new float[]{endXY[0], endXY[1]}; + float dx = endXY[0] - startXY[0]; + float dy = endXY[1] - startXY[1]; + float travel = (float) Math.sqrt(dx * dx + dy * dy); + if (travel < 1e-5f) { + return corrected; + } + + IntersectionHit firstHit = findFirstIntersection(startXY, endXY, floor); + boolean wallCorrected = false; + if (firstHit != null) { + float margin = Math.max(0f, safetyMarginMeters); + float marginT = margin / travel; + float safeT = Math.max(0f, firstHit.t - marginT); + corrected[0] = startXY[0] + dx * safeT; + corrected[1] = startXY[1] + dy * safeT; + wallCorrected = true; + + float remaining = Math.max(0f, travel * (1f - safeT) - margin); + if (remaining > 0.02f) { + float dirX = dx / travel; + float dirY = dy / travel; + float[] slideCandidate = slideAlongWall( + corrected, + firstHit.wallStart, + firstHit.wallEnd, + dirX, + dirY, + remaining, + 1f + ); + if (!isValidSlidePath(corrected, slideCandidate, floor)) { + slideCandidate = slideAlongWall( + corrected, + firstHit.wallStart, + firstHit.wallEnd, + dirX, + dirY, + remaining, + -1f + ); + } + if (isValidSlidePath(corrected, slideCandidate, floor)) { + corrected[0] = slideCandidate[0]; + corrected[1] = slideCandidate[1]; + } + } + } + + corrected = guideMovementAlongNearestWall( + startXY, + corrected, + floor, + safetyMarginMeters, + wallCorrected + ); + + float safeDistance = Math.max(MIN_SAFE_DISTANCE_METERS, safetyMarginMeters * 0.8f); + float minWallDistance = minDistanceToWalls(corrected, floor); + if (minWallDistance < safeDistance) { + float pull = Math.min(1f, (safeDistance - minWallDistance) / safeDistance); + corrected[0] = corrected[0] * (1f - pull) + startXY[0] * pull; + corrected[1] = corrected[1] * (1f - pull) + startXY[1] * pull; + } + + IntersectionHit secondHit = findFirstIntersection(startXY, corrected, floor); + if (secondHit != null && secondHit.t < 0.999f) { + float retreatT = Math.max(0f, secondHit.t - 0.02f); + return new float[]{ + startXY[0] + (corrected[0] - startXY[0]) * retreatT, + startXY[1] + (corrected[1] - startXY[1]) * retreatT + }; + } + + return corrected; + } + + // Checks whether the current position allows a floor transition. + public boolean canChangeFloor(float[] currentXY, boolean isElevator, FloorPlan floor) { + return verticalTransitionScore(currentXY, isElevator, floor) >= 0.6f; + } + + // Finds whether the current position is best explained by stairs, lift, or neither. + public VerticalZoneType detectVerticalZone(float[] currentXY, FloorPlan floor) { + if (!isValidXY(currentXY) || floor == null) { + return VerticalZoneType.NONE; + } + + float nearestLift = nearestDistanceToFeatures(currentXY, floor.getLifts()); + float nearestStairs = nearestDistanceToFeatures(currentXY, floor.getStairs()); + + boolean inLift = isPointInAnyFeature(currentXY, floor.getLifts()); + boolean inStairs = isPointInAnyFeature(currentXY, floor.getStairs()); + + if (inLift) { + return VerticalZoneType.LIFT; + } + if (inStairs) { + return VerticalZoneType.STAIRS; + } + + if (nearestLift <= VERTICAL_ZONE_LIFT_NEAR_METERS + && nearestLift <= nearestStairs + FEATURE_DISTANCE_BIAS_METERS) { + return VerticalZoneType.LIFT; + } + if (nearestStairs <= VERTICAL_ZONE_STAIR_NEAR_METERS) { + return VerticalZoneType.STAIRS; + } + return VerticalZoneType.NONE; + } + + // Snaps a point onto the nearest valid stairs or lift access area. + public float[] snapToVerticalAccess(float[] currentXY, boolean elevatorMode, FloorPlan floor) { + if (!isValidXY(currentXY)) { + return currentXY; + } + if (floor == null) { + return new float[]{currentXY[0], currentXY[1]}; + } + + List>> features = elevatorMode ? floor.getLifts() : floor.getStairs(); + float[] snapped = snapToNearestFeature(currentXY, features); + if (!isValidXY(snapped)) { + return new float[]{currentXY[0], currentXY[1]}; + } + return snapped; + } + + // Slides the remaining motion along the wall direction. + private float[] slideAlongWall(float[] anchor, + float[] wallStart, + float[] wallEnd, + float moveDirX, + float moveDirY, + float distanceMeters, + float preferredSign) { + if (!isValidXY(anchor) || !isValidXY(wallStart) || !isValidXY(wallEnd) || distanceMeters <= 0f) { + return null; + } + float wallDx = wallEnd[0] - wallStart[0]; + float wallDy = wallEnd[1] - wallStart[1]; + float wallLen = (float) Math.sqrt(wallDx * wallDx + wallDy * wallDy); + if (wallLen < 1e-5f) { + return null; + } + float tangentX = wallDx / wallLen; + float tangentY = wallDy / wallLen; + float dirAlignment = moveDirX * tangentX + moveDirY * tangentY; + float sign = dirAlignment >= 0f ? 1f : -1f; + sign *= preferredSign; + return new float[]{ + anchor[0] + sign * tangentX * distanceMeters, + anchor[1] + sign * tangentY * distanceMeters + }; + } + + // Checks whether a sliding path stays clear of walls. + private boolean isValidSlidePath(float[] startXY, float[] endXY, FloorPlan floor) { + if (!isValidXY(startXY) || !isValidXY(endXY)) { + return false; + } + IntersectionHit hit = findFirstIntersection(startXY, endXY, floor); + return hit == null; + } + + // Biases a near-wall step to follow the corridor instead of cutting through the wall. + private float[] guideMovementAlongNearestWall(float[] startXY, + float[] endXY, + FloorPlan floor, + float safetyMarginMeters, + boolean forceGuidance) { + if (!isValidXY(startXY) || !isValidXY(endXY) || floor == null || floor.getWalls() == null) { + return endXY; + } + + float moveDx = endXY[0] - startXY[0]; + float moveDy = endXY[1] - startXY[1]; + float travel = (float) Math.sqrt(moveDx * moveDx + moveDy * moveDy); + if (travel < WALL_GUIDANCE_MIN_PROGRESS_METERS) { + return endXY; + } + + NearestWallInfo nearestWall = findNearestWall(endXY, floor); + if (nearestWall == null || !isValidXY(nearestWall.nearestPoint)) { + return endXY; + } + + float triggerDistance = Math.max(WALL_GUIDANCE_TRIGGER_METERS, safetyMarginMeters * 1.35f); + if (!forceGuidance && nearestWall.distanceMeters > triggerDistance) { + return endXY; + } + if (hasAmbiguousWallGuidance( + endXY, + nearestWall.wallStart, + nearestWall.wallEnd, + floor, + Math.max(WALL_GUIDANCE_JUNCTION_RADIUS_METERS, triggerDistance * 1.7f) + )) { + return endXY; + } + + float wallDx = nearestWall.wallEnd[0] - nearestWall.wallStart[0]; + float wallDy = nearestWall.wallEnd[1] - nearestWall.wallStart[1]; + float wallLength = (float) Math.sqrt(wallDx * wallDx + wallDy * wallDy); + if (wallLength < 1e-5f) { + return endXY; + } + + float tangentX = wallDx / wallLength; + float tangentY = wallDy / wallLength; + float parallelProgress = moveDx * tangentX + moveDy * tangentY; + if (!forceGuidance && Math.abs(parallelProgress) < Math.max(WALL_GUIDANCE_MIN_PROGRESS_METERS, travel * 0.18f)) { + return endXY; + } + + float offsetX = endXY[0] - nearestWall.nearestPoint[0]; + float offsetY = endXY[1] - nearestWall.nearestPoint[1]; + float offsetNorm = (float) Math.sqrt(offsetX * offsetX + offsetY * offsetY); + float normalX; + float normalY; + if (offsetNorm > 1e-5f) { + normalX = offsetX / offsetNorm; + normalY = offsetY / offsetNorm; + } else { + normalX = -tangentY; + normalY = tangentX; + float startSide = (startXY[0] - nearestWall.nearestPoint[0]) * normalX + + (startXY[1] - nearestWall.nearestPoint[1]) * normalY; + if (startSide < 0f) { + normalX = -normalX; + normalY = -normalY; + } + } + + float moveNormX = moveDx / travel; + float moveNormY = moveDy / travel; + float approachDot = -(moveNormX * normalX + moveNormY * normalY); + if (!forceGuidance && approachDot < WALL_GUIDANCE_MIN_APPROACH_DOT) { + return endXY; + } + + float safeOffset = Math.max(MIN_SAFE_DISTANCE_METERS, safetyMarginMeters * 0.9f); + float[] guided = new float[]{ + nearestWall.nearestPoint[0] + tangentX * parallelProgress + normalX * safeOffset, + nearestWall.nearestPoint[1] + tangentY * parallelProgress + normalY * safeOffset + }; + if (isValidSlidePath(startXY, guided, floor)) { + return guided; + } + return endXY; + } + + // Finds the first wall segment hit by a movement line. + private IntersectionHit findFirstIntersection(float[] startXY, float[] endXY, FloorPlan floor) { + if (floor == null || floor.getWalls() == null) { + return null; + } + + IntersectionHit firstHit = null; + for (List> wallPath : floor.getWalls()) { + if (wallPath == null || wallPath.size() < 2) { + continue; + } + for (int i = 0; i < wallPath.size() - 1; i++) { + float[] wallPt1 = latLonToXY(wallPath.get(i)); + float[] wallPt2 = latLonToXY(wallPath.get(i + 1)); + if (!isValidXY(wallPt1) || !isValidXY(wallPt2)) { + continue; + } + + Float hitT = segmentIntersectionT( + startXY[0], startXY[1], endXY[0], endXY[1], + wallPt1[0], wallPt1[1], wallPt2[0], wallPt2[1] + ); + if (hitT != null && (firstHit == null || hitT < firstHit.t)) { + firstHit = new IntersectionHit( + hitT, + new float[]{wallPt1[0], wallPt1[1]}, + new float[]{wallPt2[0], wallPt2[1]} + ); + } + } + } + return firstHit; + } + + // Finds the wall segment closest to a given point. + private NearestWallInfo findNearestWall(float[] pointXY, FloorPlan floor) { + if (!isValidXY(pointXY) || floor == null || floor.getWalls() == null) { + return null; + } + + NearestWallInfo nearestWall = null; + float bestDistance = Float.MAX_VALUE; + for (List> wallPath : floor.getWalls()) { + if (wallPath == null || wallPath.size() < 2) { + continue; + } + for (int i = 0; i < wallPath.size() - 1; i++) { + float[] wallPt1 = latLonToXY(wallPath.get(i)); + float[] wallPt2 = latLonToXY(wallPath.get(i + 1)); + float[] nearestPoint = nearestPointOnSegment(pointXY, wallPt1, wallPt2); + if (!isValidXY(wallPt1) || !isValidXY(wallPt2) || !isValidXY(nearestPoint)) { + continue; + } + float distance = distanceBetween(pointXY, nearestPoint); + if (distance < bestDistance) { + bestDistance = distance; + nearestWall = new NearestWallInfo( + distance, + nearestPoint, + new float[]{wallPt1[0], wallPt1[1]}, + new float[]{wallPt2[0], wallPt2[1]} + ); + } + } + } + return nearestWall; + } + + // Detects junctions where wall guidance would be ambiguous. + private boolean hasAmbiguousWallGuidance(float[] pointXY, + float[] chosenWallStart, + float[] chosenWallEnd, + FloorPlan floor, + float radiusMeters) { + if (!isValidXY(pointXY) + || !isValidXY(chosenWallStart) + || !isValidXY(chosenWallEnd) + || floor == null + || floor.getWalls() == null) { + return false; + } + + float chosenDx = chosenWallEnd[0] - chosenWallStart[0]; + float chosenDy = chosenWallEnd[1] - chosenWallStart[1]; + float chosenLen = (float) Math.sqrt(chosenDx * chosenDx + chosenDy * chosenDy); + if (chosenLen < 1e-5f) { + return false; + } + float chosenTx = chosenDx / chosenLen; + float chosenTy = chosenDy / chosenLen; + + for (List> wallPath : floor.getWalls()) { + if (wallPath == null || wallPath.size() < 2) { + continue; + } + for (int i = 0; i < wallPath.size() - 1; i++) { + float[] wallPt1 = latLonToXY(wallPath.get(i)); + float[] wallPt2 = latLonToXY(wallPath.get(i + 1)); + if (!isValidXY(wallPt1) || !isValidXY(wallPt2)) { + continue; + } + if (isSameWallSegment(chosenWallStart, chosenWallEnd, wallPt1, wallPt2)) { + continue; + } + + float[] nearestPoint = nearestPointOnSegment(pointXY, wallPt1, wallPt2); + if (!isValidXY(nearestPoint) || distanceBetween(pointXY, nearestPoint) > radiusMeters) { + continue; + } + + float segDx = wallPt2[0] - wallPt1[0]; + float segDy = wallPt2[1] - wallPt1[1]; + float segLen = (float) Math.sqrt(segDx * segDx + segDy * segDy); + if (segLen < 1e-5f) { + continue; + } + float segTx = segDx / segLen; + float segTy = segDy / segLen; + float alignment = Math.abs(chosenTx * segTx + chosenTy * segTy); + if (alignment < WALL_GUIDANCE_JUNCTION_COS_THRESHOLD) { + return true; + } + } + } + return false; + } + + // Checks whether two wall segments represent the same edge. + private boolean isSameWallSegment(float[] aStart, float[] aEnd, float[] bStart, float[] bEnd) { + return (distanceBetween(aStart, bStart) <= 0.05f && distanceBetween(aEnd, bEnd) <= 0.05f) + || (distanceBetween(aStart, bEnd) <= 0.05f && distanceBetween(aEnd, bStart) <= 0.05f); + } + + // Returns where two line segments intersect along the first segment. + private Float segmentIntersectionT(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + float rx = x2 - x1; + float ry = y2 - y1; + float sx = x4 - x3; + float sy = y4 - y3; + + float rCrossS = cross(rx, ry, sx, sy); + float qpx = x3 - x1; + float qpy = y3 - y1; + + if (Math.abs(rCrossS) < 1e-6f) { + return null; + } + + float t = cross(qpx, qpy, sx, sy) / rCrossS; + float u = cross(qpx, qpy, rx, ry) / rCrossS; + if (t >= 0f && t <= 1f && u >= 0f && u <= 1f) { + return t; + } + return null; + } + + // Finds the minimum distance from a point to all walls. + private float minDistanceToWalls(float[] pointXY, FloorPlan floor) { + if (!isValidXY(pointXY) || floor == null || floor.getWalls() == null) { + return Float.MAX_VALUE; + } + float minDistance = Float.MAX_VALUE; + for (List> wallPath : floor.getWalls()) { + float d = distanceToFeature(pointXY, wallPath); + if (d < minDistance) { + minDistance = d; + } + } + return minDistance; + } + + // Finds the nearest distance from a point to a set of features. + private float nearestDistanceToFeatures(float[] pointXY, List>> features) { + if (!isValidXY(pointXY) || features == null || features.isEmpty()) { + return Float.MAX_VALUE; + } + float minDistance = Float.MAX_VALUE; + for (List> feature : features) { + float d = distanceToFeature(pointXY, feature); + if (d < minDistance) { + minDistance = d; + } + } + return minDistance; + } + + // Snaps a point to the closest available feature boundary. + private float[] snapToNearestFeature(float[] pointXY, List>> features) { + if (!isValidXY(pointXY) || features == null || features.isEmpty()) { + return null; + } + + float[] bestPoint = null; + float bestDistance = Float.MAX_VALUE; + for (List> feature : features) { + float[] candidate = nearestPointOnFeature(pointXY, feature); + if (!isValidXY(candidate)) { + continue; + } + float distance = distanceBetween(pointXY, candidate); + if (distance < bestDistance) { + bestDistance = distance; + bestPoint = candidate; + } + } + return bestPoint; + } + + // Measures the distance from a point to one feature shape. + private float distanceToFeature(float[] pointXY, List> featureLatLon) { + if (!isValidXY(pointXY) || featureLatLon == null || featureLatLon.isEmpty()) { + return Float.MAX_VALUE; + } + + if (featureLatLon.size() == 1) { + float[] p = latLonToXY(featureLatLon.get(0)); + if (!isValidXY(p)) { + return Float.MAX_VALUE; + } + float dx = pointXY[0] - p[0]; + float dy = pointXY[1] - p[1]; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + float minDistance = Float.MAX_VALUE; + for (int i = 0; i < featureLatLon.size() - 1; i++) { + float[] a = latLonToXY(featureLatLon.get(i)); + float[] b = latLonToXY(featureLatLon.get(i + 1)); + if (!isValidXY(a) || !isValidXY(b)) { + continue; + } + float d = distancePointToSegment(pointXY[0], pointXY[1], a[0], a[1], b[0], b[1]); + if (d < minDistance) { + minDistance = d; + } + } + + if (featureLatLon.size() >= 3) { + float[] first = latLonToXY(featureLatLon.get(0)); + float[] last = latLonToXY(featureLatLon.get(featureLatLon.size() - 1)); + if (isValidXY(first) && isValidXY(last)) { + float d = distancePointToSegment(pointXY[0], pointXY[1], last[0], last[1], first[0], first[1]); + if (d < minDistance) { + minDistance = d; + } + } + if (isPointInPolygon(pointXY, featureLatLon)) { + return 0f; + } + } + + return minDistance; + } + + // Finds the nearest point on one feature to the given point. + private float[] nearestPointOnFeature(float[] pointXY, List> featureLatLon) { + if (!isValidXY(pointXY) || featureLatLon == null || featureLatLon.isEmpty()) { + return null; + } + + if (featureLatLon.size() == 1) { + float[] point = latLonToXY(featureLatLon.get(0)); + if (!isValidXY(point)) { + return null; + } + return new float[]{point[0], point[1]}; + } + + if (featureLatLon.size() >= 3 && isPointInPolygon(pointXY, featureLatLon)) { + return new float[]{pointXY[0], pointXY[1]}; + } + + float[] bestPoint = null; + float bestDistance = Float.MAX_VALUE; + for (int i = 0; i < featureLatLon.size() - 1; i++) { + float[] a = latLonToXY(featureLatLon.get(i)); + float[] b = latLonToXY(featureLatLon.get(i + 1)); + float[] candidate = nearestPointOnSegment(pointXY, a, b); + if (!isValidXY(candidate)) { + continue; + } + float distance = distanceBetween(pointXY, candidate); + if (distance < bestDistance) { + bestDistance = distance; + bestPoint = candidate; + } + } + + if (featureLatLon.size() >= 3) { + float[] first = latLonToXY(featureLatLon.get(0)); + float[] last = latLonToXY(featureLatLon.get(featureLatLon.size() - 1)); + float[] candidate = nearestPointOnSegment(pointXY, last, first); + if (isValidXY(candidate)) { + float distance = distanceBetween(pointXY, candidate); + if (distance < bestDistance) { + bestPoint = candidate; + } + } + } + + return bestPoint; + } + + // Checks whether a point lies inside any polygon feature. + private boolean isPointInAnyFeature(float[] pointXY, List>> features) { + if (!isValidXY(pointXY) || features == null || features.isEmpty()) { + return false; + } + for (List> feature : features) { + if (feature != null && feature.size() >= 3 && isPointInPolygon(pointXY, feature)) { + return true; + } + } + return false; + } + + // Computes the shortest distance from a point to a segment. + private float distancePointToSegment(float px, float py, float ax, float ay, float bx, float by) { + float abx = bx - ax; + float aby = by - ay; + float abLenSq = abx * abx + aby * aby; + if (abLenSq < 1e-6f) { + float dx = px - ax; + float dy = py - ay; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + float apx = px - ax; + float apy = py - ay; + float t = (apx * abx + apy * aby) / abLenSq; + t = Math.max(0f, Math.min(1f, t)); + + float cx = ax + t * abx; + float cy = ay + t * aby; + float dx = px - cx; + float dy = py - cy; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + // Projects a point onto a line segment. + private float[] nearestPointOnSegment(float[] pointXY, float[] a, float[] b) { + if (!isValidXY(pointXY) || !isValidXY(a) || !isValidXY(b)) { + return null; + } + + float abx = b[0] - a[0]; + float aby = b[1] - a[1]; + float abLenSq = abx * abx + aby * aby; + if (abLenSq < 1e-6f) { + return new float[]{a[0], a[1]}; + } + + float apx = pointXY[0] - a[0]; + float apy = pointXY[1] - a[1]; + float t = (apx * abx + apy * aby) / abLenSq; + t = Math.max(0f, Math.min(1f, t)); + return new float[]{a[0] + t * abx, a[1] + t * aby}; + } + + // Reuses converted coordinates because floor-plan vertices are queried repeatedly. + private synchronized float[] latLonToXY(List latLon) { + if (latLon == null || latLon.size() < 2 || coordinateUtils == null || !coordinateUtils.isOriginSet()) { + return null; + } + ensureOriginCacheFresh(); + float[] cached = pointXYCache.get(latLon); + if (cached != null && isValidXY(cached)) { + return cached; + } + float[] converted = coordinateUtils.latLonToXY(latLon.get(0), latLon.get(1)); + if (isValidXY(converted)) { + pointXYCache.put(latLon, converted); + } + return converted; + } + + // Clears cached geometry when the coordinate origin changes. + private synchronized void ensureOriginCacheFresh() { + if (coordinateUtils == null || !coordinateUtils.isOriginSet()) { + pointXYCache.clear(); + cacheOriginLat = Double.NaN; + cacheOriginLon = Double.NaN; + return; + } + double[] origin = coordinateUtils.getOriginLatLon(); + if (origin == null || origin.length < 2) { + return; + } + if (Double.isNaN(cacheOriginLat) + || Double.isNaN(cacheOriginLon) + || Math.abs(origin[0] - cacheOriginLat) > 1e-10 + || Math.abs(origin[1] - cacheOriginLon) > 1e-10) { + pointXYCache.clear(); + cacheOriginLat = origin[0]; + cacheOriginLon = origin[1]; + } + } + + // Checks whether an xy array contains usable coordinates. + private boolean isValidXY(float[] xy) { + return xy != null + && xy.length >= 2 + && !Float.isNaN(xy[0]) + && !Float.isNaN(xy[1]) + && !Float.isInfinite(xy[0]) + && !Float.isInfinite(xy[1]); + } + + // Computes the 2D cross product of two vectors. + private float cross(float ax, float ay, float bx, float by) { + return ax * by - ay * bx; + } + + // Computes the distance between two xy points. + private float distanceBetween(float[] a, float[] b) { + if (!isValidXY(a) || !isValidXY(b)) { + return Float.MAX_VALUE; + } + float dx = a[0] - b[0]; + float dy = a[1] - b[1]; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + // Uses ray casting to test whether a point is inside a polygon. + private boolean isPointInPolygon(float[] point, List> polygonLatLon) { + if (!isValidXY(point) || polygonLatLon == null || polygonLatLon.size() < 3) { + return false; + } + + int intersections = 0; + float px = point[0]; + float py = point[1]; + + for (int i = 0; i < polygonLatLon.size(); i++) { + float[] v1 = latLonToXY(polygonLatLon.get(i)); + float[] v2 = latLonToXY(polygonLatLon.get((i + 1) % polygonLatLon.size())); + if (!isValidXY(v1) || !isValidXY(v2)) { + continue; + } + + if (((v1[1] > py) != (v2[1] > py)) + && (px < (v2[0] - v1[0]) * (py - v1[1]) / (v2[1] - v1[1]) + v1[0])) { + intersections++; + } + } + return (intersections % 2) != 0; + } + + // Limits a value to the given minimum and maximum range. + private float clamp(float value, float min, float max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/ParticleFilter.java b/app/src/main/java/com/openpositioning/PositionMe/utils/ParticleFilter.java new file mode 100644 index 00000000..9b0d2f6e --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/ParticleFilter.java @@ -0,0 +1,600 @@ +package com.openpositioning.PositionMe.utils; + +import com.openpositioning.PositionMe.data.remote.FloorPlan; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Particle filter used to fuse relative PDR motion with absolute GNSS/WiFi fixes. + * + * Position, heading, and floor are all particle state. PDR only predicts. Absolute observations + * only update particle weights. + * + * New code guide: + * 1. Initialization around a preferred absolute fix. + * 2. Prediction using PDR motion plus map constraints. + * 3. Absolute measurement updates from GNSS/WiFi. + * 4. Floor transitions through stairs/lift access points. + * 5. State extraction and resampling helpers. + */ +public class ParticleFilter { + + // Labels the source of an absolute update so noise can be tuned per sensor. + public enum MeasurementType { + GNSS, + WIFI + } + + public static final class Estimate { + private final float x; + private final float y; + private final float headingRad; + private final int floor; + + // Stores one fused state estimate from the particle cloud. + Estimate(float x, float y, float headingRad, int floor) { + this.x = x; + this.y = y; + this.headingRad = headingRad; + this.floor = floor; + } + + // Returns the estimated xy position. + public float[] getPositionXY() { + return new float[]{x, y}; + } + + // Returns the estimated heading in radians. + public float getHeadingRad() { + return headingRad; + } + + // Returns the estimated floor number. + public int getFloor() { + return floor; + } + } + + private static final float DEFAULT_INIT_STD_METERS = 1.2f; + private static final float STEP_NOISE_STD_METERS = 0.18f; + private static final float HEADING_NOISE_STD_RAD = 0.12f; + private static final float RESAMPLE_POSITION_JITTER_STD_METERS = 0.08f; + private static final float RESAMPLE_HEADING_JITTER_STD_RAD = 0.04f; + private static final float MIN_PARTICLE_WEIGHT = 1e-6f; + private static final float RESAMPLE_ESS_RATIO = 0.55f; + private static final float DEFAULT_WIFI_STD_METERS = 3.8f; + private static final float DEFAULT_GNSS_STD_METERS = 18.0f; + private static final float MAX_INIT_STD_METERS = 8.0f; + private static final float WRONG_FLOOR_PENALTY = 0.08f; + private static final float ADJACENT_FLOOR_PENALTY = 0.45f; + private static final float OBSERVED_FLOOR_INIT_SHARE = 0.72f; + private static final float PREFERRED_FLOOR_INIT_SHARE = 0.58f; + private static final float ADJACENT_FLOOR_INIT_SHARE = 0.24f; + private static final float MAP_CORRECTION_MARGIN_METERS = 0.18f; + + private final int numParticles; + private float[] particlesX; + private float[] particlesY; + private float[] headingsRad; + private int[] floors; + private final float[] weights; + private final Random random; + + private boolean initialized; + + // Creates the particle arrays and fills them with defaults. + public ParticleFilter(int numParticles) { + this.numParticles = numParticles; + this.particlesX = new float[numParticles]; + this.particlesY = new float[numParticles]; + this.headingsRad = new float[numParticles]; + this.floors = new int[numParticles]; + this.weights = new float[numParticles]; + this.random = new Random(); + reset(); + } + + // Resets all particles back to a uniform empty state. + public void reset() { + float uniformWeight = 1.0f / numParticles; + for (int i = 0; i < numParticles; i++) { + particlesX[i] = 0f; + particlesY[i] = 0f; + headingsRad[i] = 0f; + floors[i] = 0; + weights[i] = uniformWeight; + } + initialized = false; + } + + // Reports whether the filter has received an initial state. + public boolean isInitialized() { + return initialized; + } + + // Spreads particles around the first trusted anchor and an initial floor hypothesis. + public void initialize(float centerX, + float centerY, + float headingRad, + int floor, + float positionStdMeters) { + initialize( + centerX, + centerY, + headingRad, + Integer.valueOf(floor), + Integer.valueOf(floor), + null, + positionStdMeters + ); + } + + // Initializes particles using optional preferred and observed floors. + public void initialize(float centerX, + float centerY, + float headingRad, + Integer preferredFloor, + Integer observedFloor, + Map floorPlansByFloor, + float positionStdMeters) { + float initStd = clamp(positionStdMeters, 0.2f, MAX_INIT_STD_METERS); + float uniformWeight = 1.0f / numParticles; + List candidateFloors = candidateFloors(preferredFloor, observedFloor, floorPlansByFloor); + for (int i = 0; i < numParticles; i++) { + particlesX[i] = centerX + sampleGaussian(initStd); + particlesY[i] = centerY + sampleGaussian(initStd); + headingsRad[i] = normalizeAngleRad(headingRad + sampleGaussian(HEADING_NOISE_STD_RAD)); + floors[i] = sampleInitialFloor(candidateFloors, preferredFloor, observedFloor); + weights[i] = uniformWeight; + } + initialized = true; + } + + // Uses a default spread when only one floor guess is given. + public void initialize(float centerX, float centerY, float headingRad, int floor) { + initialize(centerX, centerY, headingRad, floor, DEFAULT_INIT_STD_METERS); + } + + // Predicts one motion step with the default confidence value. + public void predict(float stepLengthMeters, + float measuredHeadingRad, + Map floorPlansByFloor, + MapMatchingEngine mapMatchingEngine) { + predict(stepLengthMeters, measuredHeadingRad, floorPlansByFloor, mapMatchingEngine, 0.5f); + } + + // Moves particles using PDR motion and optional map correction. + public void predict(float stepLengthMeters, + float measuredHeadingRad, + Map floorPlansByFloor, + MapMatchingEngine mapMatchingEngine, + float predictionConfidence) { + if (!initialized) { + return; + } + + float confidence = clamp(predictionConfidence, 0f, 1f); + float stepNoiseStd = clamp( + STEP_NOISE_STD_METERS * (1.18f - 0.48f * confidence), + 0.08f, + STEP_NOISE_STD_METERS * 1.6f + ); + float headingNoiseStd = clamp( + HEADING_NOISE_STD_RAD * (1.18f - 0.52f * confidence), + 0.045f, + HEADING_NOISE_STD_RAD * 1.6f + ); + float weightSum = 0f; + for (int i = 0; i < numParticles; i++) { + float startX = particlesX[i]; + float startY = particlesY[i]; + FloorPlan floorPlan = resolveFloorPlan(floorPlansByFloor, floors[i]); + + float noisyHeading = normalizeAngleRad(measuredHeadingRad + sampleGaussian(headingNoiseStd)); + float noisyStep = Math.max(0f, stepLengthMeters + sampleGaussian(stepNoiseStd)); + float endX = startX + noisyStep * (float) Math.sin(noisyHeading); + float endY = startY + noisyStep * (float) Math.cos(noisyHeading); + + float transitionScore = 1f; + if (mapMatchingEngine != null && floorPlan != null) { + float[] correctedEnd = mapMatchingEngine.correctMovementAgainstWalls( + new float[]{startX, startY}, + new float[]{endX, endY}, + floorPlan, + MAP_CORRECTION_MARGIN_METERS + ); + float correctionDistance = distanceMeters(new float[]{endX, endY}, correctedEnd); + float correctionPenalty = clamp( + 1f - 0.35f * correctionDistance / Math.max(0.4f, noisyStep + MAP_CORRECTION_MARGIN_METERS), + 0.55f, + 1f + ); + endX = correctedEnd[0]; + endY = correctedEnd[1]; + transitionScore = mapMatchingEngine.transitionScore( + new float[]{startX, startY}, + new float[]{endX, endY}, + floorPlan, + false, + false + ) * correctionPenalty; + } + + particlesX[i] = endX; + particlesY[i] = endY; + headingsRad[i] = noisyHeading; + weights[i] = Math.max(MIN_PARTICLE_WEIGHT, weights[i] * transitionScore); + weightSum += weights[i]; + } + + normalizeWeights(weightSum); + maybeResample(); + } + + // Reweights particles against an absolute observation without moving them directly. + public void update(float measurementX, + float measurementY, + float measurementAccuracyMeters, + Integer measurementFloor, + MeasurementType type, + Map floorPlansByFloor, + MapMatchingEngine mapMatchingEngine) { + if (!initialized) { + initialize( + measurementX, + measurementY, + 0f, + measurementFloor, + measurementFloor, + floorPlansByFloor, + initialSpreadForMeasurement(measurementAccuracyMeters, type) + ); + return; + } + + float measurementStd = measurementStd(measurementAccuracyMeters, type); + float variance = Math.max(0.25f, measurementStd * measurementStd); + float weightSum = 0f; + + for (int i = 0; i < numParticles; i++) { + float dx = particlesX[i] - measurementX; + float dy = particlesY[i] - measurementY; + float distanceSq = dx * dx + dy * dy; + float likelihood = (float) Math.exp(-distanceSq / (2f * variance)); + + FloorPlan floorPlan = resolveFloorPlan(floorPlansByFloor, floors[i]); + float legalityScore = 1f; + if (mapMatchingEngine != null && floorPlan != null) { + legalityScore = mapMatchingEngine.stateSupportScore( + new float[]{particlesX[i], particlesY[i]}, + floorPlan + ); + } + + float floorScore = floorLikelihood(floors[i], measurementFloor); + weights[i] = Math.max(MIN_PARTICLE_WEIGHT, weights[i] * likelihood * legalityScore * floorScore); + weightSum += weights[i]; + } + + normalizeWeights(weightSum); + maybeResample(); + } + + // Attempts a floor jump only when both source and target floors support the transition. + public boolean applyFloorChange(int delta, + boolean elevatorMode, + Map floorPlansByFloor, + MapMatchingEngine mapMatchingEngine) { + if (!initialized || delta == 0 || mapMatchingEngine == null || floorPlansByFloor == null || floorPlansByFloor.isEmpty()) { + return false; + } + + boolean anyAccepted = false; + float weightSum = 0f; + float[] nextParticlesX = particlesX.clone(); + float[] nextParticlesY = particlesY.clone(); + int[] nextFloors = floors.clone(); + float[] nextWeights = weights.clone(); + + for (int i = 0; i < numParticles; i++) { + int sourceFloor = floors[i]; + int targetFloor = sourceFloor + delta; + FloorPlan sourcePlan = resolveFloorPlan(floorPlansByFloor, sourceFloor); + FloorPlan targetPlan = resolveFloorPlan(floorPlansByFloor, targetFloor); + if (sourcePlan == null || targetPlan == null) { + nextWeights[i] = Math.max(MIN_PARTICLE_WEIGHT, weights[i] * WRONG_FLOOR_PENALTY); + weightSum += nextWeights[i]; + continue; + } + + float[] pos = new float[]{particlesX[i], particlesY[i]}; + float[] sourceAccessPoint = mapMatchingEngine.snapToVerticalAccess(pos, elevatorMode, sourcePlan); + float[] targetAccessPoint = mapMatchingEngine.snapToVerticalAccess(sourceAccessPoint, elevatorMode, targetPlan); + float sourceTransitionScore = mapMatchingEngine.verticalTransitionScore(sourceAccessPoint, elevatorMode, sourcePlan); + float targetTransitionScore = mapMatchingEngine.verticalTransitionScore(targetAccessPoint, elevatorMode, targetPlan); + float targetSupportScore = mapMatchingEngine.stateSupportScore(targetAccessPoint, targetPlan); + float combinedScore = sourceTransitionScore + * targetTransitionScore + * Math.max(0.5f, targetSupportScore); + + if (combinedScore >= 0.6f) { + nextFloors[i] = targetFloor; + nextParticlesX[i] = targetAccessPoint[0]; + nextParticlesY[i] = targetAccessPoint[1]; + anyAccepted = true; + } + + nextWeights[i] = Math.max(MIN_PARTICLE_WEIGHT, weights[i] * combinedScore); + weightSum += nextWeights[i]; + } + + if (!anyAccepted) { + return false; + } + + particlesX = nextParticlesX; + particlesY = nextParticlesY; + floors = nextFloors; + System.arraycopy(nextWeights, 0, weights, 0, numParticles); + normalizeWeights(weightSum); + maybeResample(); + return true; + } + + // Collapses the weighted cloud into one display-ready state estimate. + public Estimate getEstimatedState() { + if (!initialized) { + return new Estimate(0f, 0f, 0f, 0); + } + + float sumWeights = 0f; + float meanX = 0f; + float meanY = 0f; + float sinHeading = 0f; + float cosHeading = 0f; + Map floorWeight = new HashMap<>(); + + for (int i = 0; i < numParticles; i++) { + float w = weights[i]; + sumWeights += w; + meanX += particlesX[i] * w; + meanY += particlesY[i] * w; + sinHeading += (float) Math.sin(headingsRad[i]) * w; + cosHeading += (float) Math.cos(headingsRad[i]) * w; + floorWeight.put(floors[i], floorWeight.getOrDefault(floors[i], 0f) + w); + } + + if (sumWeights <= 0f) { + return new Estimate(0f, 0f, 0f, 0); + } + + int bestFloor = 0; + float bestFloorWeight = Float.NEGATIVE_INFINITY; + for (Map.Entry entry : floorWeight.entrySet()) { + if (entry.getValue() > bestFloorWeight) { + bestFloorWeight = entry.getValue(); + bestFloor = entry.getKey(); + } + } + + float heading = (float) Math.atan2(sinHeading, cosHeading); + return new Estimate(meanX / sumWeights, meanY / sumWeights, heading, bestFloor); + } + + // Returns only the current estimated xy position. + public float[] getEstimatedPosition() { + return getEstimatedState().getPositionXY(); + } + + // Looks up the floor plan for one floor number. + private FloorPlan resolveFloorPlan(Map floorPlansByFloor, int floor) { + if (floorPlansByFloor == null || floorPlansByFloor.isEmpty()) { + return null; + } + return floorPlansByFloor.get(floor); + } + + // Builds the candidate floor list used during initialization. + private List candidateFloors(Integer preferredFloor, + Integer observedFloor, + Map floorPlansByFloor) { + List candidates = new ArrayList<>(); + if (floorPlansByFloor != null && !floorPlansByFloor.isEmpty()) { + candidates.addAll(floorPlansByFloor.keySet()); + Collections.sort(candidates); + } + if (observedFloor != null && !candidates.contains(observedFloor)) { + candidates.add(observedFloor); + } + if (preferredFloor != null && !candidates.contains(preferredFloor)) { + candidates.add(preferredFloor); + } + if (candidates.isEmpty()) { + candidates.add(observedFloor != null ? observedFloor : (preferredFloor != null ? preferredFloor : 0)); + } + Collections.sort(candidates); + return candidates; + } + + // Draws one initial floor sample from the candidate list. + private int sampleInitialFloor(List candidateFloors, + Integer preferredFloor, + Integer observedFloor) { + if (candidateFloors == null || candidateFloors.isEmpty()) { + return observedFloor != null ? observedFloor : (preferredFloor != null ? preferredFloor : 0); + } + if (candidateFloors.size() == 1) { + return candidateFloors.get(0); + } + + Integer primaryFloor = observedFloor != null ? observedFloor : preferredFloor; + if (primaryFloor != null && candidateFloors.contains(primaryFloor)) { + float primaryShare = observedFloor != null ? OBSERVED_FLOOR_INIT_SHARE : PREFERRED_FLOOR_INIT_SHARE; + float adjacentShare = ADJACENT_FLOOR_INIT_SHARE; + float draw = random.nextFloat(); + if (draw < primaryShare) { + return primaryFloor; + } + + List adjacentFloors = adjacentFloors(candidateFloors, primaryFloor); + if (!adjacentFloors.isEmpty() && draw < primaryShare + adjacentShare) { + return adjacentFloors.get(random.nextInt(adjacentFloors.size())); + } + } + + return candidateFloors.get(random.nextInt(candidateFloors.size())); + } + + // Collects floors that are directly above or below the reference floor. + private List adjacentFloors(List candidateFloors, int referenceFloor) { + List adjacent = new ArrayList<>(); + for (Integer floor : candidateFloors) { + if (floor != null && Math.abs(floor - referenceFloor) == 1) { + adjacent.add(floor); + } + } + return adjacent; + } + + // Scores how well a particle floor matches the measured floor. + private float floorLikelihood(int particleFloor, Integer measurementFloor) { + if (measurementFloor == null) { + return 1f; + } + if (particleFloor == measurementFloor) { + return 1f; + } + if (Math.abs(particleFloor - measurementFloor) == 1) { + return ADJACENT_FLOOR_PENALTY; + } + return WRONG_FLOOR_PENALTY; + } + + // Chooses the standard deviation for one absolute measurement. + private float measurementStd(float measurementAccuracyMeters, MeasurementType type) { + float fallback = type == MeasurementType.GNSS ? DEFAULT_GNSS_STD_METERS : DEFAULT_WIFI_STD_METERS; + if (Float.isNaN(measurementAccuracyMeters) || measurementAccuracyMeters <= 0f) { + return fallback; + } + float minStd = type == MeasurementType.GNSS ? 10f : 2.2f; + float maxStd = type == MeasurementType.GNSS ? 45f : 16f; + return clamp(measurementAccuracyMeters, minStd, maxStd); + } + + // Derives a reasonable initial spread from measurement accuracy. + private float initialSpreadForMeasurement(float measurementAccuracyMeters, MeasurementType type) { + return clamp(measurementStd(measurementAccuracyMeters, type) * 0.65f, 0.6f, MAX_INIT_STD_METERS); + } + + // Computes the distance between two xy points. + private float distanceMeters(float[] a, float[] b) { + if (a == null || b == null || a.length < 2 || b.length < 2) { + return 0f; + } + float dx = a[0] - b[0]; + float dy = a[1] - b[1]; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + // Normalizes all particle weights so they sum to one. + private void normalizeWeights(float weightSum) { + if (weightSum <= 0f || Float.isNaN(weightSum) || Float.isInfinite(weightSum)) { + float uniformWeight = 1.0f / numParticles; + for (int i = 0; i < numParticles; i++) { + weights[i] = uniformWeight; + } + return; + } + + for (int i = 0; i < numParticles; i++) { + weights[i] /= weightSum; + } + } + + // Resamples only when the effective particle count is too low. + private void maybeResample() { + float ess = effectiveSampleSize(); + if (ess / numParticles < RESAMPLE_ESS_RATIO) { + resample(); + } + } + + // Computes the effective sample size from the current weights. + private float effectiveSampleSize() { + float sumSq = 0f; + for (float weight : weights) { + sumSq += weight * weight; + } + if (sumSq <= 0f) { + return 0f; + } + return 1f / sumSq; + } + + // Uses systematic resampling to keep particle diversity stable at low cost. + private void resample() { + float[] newParticlesX = new float[numParticles]; + float[] newParticlesY = new float[numParticles]; + float[] newHeadingsRad = new float[numParticles]; + int[] newFloors = new int[numParticles]; + + float step = 1f / numParticles; + float r = random.nextFloat() * step; + float c = weights[0]; + int i = 0; + + for (int m = 0; m < numParticles; m++) { + float u = r + m * step; + while (u > c && i < numParticles - 1) { + i++; + c += weights[i]; + } + newParticlesX[m] = particlesX[i] + sampleGaussian(RESAMPLE_POSITION_JITTER_STD_METERS); + newParticlesY[m] = particlesY[i] + sampleGaussian(RESAMPLE_POSITION_JITTER_STD_METERS); + newHeadingsRad[m] = normalizeAngleRad(headingsRad[i] + sampleGaussian(RESAMPLE_HEADING_JITTER_STD_RAD)); + newFloors[m] = floors[i]; + } + + particlesX = newParticlesX; + particlesY = newParticlesY; + headingsRad = newHeadingsRad; + floors = newFloors; + + float uniformWeight = 1.0f / numParticles; + for (int j = 0; j < numParticles; j++) { + weights[j] = uniformWeight; + } + } + + // Draws Gaussian noise with the given standard deviation. + private float sampleGaussian(float std) { + return (float) (random.nextGaussian() * std); + } + + // Wraps angles into the normal -pi to pi range. + private float normalizeAngleRad(float angleRad) { + while (angleRad > Math.PI) { + angleRad -= (float) (2.0 * Math.PI); + } + while (angleRad < -Math.PI) { + angleRad += (float) (2.0 * Math.PI); + } + return angleRad; + } + + // Limits a value to the given range. + private float clamp(float value, float min, float max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + } +} diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/PathView.java b/app/src/main/java/com/openpositioning/PositionMe/utils/PathView.java index 5a5efa8d..b3d95cf1 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/utils/PathView.java +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/PathView.java @@ -31,20 +31,17 @@ public class PathView extends View { // Defines paint and canvas private Paint drawPaint; // Path of straight lines - private Path path = new Path(); + private final Path path = new Path(); // Array lists of integers to store coordinates - private static ArrayList xCoords = new ArrayList(); - private static ArrayList yCoords = new ArrayList(); + private final ArrayList xCoords = new ArrayList<>(); + private final ArrayList yCoords = new ArrayList<>(); // Scaling ratio for multiplying PDR coordinates to fill the screen size - private static float scalingRatio; + private float scalingRatio = 1f; // Instantiate correction fragment for passing it the scaling ratio - private CorrectionFragment correctionFragment = new CorrectionFragment(); - // Boolean flag to avoid rescaling trajectory when view is redrawn - private static boolean firstTimeOnDraw = true; - //Variable to only draw when the variable is true - private static boolean draw = true; - //Variable to only draw when the variable is true - private static boolean reDraw = false; + private final CorrectionFragment correctionFragment = new CorrectionFragment(); + // Track whether the next draw should apply initial scaling or a user-requested rescale. + private boolean needsInitialScale = true; + private boolean needsRescale = false; /** * Public default constructor for PathView. The constructor initialises the view with a context @@ -90,76 +87,26 @@ private void setupPaint() { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - //If drawing for first time scale trajectory to fit screen - if(this.draw){ - // If there are no coordinates, don't draw anything - if (xCoords.size() == 0) - return; + if (xCoords.isEmpty()) { + return; + } - //Scale trajectory to fit screen + if (needsInitialScale) { scaleTrajectory(); - - // Start a new path at the center of the view - path.moveTo(getWidth()/2, getHeight()/2); - - // Draw line between last point and this point - for (int i = 1; i < xCoords.size(); i++) { - path.lineTo(xCoords.get(i), yCoords.get(i)); - } - - //Draw path - canvas.drawPath(path, drawPaint); - - //Ensure path not redrawn - draw = false; - + needsInitialScale = false; + needsRescale = false; + } else if (needsRescale) { + rescaleAroundCenter(); + needsRescale = false; } - //If redrawing due to scaling of the average step length - else if(reDraw){ - // If there are no coordinates, don't draw anything - if (xCoords.size() == 0) - return; - - //Clear old path - path.reset(); - - // Iterate over all coordinates, shifting to the center and scaling then returning to original location - for (int i = 0; i < xCoords.size(); i++) { - float newXCoord = (xCoords.get(i) - getWidth()/2) * scalingRatio + getWidth()/2; - xCoords.set(i, newXCoord); - float newYCoord = (yCoords.get(i) - getHeight()/2) * scalingRatio + getHeight()/2; - yCoords.set(i, newYCoord); - } - - // Start a new path at the center of the view - path.moveTo(getWidth()/2, getHeight()/2); - - // Draw line between last point and this point - for (int i = 1; i < xCoords.size(); i++) { - path.lineTo(xCoords.get(i), yCoords.get(i)); - } - - canvas.drawPath(path, drawPaint); - - //Ensure path not redrawn when screen is resized - reDraw = false; - } - else{ - - // If there are no coordinates, don't draw anything - if (xCoords.size() == 0) - return; - // Start a new path at the center of the view - path.moveTo(getWidth()/2, getHeight()/2); - - // Draw line between last point and this point - for (int i = 1; i < xCoords.size(); i++) { - path.lineTo(xCoords.get(i), yCoords.get(i)); - } - - canvas.drawPath(path, drawPaint); + path.reset(); + path.moveTo(getWidth() / 2f, getHeight() / 2f); + for (int i = 1; i < xCoords.size(); i++) { + path.lineTo(xCoords.get(i), yCoords.get(i)); } + + canvas.drawPath(path, drawPaint); } /** @@ -169,6 +116,9 @@ else if(reDraw){ * @param newCords An array containing the newly calculated coordinates to be added. */ public void drawTrajectory(float[] newCords) { + if (newCords == null || newCords.length < 2) { + return; + } // Add x coordinates xCoords.add(newCords[0]); // Negate the y coordinate and add it to the yCoords list, since screen coordinates @@ -187,22 +137,24 @@ private void scaleTrajectory() { int centerY = getHeight() / 2; // Calculate the scaling that would be required in each direction - float xRightRange = (getWidth() / 2) / (Math.abs(Collections.max(xCoords))); - float xLeftRange = (getWidth() / 2) / (Math.abs(Collections.min(xCoords))); - float yTopRange = (getHeight() / 2) / (Math.abs(Collections.max(yCoords))); - float yBottomRange = (getHeight() / 2) / (Math.abs(Collections.min(yCoords))); + float xRightRange = safeAxisScale(Collections.max(xCoords), getWidth() / 2f); + float xLeftRange = safeAxisScale(Collections.min(xCoords), getWidth() / 2f); + float yTopRange = safeAxisScale(Collections.max(yCoords), getHeight() / 2f); + float yBottomRange = safeAxisScale(Collections.min(yCoords), getHeight() / 2f); // Take the minimum scaling ratio to ensure all points fit within the view float minRatio = Math.min(Math.min(xRightRange, xLeftRange), Math.min(yTopRange, yBottomRange)); // Add margins to the scaling ratio scalingRatio = 0.9f * minRatio; + if (!Float.isFinite(scalingRatio) || scalingRatio <= 0f) { + scalingRatio = 1f; + } // Limit scaling ratio to an equivalent of zoom of 21 in google maps if (scalingRatio >= 23.926) { scalingRatio = 23.926f; } - System.out.println("Adjusted scaling ratio: " + scalingRatio); // Set the scaling ratio for the correction fragment for setting Google Maps zoom correctionFragment.setScalingRatio(scalingRatio); @@ -216,6 +168,25 @@ private void scaleTrajectory() { } } + private void rescaleAroundCenter() { + float centerX = getWidth() / 2f; + float centerY = getHeight() / 2f; + for (int i = 0; i < xCoords.size(); i++) { + float newXCoord = (xCoords.get(i) - centerX) * scalingRatio + centerX; + float newYCoord = (yCoords.get(i) - centerY) * scalingRatio + centerY; + xCoords.set(i, newXCoord); + yCoords.set(i, newYCoord); + } + } + + private float safeAxisScale(float extent, float halfSize) { + float safeExtent = Math.abs(extent); + if (safeExtent < 1e-4f) { + return Float.MAX_VALUE; + } + return halfSize / safeExtent; + } + /** * Method called when PathView is detached from its window. {@link PathView#xCoords} and * {@link PathView#yCoords} are cleared so that path can start from 0 for next recording. @@ -223,11 +194,7 @@ private void scaleTrajectory() { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - // Reset trajectory - xCoords.clear(); - yCoords.clear(); - //New recording so must scale trajectory - draw = true; + clearTrajectory(); } /** @@ -238,10 +205,25 @@ protected void onDetachedFromWindow() { * @param newScale */ public void redraw(float newScale){ + if (!Float.isFinite(newScale) || newScale <= 0f) { + return; + } //Set scaling ratio based on user input scalingRatio = newScale; //Enable redrawing of path - reDraw = true; + needsRescale = true; } + + public void clearTrajectory() { + path.reset(); + xCoords.clear(); + yCoords.clear(); + scalingRatio = 1f; + needsInitialScale = true; + needsRescale = false; + invalidate(); + } + + } 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..d74df38e 100644 --- a/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/PdrProcessing.java @@ -33,11 +33,22 @@ public class PdrProcessing { private static final int elevationSeconds = 4; // Number of samples (0.01 seconds) private static final int accelSamples = 100; + private static final int MIN_FLOOR_HEIGHT_METERS = 4; // Threshold used to detect significant movement private static final float movementThreshold = 0.3f; // m/s^2 // Threshold under which movement is considered non-existent private static final float epsilon = 0.18f; private static final int MIN_REQUIRED_SAMPLES = 2; + private static final float DEFAULT_FALLBACK_STEP_METERS = 0.70f; + private static final float MIN_STEP_METERS = 0.30f; + private static final float MAX_STEP_METERS = 1.25f; + private static final long MIN_CADENCE_INTERVAL_MS = 280L; + private static final long MAX_CADENCE_INTERVAL_MS = 2400L; // was 1600L โ€” extend cadence model to cover slow walking (~0.4 Hz) + private static final float MIN_CADENCE_HZ = 0.45f; // was 0.75f โ€” consistent with above + private static final float MAX_CADENCE_HZ = 2.85f; + private static final float CADENCE_REFERENCE_HZ = 1.80f; + private static final float CADENCE_STEP_GAIN = 0.18f; + private static final float STEP_SMOOTHING_ALPHA = 0.34f; //endregion //region Instance variables @@ -103,27 +114,10 @@ public PdrProcessing(Context context) { this.positionX = 0f; this.positionY = 0f; this.elevation = 0f; - - - if(this.settings.getBoolean("overwrite_constants", false)) { - // Capacity - pressure is read with 1Hz - store values of past 10 seconds - this.elevationList = new CircularFloatBuffer(Integer.parseInt(settings.getString("elevation_seconds", "4"))); - - // Buffer for most recent acceleration values - this.verticalAccel = new CircularFloatBuffer(Integer.parseInt(settings.getString("accel_samples", "4"))); - this.horizontalAccel = new CircularFloatBuffer(Integer.parseInt(settings.getString("accel_samples", "4"))); - } - else { - // Capacity - pressure is read with 1Hz - store values of past 10 seconds - this.elevationList = new CircularFloatBuffer(elevationSeconds); - - // Buffer for most recent acceleration values - this.verticalAccel = new CircularFloatBuffer(accelSamples); - this.horizontalAccel = new CircularFloatBuffer(accelSamples); - } + initialiseMotionAndElevationBuffers(); // Distance between floors is building dependent, use manual value - this.floorHeight = settings.getInt("floor_height", 4); + this.floorHeight = getConfiguredFloorHeightMeters(); // Array for holding initial values this.startElevationBuffer = new Float[3]; // Start floor - assumed to be zero @@ -139,28 +133,31 @@ public PdrProcessing(Context context) { * @param accelMagnitudeOvertime recorded acceleration magnitudes since the last step. * @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 - } - + public float[] updatePdr(long currentStepEnd, + List accelMagnitudeOvertime, + float headingRad, + long stepIntervalMs) { // Change angle so zero rad is east - float adaptedHeading = (float) (Math.PI/2 - headingRad); + float safeHeading = (Float.isNaN(headingRad) || Float.isInfinite(headingRad)) ? 0f : headingRad; + float adaptedHeading = (float) (Math.PI/2 - safeHeading); + + float candidateStep = this.stepLength; + boolean hasAccelWindow = accelMagnitudeOvertime != null + && accelMagnitudeOvertime.size() >= MIN_REQUIRED_SAMPLES; - // 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); + if (!useManualStep) { + float cadenceStep = estimateCadenceAdaptiveStep(stepIntervalMs); + if (hasAccelWindow) { + float weibergStep = weibergMinMax(accelMagnitudeOvertime); + candidateStep = blendStrideEstimate(weibergStep, cadenceStep, accelMagnitudeOvertime); + } else { + candidateStep = cadenceStep; + } + } else if (candidateStep <= 0f) { + candidateStep = resolveFallbackStepLength(); } + this.stepLength = smoothStepEstimate(candidateStep); // Increment aggregate variables sumStepLength += stepLength; @@ -178,6 +175,68 @@ public float[] updatePdr(long currentStepEnd, List accelMagnitudeOvertim return new float[]{this.positionX, this.positionY}; } + private float blendStrideEstimate(float weibergStep, + float cadenceStep, + List accelMagnitudeOvertime) { + float safeWeibergStep = clamp( + weibergStep > 0f ? weibergStep : resolveFallbackStepLength(), + MIN_STEP_METERS, + MAX_STEP_METERS + ); + float safeCadenceStep = clamp( + cadenceStep > 0f ? cadenceStep : resolveFallbackStepLength(), + MIN_STEP_METERS, + MAX_STEP_METERS + ); + + float accelPeak = computePeakAcceleration(accelMagnitudeOvertime); + float accelConfidence = 0f; + if (accelPeak > 0f) { + accelConfidence = clamp((accelPeak - 0.7f) / 1.8f, 0f, 1f); + } + + float cadenceWeight = 0.32f - 0.18f * accelConfidence; + cadenceWeight = clamp(cadenceWeight, 0.12f, 0.32f); + return safeWeibergStep * (1f - cadenceWeight) + safeCadenceStep * cadenceWeight; + } + + private float estimateCadenceAdaptiveStep(long stepIntervalMs) { + float baseStep = this.stepLength > 0f ? this.stepLength : resolveFallbackStepLength(); + if (stepIntervalMs < MIN_CADENCE_INTERVAL_MS || stepIntervalMs > MAX_CADENCE_INTERVAL_MS) { + return clamp(baseStep, MIN_STEP_METERS, MAX_STEP_METERS); + } + + float cadenceHz = 1000f / Math.max(1f, (float) stepIntervalMs); + cadenceHz = clamp(cadenceHz, MIN_CADENCE_HZ, MAX_CADENCE_HZ); + float cadenceDelta = clamp(cadenceHz - CADENCE_REFERENCE_HZ, -0.90f, 0.90f); + float cadenceScale = 1f + CADENCE_STEP_GAIN * cadenceDelta; + return clamp(baseStep * cadenceScale, MIN_STEP_METERS, MAX_STEP_METERS); + } + + private float smoothStepEstimate(float candidateStep) { + float safeCandidateStep = clamp(candidateStep, MIN_STEP_METERS, MAX_STEP_METERS); + if (useManualStep) { + return safeCandidateStep; + } + float previousStep = this.stepLength > 0f ? this.stepLength : resolveFallbackStepLength(); + float smoothedStep = previousStep + STEP_SMOOTHING_ALPHA * (safeCandidateStep - previousStep); + return clamp(smoothedStep, MIN_STEP_METERS, MAX_STEP_METERS); + } + + private float resolveFallbackStepLength() { + if (this.stepLength > 0f) { + return this.stepLength; + } + try { + int userStepCm = this.settings.getInt("user_step_length", 75); + if (userStepCm > 20) { + return userStepCm / 100f; + } + } catch (Exception ignored) { + } + return DEFAULT_FALLBACK_STEP_METERS; + } + /** * Calculates the relative elevation compared to the start position. * The start elevation is the median of the first three seconds of data to give the sensor time @@ -204,21 +263,6 @@ public float updateElevation(float absoluteElevation) { this.elevation = absoluteElevation - startElevation; // Add to buffer this.elevationList.putNewest(absoluteElevation); - - // Check if there was floor movement - // Check if there is enough data to evaluate - if(this.elevationList.isFull()) { - // Check average of elevation array - List elevationMemory = this.elevationList.getListCopy(); - OptionalDouble currentAvg = elevationMemory.stream().mapToDouble(f -> f).average(); - float finishAvg = currentAvg.isPresent() ? (float) currentAvg.getAsDouble() : 0; - - // Check if we moved floor by comparing with start position - if(Math.abs(finishAvg - startElevation) > this.floorHeight) { - // Change floors - 'floor' division - this.currentFloor += (finishAvg - startElevation)/this.floorHeight; - } - } // Return current elevation return elevation; } @@ -255,12 +299,26 @@ private float weibergMinMax(List accelMagnitude) { // determine which constant to use based on settings if (this.settings.getBoolean("overwrite_constants", false)) { - return bounce * Float.parseFloat(settings.getString("weiberg_k", "0.934")) * 2; + return bounce * getPositiveFloatPreference("weiberg_k", K) * 2; } return bounce * K * 2; } + private float computePeakAcceleration(List accelMagnitude) { + if (accelMagnitude == null || accelMagnitude.isEmpty()) { + return 0f; + } + double peak = 0.0; + for (Double sample : accelMagnitude) { + if (sample == null || Double.isNaN(sample) || Double.isInfinite(sample)) { + continue; + } + peak = Math.max(peak, Math.abs(sample)); + } + return (float) peak; + } + /** * Get the current X and Y coordinates from the PDR processing class. * The coordinates are in meters, the start of the recording is the (0,0) @@ -303,6 +361,9 @@ public int getCurrentFloor() { * @return boolean true if currently in an elevator, false otherwise. */ public boolean estimateElevator(float[] gravity, float[] acc) { + if (gravity == null || acc == null || gravity.length < 3 || acc.length < 3) { + return false; + } // Standard gravity float g = SensorManager.STANDARD_GRAVITY; // get horizontal and vertical acceleration magnitude @@ -319,23 +380,16 @@ public boolean estimateElevator(float[] gravity, float[] acc) { this.horizontalAccel.putNewest(horizontalAcc); // Once buffer is full, evaluate data if(this.verticalAccel.isFull() && this.horizontalAccel.isFull()) { - - // calculate average vertical accel - List verticalMemory = this.verticalAccel.getListCopy(); - OptionalDouble optVerticalAvg = verticalMemory.stream().mapToDouble(Math::abs).average(); - float verticalAvg = optVerticalAvg.isPresent() ? (float) optVerticalAvg.getAsDouble() : 0; - - - // calculate average horizontal accel - List horizontalMemory = this.horizontalAccel.getListCopy(); - OptionalDouble optHorizontalAvg = horizontalMemory.stream().mapToDouble(Math::abs).average(); - float horizontalAvg = optHorizontalAvg.isPresent() ? (float) optHorizontalAvg.getAsDouble() : 0; + CircularFloatBuffer.SnapshotStats verticalStats = this.verticalAccel.getSnapshotStats(); + CircularFloatBuffer.SnapshotStats horizontalStats = this.horizontalAccel.getSnapshotStats(); + float verticalAvg = verticalStats.averageAbs; + float horizontalAvg = horizontalStats.averageAbs; //System.err.println("LIFT: Vertical: " + verticalAvg); //System.err.println("LIFT: Horizontal: " + horizontalAvg); if(this.settings.getBoolean("overwrite_constants", false)) { - float eps = Float.parseFloat(settings.getString("epsilon", "0.18")); + float eps = getNonNegativeFloatPreference("epsilon", epsilon); return horizontalAvg < eps && verticalAvg > movementThreshold; } // Check if there is minimal horizontal and significant vertical movement @@ -372,37 +426,75 @@ public void resetPDR() { this.positionY = 0f; this.elevation = 0f; - if(this.settings.getBoolean("overwrite_constants", false)) { - // Capacity - pressure is read with 1Hz - store values of past 10 seconds - this.elevationList = new CircularFloatBuffer(Integer.parseInt(settings.getString("elevation_seconds", "4"))); - - // Buffer for most recent acceleration values - this.verticalAccel = new CircularFloatBuffer(Integer.parseInt(settings.getString("accel_samples", "4"))); - this.horizontalAccel = new CircularFloatBuffer(Integer.parseInt(settings.getString("accel_samples", "4"))); - } - else { - // Capacity - pressure is read with 1Hz - store values of past 10 seconds - this.elevationList = new CircularFloatBuffer(elevationSeconds); - - // Buffer for most recent acceleration values - this.verticalAccel = new CircularFloatBuffer(accelSamples); - this.horizontalAccel = new CircularFloatBuffer(accelSamples); - } + initialiseMotionAndElevationBuffers(); // Distance between floors is building dependent, use manual value - this.floorHeight = settings.getInt("floor_height", 4); + this.floorHeight = getConfiguredFloorHeightMeters(); // Array for holding initial values this.startElevationBuffer = new Float[3]; // Start floor - assumed to be zero this.currentFloor = 0; } + private void initialiseMotionAndElevationBuffers() { + if (this.settings.getBoolean("overwrite_constants", false)) { + this.elevationList = new CircularFloatBuffer( + getPositiveIntPreference("elevation_seconds", elevationSeconds) + ); + int configuredAccelSamples = getPositiveIntPreference("accel_samples", accelSamples); + this.verticalAccel = new CircularFloatBuffer(configuredAccelSamples); + this.horizontalAccel = new CircularFloatBuffer(configuredAccelSamples); + return; + } + + this.elevationList = new CircularFloatBuffer(elevationSeconds); + this.verticalAccel = new CircularFloatBuffer(accelSamples); + this.horizontalAccel = new CircularFloatBuffer(accelSamples); + } + + private int getConfiguredFloorHeightMeters() { + return Math.max(MIN_FLOOR_HEIGHT_METERS, settings.getInt("floor_height", MIN_FLOOR_HEIGHT_METERS)); + } + + private int getPositiveIntPreference(String key, int defaultValue) { + try { + String value = settings.getString(key, Integer.toString(defaultValue)); + int parsed = Integer.parseInt(value); + return parsed > 0 ? parsed : defaultValue; + } catch (RuntimeException e) { + return defaultValue; + } + } + + private float getPositiveFloatPreference(String key, float defaultValue) { + try { + String value = settings.getString(key, Float.toString(defaultValue)); + float parsed = Float.parseFloat(value); + return parsed > 0f ? parsed : defaultValue; + } catch (RuntimeException e) { + return defaultValue; + } + } + + private float getNonNegativeFloatPreference(String key, float defaultValue) { + try { + String value = settings.getString(key, Float.toString(defaultValue)); + float parsed = Float.parseFloat(value); + return parsed >= 0f ? parsed : defaultValue; + } catch (RuntimeException e) { + return defaultValue; + } + } + /** * Getter for the average step length calculated from the aggregated distance and step count. * * @return average step length in meters. */ public float getAverageStepLength(){ + if (stepCount <= 0) { + return stepLength > 0f ? stepLength : DEFAULT_FALLBACK_STEP_METERS; + } //Calculate average step length float averageStepLength = sumStepLength/(float) stepCount; @@ -414,4 +506,14 @@ public float getAverageStepLength(){ return averageStepLength; } + private float clamp(float value, float min, float max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + } + } diff --git a/app/src/main/java/com/openpositioning/PositionMe/utils/VenueSelectionHelper.java b/app/src/main/java/com/openpositioning/PositionMe/utils/VenueSelectionHelper.java new file mode 100644 index 00000000..c3f4af2c --- /dev/null +++ b/app/src/main/java/com/openpositioning/PositionMe/utils/VenueSelectionHelper.java @@ -0,0 +1,74 @@ +package com.openpositioning.PositionMe.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +/** + * Centralises venue selection persistence so map selection and upload stay in sync. + */ +public final class VenueSelectionHelper { + + public static final String PREF_SELECTED_BUILDING = "selected_building_name"; + public static final String PREF_CURRENT_CAMPAIGN = "current_campaign"; + public static final String DEFAULT_CAMPAIGN = "nucleus"; + + // Prevents this helper class from being created. + private VenueSelectionHelper() { + } + + // Saves the selected building and matching campaign name. + public static void persistSelectedBuilding(Context context, String buildingName) { + if (context == null || buildingName == null || buildingName.trim().isEmpty()) { + return; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + prefs.edit() + .putString(PREF_SELECTED_BUILDING, buildingName) + .putString(PREF_CURRENT_CAMPAIGN, resolveCampaignName(buildingName)) + .apply(); + } + + // Returns the campaign name that should be used now. + public static String getSelectedCampaign(Context context) { + if (context == null) { + return DEFAULT_CAMPAIGN; + } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + String campaign = prefs.getString(PREF_CURRENT_CAMPAIGN, null); + if ((campaign == null || campaign.trim().isEmpty())) { + String selectedBuilding = prefs.getString(PREF_SELECTED_BUILDING, null); + if (selectedBuilding != null && !selectedBuilding.trim().isEmpty()) { + campaign = resolveCampaignName(selectedBuilding); + } + } + if (campaign == null || campaign.trim().isEmpty()) { + return DEFAULT_CAMPAIGN; + } + return campaign; + } + + // Converts a building name into a simple campaign key. + public static String resolveCampaignName(String buildingName) { + if (buildingName == null) { + return DEFAULT_CAMPAIGN; + } + + String normalized = buildingName.trim().toLowerCase(); + if (normalized.contains("nucleus")) { + return "nucleus"; + } + if (normalized.contains("library")) { + return "library"; + } + if (normalized.contains("murchison")) { + return "murchison_house"; + } + + String slug = normalized.replaceAll("[^a-z0-9]+", "_") + .replaceAll("^_+|_+$", ""); + return slug.isEmpty() ? DEFAULT_CAMPAIGN : slug; + } +} diff --git a/app/src/main/proto/new_traj.proto b/app/src/main/proto/new_traj.proto new file mode 100644 index 00000000..a9ed6190 --- /dev/null +++ b/app/src/main/proto/new_traj.proto @@ -0,0 +1,213 @@ +syntax = "proto3"; +option java_package = "com.openpositioning.PositionMe"; +option java_outer_classname = "Traj"; + +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; + + repeated GNSSPosition test_points = 26; + +} + +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; +} + +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; + + // Magnetometer [uT] + Vector3 mag = 2; +} + +message BarometerReading { + int64 relative_timestamp = 1; + + // mbar + float pressure = 2; +} + +message LightReading { + int64 relative_timestamp = 1; + // lux + float light = 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 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 Fingerprint { + int64 relative_timestamp = 1; + repeated RFScan rf_scans = 2; + +} + +message RFScan { + int64 relative_timestamp = 1; + + // Integer encoding of the hex mac address (BSSID) + // e.g. 207394925843984 + int64 mac = 2; + + // rssi integer in dBm. + // typically between -120 and -10 + int32 rssi = 3; + + // returned position + optional GNSSPosition position = 4; +} + +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 WiFiAPData { + // Integer encoding of the hex mac address (BSSID) + // e.g. 207394925843984 + int64 mac = 1; + + // E.g. 'Eduroam' or 'Starbucks_free_wifi' + string ssid = 2; + + // Typically 2.4GHz or 5GHz + int64 frequency = 3; + + // Flag to indicate if the AP supports RTT measurements + bool rtt_enabled = 4; +} + +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; +} + + // --- Common Types --- +message Vector3 { + float x = 1; + float y = 2; + float z = 3; +} + +message Quaternion { + float x = 1; + float y = 2; + float z = 3; + float w = 4; +} + +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; +} + diff --git a/app/src/main/proto/traj.proto b/app/src/main/proto/traj.proto deleted file mode 100644 index 95d8b0ac..00000000 --- a/app/src/main/proto/traj.proto +++ /dev/null @@ -1,149 +0,0 @@ -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; - -// 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 Pdr_Sample { -// 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; -} - - -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 Position_Sample { - int64 relative_timestamp = 1; - - // uT - float mag_x = 2; - float mag_y = 3; - float mag_z = 4; -} - -message Pressure_Sample { - int64 relative_timestamp = 1; - - // mbar - float pressure = 2; - -} -message Light_Sample { - int64 relative_timestamp = 1; - // lux - float light = 2; -} -message GNSS_Sample { - int64 relative_timestamp = 1; - - // degrees (minimum 6 significant figures) - // latitude between -90 and 90 - float latitude = 2; - - // longitude between -180 and 180 - float longitude = 3; - - //metres - float altitude = 4; - - // metres - float accuracy = 5; - - // m/s - float speed = 6; - - // e.g 'gps' or 'network' - string provider = 7; -} - -message WiFi_Sample { - int64 relative_timestamp = 1; - repeated Mac_Scan mac_scans = 2; - -} - -message Mac_Scan { - int64 relative_timestamp = 1; - - // Integer encoding of the hex mac address - // e.g. 207394925843984 - int64 mac = 2; - - // rssi integer in dBm. - // typically between -120 and -10 - int32 rssi = 3; -} - -message AP_Data { - // Integer encoding of the hex mac address - // e.g. 207394925843984 - int64 mac = 1; - - // E.g. 'Eduroam' or 'Starbucks_free_wifi' - string ssid = 2; - - // Typically 2.4GHz or 5GHz - int64 frequency = 3; -} - -message Sensor_Info { - string name = 1; - string vendor = 2; - float resolution = 3; - float power = 4; - int32 version = 5; - int32 type = 6; -} \ No newline at end of file diff --git a/app/src/main/proto/upload_legacy_traj.proto b/app/src/main/proto/upload_legacy_traj.proto new file mode 100644 index 00000000..61d35bff --- /dev/null +++ b/app/src/main/proto/upload_legacy_traj.proto @@ -0,0 +1,100 @@ +syntax = "proto3"; + +package upload_legacy; + +option java_package = "com.openpositioning.PositionMe"; +option java_outer_classname = "UploadTraj"; + +message Trajectory { + string android_version = 1; + repeated MotionSample imu_data = 2; + repeated PdrSample pdr_data = 3; + repeated PositionSample position_data = 4; + repeated PressureSample pressure_data = 5; + repeated LightSample light_data = 6; + repeated GnssSample gnss_data = 7; + repeated WifiSample wifi_data = 8; + repeated ApData aps_data = 9; + int64 start_timestamp = 10; + string data_identifier = 11; + SensorInfo accelerometer_info = 12; + SensorInfo gyroscope_info = 13; + SensorInfo rotation_vector_info = 14; + SensorInfo magnetometer_info = 15; + SensorInfo barometer_info = 16; + SensorInfo light_sensor_info = 17; +} + +message PdrSample { + int64 relative_timestamp = 1; + float x = 2; + float y = 3; +} + +message MotionSample { + int64 relative_timestamp = 1; + float acc_x = 2; + float acc_y = 3; + float acc_z = 4; + float gyr_x = 5; + float gyr_y = 6; + float gyr_z = 7; + float rotation_vector_x = 8; + float rotation_vector_y = 9; + float rotation_vector_z = 10; + float rotation_vector_w = 11; + int32 step_count = 12; +} + +message PositionSample { + int64 relative_timestamp = 1; + float mag_x = 2; + float mag_y = 3; + float mag_z = 4; +} + +message PressureSample { + int64 relative_timestamp = 1; + float pressure = 2; +} + +message LightSample { + int64 relative_timestamp = 1; + float light = 2; +} + +message GnssSample { + int64 relative_timestamp = 1; + float latitude = 2; + float longitude = 3; + float altitude = 4; + float accuracy = 5; + float speed = 6; + string provider = 7; +} + +message WifiSample { + int64 relative_timestamp = 1; + repeated MacScan mac_scans = 2; +} + +message MacScan { + int64 relative_timestamp = 1; + int64 mac = 2; + int32 rssi = 3; +} + +message ApData { + int64 mac = 1; + string ssid = 2; + int64 frequency = 3; +} + +message SensorInfo { + string name = 1; + string vendor = 2; + float resolution = 3; + float power = 4; + int32 version = 5; + int32 type = 6; +} diff --git a/app/src/main/res/layout-small/fragment_home.xml b/app/src/main/res/layout-small/fragment_home.xml index bd713b67..39b6d66b 100644 --- a/app/src/main/res/layout-small/fragment_home.xml +++ b/app/src/main/res/layout-small/fragment_home.xml @@ -1,176 +1,238 @@ - - + android:clipToPadding="false" + android:fillViewport="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingTop="16dp" + android:paddingEnd="16dp" + android:paddingBottom="24dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 99c1ef13..1577e940 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,173 +1,238 @@ - - + android:clipToPadding="false" + android:fillViewport="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + android:paddingStart="18dp" + android:paddingTop="18dp" + android:paddingEnd="18dp" + android:paddingBottom="24dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_indoor_map.xml b/app/src/main/res/layout/fragment_indoor_map.xml new file mode 100644 index 00000000..856f4420 --- /dev/null +++ b/app/src/main/res/layout/fragment_indoor_map.xml @@ -0,0 +1,56 @@ + + + + + + + +