diff --git a/.gitignore b/.gitignore
index d4c3a57e..55e62e88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
.cxx
local.properties
/.idea/
+secrets.properties
diff --git a/app/src/main/java/com/openpositioning/PositionMe/Traj.java b/app/src/main/java/com/openpositioning/PositionMe/Traj.java
index 7925fa55..8700e361 100644
--- a/app/src/main/java/com/openpositioning/PositionMe/Traj.java
+++ b/app/src/main/java/com/openpositioning/PositionMe/Traj.java
@@ -1,3 +1,42 @@
+/**
+ * This class represents the Trajectory data structure generated from the Protocol Buffers definition.
+ * It contains various sensor data samples and metadata related to a trajectory data collection event.
+ *
+ *
Key Features:
+ *
+ * - Contains optional and repeated fields for various sensor data types such as IMU, PDR, GNSS, WiFi, etc.
+ * - Includes metadata like Android version, start timestamp, and data identifier.
+ * - Supports relative timestamps for sensor data based on the start timestamp.
+ * - Provides information about sensor hardware through Sensor_Info fields.
+ *
+ *
+ * Field Descriptions:
+ *
+ * android_version: The Android version of the device collecting the data.
+ * imu_data: List of motion samples from the IMU (Inertial Measurement Unit).
+ * pdr_data: List of PDR (Pedestrian Dead Reckoning) samples.
+ * position_data: List of position samples.
+ * pressure_data: List of pressure sensor samples.
+ * light_data: List of light sensor samples.
+ * gnss_data: List of GNSS (Global Navigation Satellite System) samples.
+ * wifi_data: List of WiFi samples.
+ * aps_data: List of Access Point (AP) data samples.
+ * start_timestamp: UNIX timestamp (in milliseconds) marking the start of the trajectory data collection.
+ * data_identifier: A unique identifier for the data collection event.
+ * accelerometer_info: Information about the accelerometer sensor.
+ * gyroscope_info: Information about the gyroscope sensor.
+ * rotation_vector_info: Information about the rotation vector sensor.
+ * magnetometer_info: Information about the magnetometer sensor.
+ * barometer_info: Information about the barometer sensor.
+ * light_sensor_info: Information about the light sensor.
+ *
+ *
+ * Note:
+ *
+ * - All timestamps in the sub-classes are relative to the
start_timestamp.
+ * - This class is auto-generated and should not be edited manually.
+ *
+ */
package com.openpositioning.PositionMe;// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: Cloud/app/src/main/proto/traj.proto
diff --git a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java
index 2d2b1cbf..04e7d357 100644
--- a/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java
+++ b/app/src/main/java/com/openpositioning/PositionMe/data/local/TrajParser.java
@@ -4,6 +4,9 @@
import android.hardware.SensorManager;
import android.util.Log;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.Volley;
import com.google.android.gms.maps.model.LatLng;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
@@ -11,6 +14,10 @@
import com.google.gson.JsonParser;
import com.openpositioning.PositionMe.presentation.fragment.ReplayFragment;
import com.openpositioning.PositionMe.sensors.SensorFusion;
+import com.openpositioning.PositionMe.sensors.WiFiPositioning;
+import com.android.volley.Request;
+import org.json.JSONException;
+import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
@@ -19,6 +26,8 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Handles parsing of trajectory data stored in JSON files, combining IMU, PDR, and GNSS data
@@ -46,7 +55,7 @@
*
* @see ReplayFragment which uses parsed trajectory data for visualization.
* @see SensorFusion for motion processing and sensor integration.
- * @see com.openpositioning.PositionMe.presentation.fragment.ReplayFragment for implementation details.
+ * @see ReplayFragment for implementation details.
*
* @author Shu Gu
* @author Lin Cheng
@@ -55,13 +64,17 @@ public class TrajParser {
private static final String TAG = "TrajParser";
+ private WiFiPositioning wiFiPositioning;
+
/**
- * Represents a single replay point containing estimated PDR position, GNSS location,
+ * Represents a single replay point containing estimated PDR position, GNSS location, WiFi location
* orientation, speed, and timestamp.
*/
public static class ReplayPoint {
+ public LatLng wifiLocation;
public LatLng pdrLocation; // PDR-derived location estimate
public LatLng gnssLocation; // GNSS location (may be null if unavailable)
+ public LatLng wifiLocation; // WiFi location (may be null if unavailable)
public float orientation; // Orientation in degrees
public float speed; // Speed in meters per second
public long timestamp; // Relative timestamp
@@ -75,9 +88,10 @@ public static class ReplayPoint {
* @param speed The speed in meters per second.
* @param timestamp The timestamp associated with this point.
*/
- public ReplayPoint(LatLng pdrLocation, LatLng gnssLocation, float orientation, float speed, long timestamp) {
+ public ReplayPoint(LatLng pdrLocation, LatLng gnssLocation, LatLng wifiLocation, float orientation, float speed, long timestamp) {
this.pdrLocation = pdrLocation;
this.gnssLocation = gnssLocation;
+ this.wifiLocation = wifiLocation;//更改构造函数,添加wifiLocation参数
this.orientation = orientation;
this.speed = speed;
this.timestamp = timestamp;
@@ -104,6 +118,17 @@ private static class GnssRecord {
public double latitude, longitude; // GNSS coordinates
}
+ private static class MacScan {
+ public String mac;
+ public double rssi;
+ public int timestamp;
+ }
+
+ private static class WiFiRecord{
+ public long relativeTimestamp;
+ public List macScans;
+ }
+
/**
* Parses trajectory data from a JSON file and reconstructs a list of replay points.
*
@@ -184,8 +209,80 @@ public static List parseTrajectoryData(String filePath, Context con
GnssRecord closestGnss = findClosestGnssRecord(gnssList, pdr.relativeTimestamp);
LatLng gnssLocation = closestGnss != null ?
new LatLng(closestGnss.latitude, closestGnss.longitude) : null;
+ WifiRecord closestWifi = findClosestWifiRecord(wifiList, pdr.relativeTimestamp);
+ LatLng wifiLocation = closestWifi != null ?
+ new LatLng(closestWifi.latitude, closestWifi.longitude) : null;
+
+ WiFiRecord closestWiFi = findClosestWiFiRecord(WiFiList, pdr.relativeTimestamp);
+ AtomicReference wifilocation = new AtomicReference<>();
+ AtomicInteger floor = new AtomicInteger();
+ try {
+ // Creating a JSON object to store the WiFi access points
+ JSONObject wifiAccessPoints = new JSONObject();
+ for (MacScan scan : closestWiFi.macScans) {
+ wifiAccessPoints.put(scan.mac, scan.rssi);
+ }
+ // Creating POST Request
+ JSONObject wifiFingerPrint = new JSONObject();
+ wifiFingerPrint.put("wf", wifiAccessPoints);
+ JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
+ Request.Method.POST, "https://openpositioning.org/api/position/fine", wifiFingerPrint,
+ // Parses the response to obtain the WiFi location and WiFi floor
+ response -> {
+ try {
+ wifilocation.set(new LatLng(response.getDouble("lat"), response.getDouble("lon")));
+ floor.set(response.getInt("floor"));
+ } catch (JSONException e) {
+ // Error log to keep record of errors (for secure programming and maintainability)
+ Log.e("jsonErrors", "Error parsing response: " + e.getMessage() + " " + response);
+ }
+ // Block return when a message is received
+ synchronized (TrajParser.class) {
+ TrajParser.class.notify();
+ }
+ },
+ // Handles the errors obtained from the POST request
+ error -> {
+ // Validation Error
+ if (error.networkResponse != null && error.networkResponse.statusCode == 422) {
+ Log.e("WiFiPositioning", "Validation Error " + error.getMessage());
+ }
+ // Other Errors
+ else {
+ // When Response code is available
+ if (error.networkResponse != null) {
+ Log.e("WiFiPositioning", "Response Code: " + error.networkResponse.statusCode + ", " + error.getMessage());
+ } else {
+ Log.e("WiFiPositioning", "Error message: " + error.getMessage());
+ }
+ }
+ // Block return when an error occurs
+ synchronized (TrajParser.class) {
+ TrajParser.class.notify();
+ }
+ }
+ );
+
+ // Add the request to the RequestQueue
+ RequestQueue requestQueue = Volley.newRequestQueue(context);
+ requestQueue.add(jsonObjectRequest);
+
+ } 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());
+ }
+
+ // Wait for the response or error to be processed
+ synchronized (TrajParser.class) {
+ try {
+ TrajParser.class.wait(1000); // Sleep for 1 second
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Thread interrupted while waiting for WiFi positioning response", e);
+ }
+ }
- result.add(new ReplayPoint(pdrLocation, gnssLocation, orientationDeg,
+ result.add(new ReplayPoint(pdrLocation, gnssLocation, wifilocation.get(), orientationDeg,
0f, pdr.relativeTimestamp));
}
@@ -229,17 +326,31 @@ private static List parseGnssData(JsonArray gnssArray) {
gnssList.add(record);
}
return gnssList;
-}/** Finds the closest IMU record to the given timestamp. */
+}/**
+ * Finds the closest IMU record to the given timestamp.
+ */
+private static List parseWiFiData(JsonArray WiFiArray) {
+ List WiFiList = new ArrayList<>();
+ if (WiFiArray == null) return WiFiList;
+ Gson gson = new Gson();
+ for (int i = 0; i < WiFiArray.size(); i++) {
+ WiFiRecord record = gson.fromJson(WiFiArray.get(i), WiFiRecord.class);
+ WiFiList.add(record);
+ }
+ return WiFiList;
+}
private static ImuRecord findClosestImuRecord(List imuList, long targetTimestamp) {
return imuList.stream().min(Comparator.comparingLong(imu -> Math.abs(imu.relativeTimestamp - targetTimestamp)))
.orElse(null);
-
}/** Finds the closest GNSS record to the given timestamp. */
private static GnssRecord findClosestGnssRecord(List gnssList, long targetTimestamp) {
return gnssList.stream().min(Comparator.comparingLong(gnss -> Math.abs(gnss.relativeTimestamp - targetTimestamp)))
.orElse(null);
-
}/** Computes the orientation from a rotation vector. */
+private static WiFiRecord findClosestWiFiRecord(List WiFiList, long targetTimestamp) {
+ return WiFiList.stream().min(Comparator.comparingLong(wifi -> Math.abs(wifi.relativeTimestamp - targetTimestamp)))
+ .orElse(null);
+}
private static float computeOrientationFromRotationVector(float rx, float ry, float rz, float rw, Context context) {
float[] rotationVector = new float[]{rx, ry, rz, rw};
float[] rotationMatrix = new float[9];
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..5dfccd0d 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
@@ -265,6 +265,16 @@ private void updateUIandPosition() {
}
}
+ // WiFi location example
+ //sensorFusion.getWifiFloor() // Returns the current floor the user is on using WiFi positioning.
+ if(trajectoryMapFragment.isWiFiLocationOn()){
+ LatLng wifiLocation = sensorFusion.getLatLngWifiPositioning(); // Returns the user's position obtained using WiFi positioning.
+ if(wifiLocation != null) {
+ trajectoryMapFragment.updateWifiLocation(wifiLocation);
+ }
+ }
+ //sensorFusion.getWifiList() // Returns the most recent list of WiFi names and levels.
+
// Update previous
previousPosX = pdrValues[0];
previousPosY = pdrValues[1];
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..86dbc269 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
@@ -18,6 +18,7 @@
import com.openpositioning.PositionMe.R;
import com.openpositioning.PositionMe.presentation.activity.ReplayActivity;
import com.openpositioning.PositionMe.data.local.TrajParser;
+import com.openpositioning.PositionMe.sensors.WiFiPositioning;
import java.io.File;
import java.util.ArrayList;
@@ -65,6 +66,7 @@ public class ReplayFragment extends Fragment {
private int currentIndex = 0;
private boolean isPlaying = false;
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -134,18 +136,22 @@ public void onViewCreated(@NonNull View view,
// 1) Check if the file contains any GNSS data
- boolean gnssExists = hasAnyGnssData(replayData);
+// boolean gnssExists = hasAnyGnssData(replayData);
+//
+// if (gnssExists) {
+// showGnssChoiceDialog();
+// } else {
+// // No GNSS data -> automatically use param lat/lon
+// if (initialLat != 0f || initialLon != 0f) {
+// LatLng startPoint = new LatLng(initialLat, initialLon);
+// Log.i(TAG, "Setting initial map position: " + startPoint.toString());
+// trajectoryMapFragment.setInitialCameraPosition(startPoint);
+// }
+// }
+ // Set initial map position
+ setupInitialMapPosition();
+
- if (gnssExists) {
- showGnssChoiceDialog();
- } else {
- // No GNSS data -> automatically use param lat/lon
- if (initialLat != 0f || initialLon != 0f) {
- LatLng startPoint = new LatLng(initialLat, initialLon);
- Log.i(TAG, "Setting initial map position: " + startPoint.toString());
- trajectoryMapFragment.setInitialCameraPosition(startPoint);
- }
- }
// Initialize UI controls
playPauseButton = view.findViewById(R.id.playPauseButton);
@@ -234,14 +240,16 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/**
* Checks if any ReplayPoint contains a non-null gnssLocation.
*/
- private boolean hasAnyGnssData(List data) {
- for (TrajParser.ReplayPoint point : data) {
- if (point.gnssLocation != null) {
- return true;
- }
- }
- return false;
- }
+// private boolean hasAnyGnssData(List data) {
+// for (TrajParser.ReplayPoint point : data) {
+// if (point.gnssLocation != null) {
+// return true;
+// }
+// }
+// return false;
+// }
+
+
/**
@@ -249,44 +257,74 @@ private boolean hasAnyGnssData(List data) {
* 1) GNSS from file
* 2) Lat/Lon from ReplayActivity arguments
*/
- private void showGnssChoiceDialog() {
- new AlertDialog.Builder(requireContext())
- .setTitle("Choose Starting Location")
- .setMessage("GNSS data is found in the file. Would you like to use the file's GNSS as the start, or the one you manually picked?")
- .setPositiveButton("Use File's GNSS", (dialog, which) -> {
- LatLng firstGnss = getFirstGnssLocation(replayData);
- if (firstGnss != null) {
- setupInitialMapPosition((float) firstGnss.latitude, (float) firstGnss.longitude);
- } else {
- // Fallback if no valid GNSS found
- setupInitialMapPosition(initialLat, initialLon);
- }
- dialog.dismiss();
- })
- .setNegativeButton("Use Manual Set", (dialog, which) -> {
- setupInitialMapPosition(initialLat, initialLon);
- dialog.dismiss();
- })
- .setCancelable(false)
- .show();
- }
- private void setupInitialMapPosition(float latitude, float longitude) {
- LatLng startPoint = new LatLng(initialLat, initialLon);
+// private void showGnssChoiceDialog() {
+// new AlertDialog.Builder(requireContext())
+// .setTitle("Choose Starting Location")
+// .setMessage("GNSS data is found in the file. Would you like to use the file's GNSS as the start, or the one you manually picked?")
+// .setPositiveButton("Use File's GNSS", (dialog, which) -> {
+// LatLng firstGnss = getFirstGnssLocation(replayData);
+// if (firstGnss != null) {
+// setupInitialMapPosition((float) firstGnss.latitude, (float) firstGnss.longitude);
+// } else {
+// // Fallback if no valid GNSS found
+// setupInitialMapPosition(initialLat, initialLon);
+// }
+// dialog.dismiss();
+// })
+// .setNegativeButton("Use Manual Set", (dialog, which) -> {
+// setupInitialMapPosition(initialLat, initialLon);
+// dialog.dismiss();
+// })
+// .setCancelable(false)
+// .show();
+// }
+
+
+// private void setupInitialMapPosition(float latitude, float longitude) {
+// LatLng startPoint = new LatLng(initialLat, initialLon);
+// Log.i(TAG, "Setting initial map position: " + startPoint.toString());
+// trajectoryMapFragment.setInitialCameraPosition(startPoint);
+// }
+ private void setupInitialMapPosition() {
+ LatLng startPoint = getFirstGnssLocation(replayData);
+ if (startPoint == null) {
+ startPoint = getFirstWifiLocation(replayData);
+ }
+ if (startPoint == null) {
+ startPoint = new LatLng(initialLat, initialLon);
+ }
Log.i(TAG, "Setting initial map position: " + startPoint.toString());
trajectoryMapFragment.setInitialCameraPosition(startPoint);
}
+
/**
* Retrieve the first available GNSS location from the replay data.
*/
+// 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 null; // None found
+// }
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
+ return null;
+ }
+ private LatLng getFirstWifiLocation(List data) {
+ for (TrajParser.ReplayPoint point : data) {
+ if (point.wifiLocation != null) {
+ return point.wifiLocation;
+ }
+ }
+ return null;
}
@@ -337,6 +375,10 @@ private void updateMapForIndex(int newIndex) {
if (p.gnssLocation != null) {
trajectoryMapFragment.updateGNSS(p.gnssLocation);
}
+ // 添加 WiFi 定位更新
+ if (p.wifiLocation != null) {
+ trajectoryMapFragment.updateWifiLocation(p.wifiLocation);
+ }
}
} else {
// Normal sequential forward step: add just the new point
@@ -345,6 +387,10 @@ private void updateMapForIndex(int newIndex) {
if (p.gnssLocation != null) {
trajectoryMapFragment.updateGNSS(p.gnssLocation);
}
+ //添加 WiFi 定位更新
+ if (p.wifiLocation != null) {
+ trajectoryMapFragment.updateWifiLocation(p.wifiLocation);
+ }
}
lastIndex = newIndex;
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..245d4616 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
@@ -57,12 +57,16 @@ public class TrajectoryMapFragment extends Fragment {
private LatLng currentLocation; // Stores the user's current location
private Marker orientationMarker; // Marker representing user's heading
private Marker gnssMarker; // GNSS position marker
+ private Marker wifiMarker;
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 boolean isWiFiLocationOn = false;
private Polyline gnssPolyline; // Polyline for GNSS path
+ private Polyline wifiPolyline; // Polyline for WiFi path
private LatLng lastGnssLocation = null; // Stores the last GNSS location
+ private LatLng lastwifiLocation = null;
private LatLng pendingCameraPosition = null; // Stores pending camera movement
private boolean hasPendingCameraMove = false; // Tracks if camera needs to move
@@ -75,6 +79,7 @@ public class TrajectoryMapFragment extends Fragment {
private Spinner switchMapSpinner;
private SwitchMaterial gnssSwitch;
+ private SwitchMaterial WiFiLoactionSwitch;
private SwitchMaterial autoFloorSwitch;
private com.google.android.material.floatingactionbutton.FloatingActionButton floorUpButton, floorDownButton;
@@ -103,6 +108,7 @@ public void onViewCreated(@NonNull View view,
// Grab references to UI controls
switchMapSpinner = view.findViewById(R.id.mapSwitchSpinner);
gnssSwitch = view.findViewById(R.id.gnssSwitch);
+ WiFiLoactionSwitch = view.findViewById(R.id.wifiSwitch);
autoFloorSwitch = view.findViewById(R.id.autoFloor);
floorUpButton = view.findViewById(R.id.floorUpButton);
floorDownButton = view.findViewById(R.id.floorDownButton);
@@ -151,6 +157,15 @@ public void onMapReady(@NonNull GoogleMap googleMap) {
}
});
+ // wifi location display做了一个wifi开启的开关,可以开启或关闭使用wifi
+ WiFiLoactionSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ isWiFiLocationOn = isChecked;
+ if (!isChecked && wifiMarker != null) {
+ wifiMarker.remove();
+ wifiMarker = null;
+ }
+ });
+
// Color switch
switchColorButton.setOnClickListener(v -> {
if (polyline != null) {
@@ -226,6 +241,12 @@ private void initMapSettings(GoogleMap map) {
.width(5f)
.add() // start empty
);
+ // WiFi path in yellow
+ wifiPolyline = map.addPolyline(new PolylineOptions()
+ .color(Color.YELLOW)
+ .width(5f)
+ .add() // start empty
+ );
}
@@ -388,6 +409,34 @@ public void updateGNSS(@NonNull LatLng gnssLocation) {
}
}
+ /**
+ * Set/update WiFi position based on GNSS
+ */
+ public void updateWifiLocation(@NonNull LatLng wifiLocation) {
+ if (gMap == null) return;
+ if (!isWiFiLocationOn) return;
+
+ // Create a marker for WiFi location
+ if (wifiMarker == null){
+ wifiMarker = gMap.addMarker(new MarkerOptions()
+ .position(wifiLocation)
+ .title("WiFi Position")
+ .icon(BitmapDescriptorFactory
+ .defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)));
+ lastwifiLocation = wifiLocation;
+ } else {
+ // Move existing WiFi marker
+ wifiMarker.setPosition(wifiLocation);
+
+ // Add a segment to the yellow WiFi line, if this is a new location
+ if (lastwifiLocation != null && !lastwifiLocation.equals(wifiLocation)) {
+ List wifiPoints = new ArrayList<>(wifiPolyline.getPoints());
+ wifiPoints.add(wifiLocation);
+ wifiPolyline.setPoints(wifiPoints);
+ }
+ lastwifiLocation = wifiLocation;
+ }
+ }
/**
* Remove GNSS marker if user toggles it off
@@ -406,6 +455,10 @@ public boolean isGnssEnabled() {
return isGnssOn;
}
+ public boolean isWiFiLocationOn(){
+ return isWiFiLocationOn;
+ }
+
private void setFloorControlsVisibility(int visibility) {
floorUpButton.setVisibility(visibility);
floorDownButton.setVisibility(visibility);
@@ -421,6 +474,10 @@ public void clearMapAndReset() {
gnssPolyline.remove();
gnssPolyline = null;
}
+ if (wifiPolyline != null) {
+ wifiPolyline.remove();
+ wifiPolyline = null;
+ }
if (orientationMarker != null) {
orientationMarker.remove();
orientationMarker = null;
@@ -430,6 +487,7 @@ public void clearMapAndReset() {
gnssMarker = null;
}
lastGnssLocation = null;
+ lastwifiLocation = null;
currentLocation = null;
// Re-create empty polylines with your chosen colors
@@ -442,6 +500,10 @@ public void clearMapAndReset() {
.color(Color.BLUE)
.width(5f)
.add());
+ wifiPolyline = gMap.addPolyline(new PolylineOptions()
+ .color(Color.YELLOW)
+ .width(5f)
+ .add());
}
}
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..dd1efb7e 100644
--- a/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java
+++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/SensorFusion.java
@@ -52,10 +52,37 @@
*
* The class provides a number of setters and getters so that other classes can have access to the
* sensor data and influence the behaviour of data collection.
- *
+ *
+ *
+ * functions in this class:
+ *
+ * - {@link #getInstance()} - Returns the singleton instance of SensorFusion.
+ * - {@link #setContext(Context)} - Initializes the SensorFusion instance with the application context.
+ * - {@link #resumeListening()} - Registers all device listeners and starts data collection.
+ * - {@link #stopListening()} - Unregisters all device listeners and pauses data collection.
+ * - {@link #startRecording()} - Enables saving sensor values to the trajectory object.
+ * - {@link #stopRecording()} - Disables saving sensor values to the trajectory object.
+ * - {@link #sendTrajectoryToCloud()} - Sends the trajectory object to the server.
+ * - {@link #getGNSSLatitude(boolean)} - Returns the GNSS latitude and longitude data.
+ * - {@link #setStartGNSSLatitude(float[])} - Sets the initial GNSS latitude and longitude data.
+ * - {@link #redrawPath(float)} - Redraws the path in the corrections fragment.
+ * - {@link #passAverageStepLength()} - Returns the average step length calculated by PDR.
+ * - {@link #passOrientation()} - Returns the current device orientation.
+ * - {@link #getSensorValueMap()} - Returns a map of the most recent sensor readings.
+ * - {@link #getWifiList()} - Returns the most recent list of WiFi names and levels.
+ * - {@link #getSensorInfos()} - Returns information about all the sensors registered in SensorFusion.
+ * - {@link #registerForServerUpdate(Observer)} - Registers an observer to receive updates from the server.
+ * - {@link #getElevation()} - Returns the estimated elevation value in meters.
+ * - {@link #getElevator()} - Returns whether the user is currently in an elevator.
+ * - {@link #getHoldMode()} - Estimates the phone's position based on proximity and light sensors.
+ * - {@link #getLatLngWifiPositioning()} - Returns the user's position obtained using WiFi positioning.
+ * - {@link #getWifiFloor()} - Returns the current floor the user is on using WiFi positioning.
+ *
+ *
* @author Michal Dvorak
* @author Mate Stodulka
* @author Virginia Cangelosi
+ * @author JackShenYt
*/
public class SensorFusion implements SensorEventListener, Observer {
@@ -159,6 +186,15 @@ public class SensorFusion implements SensorEventListener, Observer {
// WiFi positioning object
private WiFiPositioning wiFiPositioning;
+ //km filter
+ private final Timer fuseTimer = new Timer();
+
+ // Add gyroOrientation and accMagOrientation arrays
+ private final float[] gyroOrientation = new float[3];
+ private final float[] accMagOrientation = new float[3];
+ private float[] gyroMatrix = new float[9];
+ private final float[] fusedOrientation = new float[3];
+
//region Initialisation
/**
* Private constructor for implementing singleton design pattern for SensorFusion.
@@ -191,8 +227,14 @@ private SensorFusion() {
this.R = new float[9];
// GNSS initial Long-Lat array
this.startLocation = new float[2];
- }
+ // Initialize gyroMatrix in the constructor
+ gyroOrientation[0] = 0.0f;
+ gyroOrientation[1] = 0.0f;
+ gyroOrientation[2] = 0.0f;
+
+ gyroMatrix = new float[]{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
+ }
/**
* Static function to access singleton instance of SensorFusion.
@@ -259,6 +301,10 @@ public void setContext(Context context) {
// 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");
+
+ //kalmanFilter service
+ fuseTimer.schedule(new calculateFusedOrientationTask(), 5000, 30);
+ Log.d("SensorFusionService", "Service started");
}
//endregion
@@ -284,11 +330,11 @@ public void onSensorChanged(SensorEvent sensorEvent) {
if (lastTimestamp != null) {
long timeGap = currentTime - lastTimestamp;
-// // Log a warning if the time gap is larger than the threshold
-// if (timeGap > LARGE_GAP_THRESHOLD_MS) {
-// Log.e("SensorFusion", "Large time gap detected for sensor " + sensorType +
-// " | Time gap: " + timeGap + " ms");
-// }
+ // // 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");
+ // }
}
// Update timestamp and frequency counter for this sensor
@@ -302,6 +348,7 @@ public void onSensorChanged(SensorEvent sensorEvent) {
acceleration[0] = sensorEvent.values[0];
acceleration[1] = sensorEvent.values[1];
acceleration[2] = sensorEvent.values[2];
+ calculateAccMagOrientation();
break;
case Sensor.TYPE_PRESSURE:
@@ -317,6 +364,8 @@ public void onSensorChanged(SensorEvent sensorEvent) {
angularVelocity[0] = sensorEvent.values[0];
angularVelocity[1] = sensorEvent.values[1];
angularVelocity[2] = sensorEvent.values[2];
+ gyroFunction(sensorEvent);
+ break;
case Sensor.TYPE_LINEAR_ACCELERATION:
filteredAcc[0] = sensorEvent.values[0];
@@ -330,12 +379,8 @@ public void onSensorChanged(SensorEvent sensorEvent) {
Math.pow(filteredAcc[2], 2)
);
this.accelMagnitude.add(accelMagFiltered);
-
-// // Debug logging
-// Log.v("SensorFusion",
-// "Added new linear accel magnitude: " + accelMagFiltered
-// + "; accelMagnitude size = " + accelMagnitude.size());
-
+ // Debug logging
+ //Log.v("SensorFusion", "Added new linear accel magnitude: " + accelMagFiltered + "; accelMagnitude size = " + accelMagnitude.size());
elevator = pdrProcessing.estimateElevator(gravity, filteredAcc);
break;
@@ -396,7 +441,7 @@ public void onSensorChanged(SensorEvent sensorEvent) {
float[] newCords = this.pdrProcessing.updatePdr(
stepTime,
this.accelMagnitude,
- this.orientation[0]
+ this.fusedOrientation[0] //km filter
);
// Clear the accelMagnitude after using it
@@ -506,6 +551,7 @@ private void createWifiPositioningRequest(){
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
@@ -620,6 +666,32 @@ private float[] matrixMultiplication(float[] A, float[] B) {
return result;
}
+ /**
+ * KalmanFilter time irs
+ */
+ class calculateFusedOrientationTask extends TimerTask {
+ private final kalmanfilter[] kalmanFilters = new kalmanfilter[3];
+
+ public calculateFusedOrientationTask() {
+ // Initialize one Kalman filter for each axis (yaw, pitch, roll)
+ for (int i = 0; i < 3; i++) {
+ kalmanFilters[i] = new kalmanfilter();
+ }
+ }
+
+ public void run() {
+ float dt = 0.03f; // Assume 33Hz update rate
+
+ for (int i = 0; i < 3; i++) {
+ fusedOrientation[i] = kalmanFilters[i].update(accMagOrientation[i], gyroOrientation[i], dt);
+ }
+
+ // Update gyro matrix with the fused orientation
+ gyroMatrix = getRotationMatrixFromOrientation(fusedOrientation);
+ System.arraycopy(fusedOrientation, 0, gyroOrientation, 0, 3);
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -968,10 +1040,10 @@ public void run() {
.setMagY(magneticField[1])
.setMagZ(magneticField[2])
.setRelativeTimestamp(SystemClock.uptimeMillis()-bootTime))
-// .addGnssData(Traj.GNSS_Sample.newBuilder()
-// .setLatitude(latitude)
-// .setLongitude(longitude)
-// .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
@@ -997,6 +1069,8 @@ public void run() {
.setMac(currentWifi.getBssid())
.setSsid(currentWifi.getSsid())
.setFrequency(currentWifi.getFrequency()));
+
+ //trajectory.addAllWifiData
}
else {
secondCounter++;
@@ -1011,4 +1085,50 @@ public void run() {
//endregion
+ // Add calculateAccMagOrientation method
+ private void calculateAccMagOrientation() {
+ if (SensorManager.getRotationMatrix(R, null, acceleration, magneticField)) {
+ SensorManager.getOrientation(R, accMagOrientation);
+ }
+ }
+
+ // Add gyroFunction method
+ private static final float EPSILON = 0.000000001f;
+ private void gyroFunction(SensorEvent event) {
+ if (gyroMatrix == null) return;
+
+ float[] deltaVector = new float[4];
+ if (lastEventTimestamps.containsKey(Sensor.TYPE_GYROSCOPE)) {
+ float dT = (event.timestamp - lastEventTimestamps.get(Sensor.TYPE_GYROSCOPE)) * (1.0f / 1_000_000_000.0f);
+ getRotationVectorFromGyro(event.values, deltaVector, dT / 2.0f);
+ }
+
+ float[] deltaMatrix = new float[9];
+ SensorManager.getRotationMatrixFromVector(deltaMatrix, deltaVector);
+ gyroMatrix = matrixMultiplication(gyroMatrix, deltaMatrix);
+ SensorManager.getOrientation(gyroMatrix, gyroOrientation);
+ }
+
+ // Add getRotationVectorFromGyro method
+ private void getRotationVectorFromGyro(float[] gyroValues, float[] deltaRotationVector, float timeFactor) {
+ float omegaMagnitude = (float) Math.sqrt(gyroValues[0] * gyroValues[0] +
+ gyroValues[1] * gyroValues[1] +
+ gyroValues[2] * gyroValues[2]);
+
+ if (omegaMagnitude > EPSILON) {
+ gyroValues[0] /= omegaMagnitude;
+ gyroValues[1] /= omegaMagnitude;
+ gyroValues[2] /= omegaMagnitude;
+ }
+
+ float thetaOverTwo = omegaMagnitude * timeFactor;
+ float sinThetaOverTwo = (float) Math.sin(thetaOverTwo);
+ float cosThetaOverTwo = (float) Math.cos(thetaOverTwo);
+
+ deltaRotationVector[0] = sinThetaOverTwo * gyroValues[0];
+ deltaRotationVector[1] = sinThetaOverTwo * gyroValues[1];
+ deltaRotationVector[2] = sinThetaOverTwo * gyroValues[2];
+ deltaRotationVector[3] = cosThetaOverTwo;
+ }
+
}
diff --git a/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java b/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java
new file mode 100644
index 00000000..87199ce1
--- /dev/null
+++ b/app/src/main/java/com/openpositioning/PositionMe/sensors/kalmanfilter.java
@@ -0,0 +1,57 @@
+package com.openpositioning.PositionMe.sensors;
+
+public class kalmanfilter {
+ private float theta; // Estimated angle (θ)
+ private float thetaDotBias; // Bias in angular rate (θ̇b)
+
+ private float p00, p01, p10, p11; // Error covariance matrix
+
+ // Constructor
+ public kalmanfilter() {
+ this.theta = 0.0f; // Initial estimated angle
+ this.thetaDotBias = 0.0f; // Initial bias
+
+ this.p00 = 1.0f; // Initial error covariance
+ this.p01 = 0.0f;
+ this.p10 = 0.0f;
+ this.p11 = 1.0f;
+ }
+
+ public float update(float accMagAngle, float gyroAngle, float dt) {
+ // Prediction step
+ theta += gyroAngle * dt;
+ theta -= thetaDotBias * dt;
+
+ // Process noise variance for the angle
+ float qAngle = 0.01f;
+ p00 += dt * (dt * p11 - p01 - p10 + qAngle);
+ p01 -= dt * p11;
+ p10 -= dt * p11;
+
+ // Process noise variance for the gyro bias
+ float qBias = 0.003f;
+ p11 += qBias * dt;
+
+ // Update step
+ float z = accMagAngle - theta;
+
+ // Measurement noise variance
+ float rMeasure = 0.01f;
+ float k0 = p00 / (p00 + rMeasure);
+ float k1 = p10 / (p00 + rMeasure);
+
+ theta += k0 * z;
+ thetaDotBias += k1 * z;
+
+ // Update covariance matrix
+ float tempP00 = p00;
+ float tempP01 = p01;
+
+ p00 -= k0 * tempP00;
+ p01 -= k0 * tempP01;
+ p10 -= k1 * tempP00;
+ p11 -= k1 * tempP01;
+
+ return theta;
+ }
+}
diff --git a/app/src/main/res/layout/fragment_trajectory_map.xml b/app/src/main/res/layout/fragment_trajectory_map.xml
index a8ab9118..0573e232 100644
--- a/app/src/main/res/layout/fragment_trajectory_map.xml
+++ b/app/src/main/res/layout/fragment_trajectory_map.xml
@@ -37,6 +37,12 @@
android:layout_height="wrap_content"
android:text="@string/gnssSwitch" />
+
+
Floor height in meters
Color
🛰 Show GNSS
+ 📶WiFi Loc
Floor Down button
Floor Up button
Choose Map