1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi;
18 
19 import android.location.Location;
20 import android.util.Log;
21 
22 import org.json.JSONObject;
23 
24 import java.util.Random;
25 
26 /**
27  * Helper class to store a coarse ellipse location used to send to the AFC server. Randomizes the
28  * center point of the ellipse with a center leeway parameter.
29  */
30 public class AfcEllipseLocation extends AfcLocation {
31     private static final String TAG = "WifiAfcEllipseLocation";
32     static final double ONE_DEGREE_LONGITUDE_IN_METERS = 111139.0; // Used in double division
33     static final int DEFAULT_SEMI_MINOR_AXIS_METERS = 500;
34     static final int DEFAULT_SEMI_MAJOR_AXIS_METERS = 500;
35     static final float DEFAULT_ORIENTATION = 90f;
36     static final double DEFAULT_CENTER_LEEWAY_DEGREES = 0.001;
37     static final int MIN_LONGITUDE = -180;
38     static final int MAX_LONGITUDE = 180;
39     static final int MIN_LATITUDE = -90;
40     static final int MAX_LATITUDE = 90;
41     double mLatitude;
42     double mLongitude;
43     int mSemiMajorAxis;
44     int mSemiMinorAxis;
45     double mOrientation;
46     double mCenterLeeway;
47 
48     /**
49      * @param semiMinorAxisMeters The length of the semi major axis of an ellipse which is a
50      *                            positive integer in meters.
51      * @param semiMajorAxisMeters The length of the semi minor axis of an ellipse which is a
52      *                            positive integer in meters.
53      * @param orientation         The orientation of the majorAxis field in decimal degrees,
54      *                            measured clockwise from True North.
55      * @param centerLeeway        The max amount in degrees that the longitude and latitude
56      *                            coordinates of the generated ellipse will be randomly shifted +-
57      *                            by. This variable should be reasonably smaller than the longitude
58      *                            and latitude ranges. Each 0.001 degree is roughly 111 meters.
59      * @param random              The random variable to randomize the center ellipse coordinates.
60      * @param location            The geographic coordinates within which the AP or Fixed Client
61      *                            Device is located and the AfcEllipseLocation is centered around.
62      */
AfcEllipseLocation(int semiMinorAxisMeters, int semiMajorAxisMeters, double orientation, double centerLeeway, Random random, Location location)63     public AfcEllipseLocation(int semiMinorAxisMeters, int semiMajorAxisMeters, double orientation,
64             double centerLeeway, Random random, Location location) {
65         super(location);
66 
67         mSemiMajorAxis = semiMajorAxisMeters;
68         mSemiMinorAxis = semiMinorAxisMeters;
69         mCenterLeeway = centerLeeway;
70         mOrientation = orientation;
71 
72         // The ellipse longitude point is in between the location longitude point +- mCenterLeeway
73         mLongitude = random.nextDouble() * (2 * mCenterLeeway) + (location.getLongitude()
74                 - mCenterLeeway);
75 
76         // Adjust for allowed range for longitude which is -180 to 180.
77         if (mLongitude < MIN_LONGITUDE) {
78             mLongitude = MIN_LONGITUDE;
79         } else if (mLongitude > MAX_LONGITUDE) {
80             mLongitude = MAX_LONGITUDE;
81         }
82 
83         // The ellipse latitude point is in between the location latitude point +- mCenterLeeway
84         mLatitude = random.nextDouble() * (2 * mCenterLeeway) + (location.getLatitude()
85                 - mCenterLeeway);
86 
87         // Adjust for allowed range for latitude which is -90 to 90.
88         if (mLatitude < MIN_LATITUDE) {
89             mLatitude = MIN_LATITUDE;
90         } else if (mLatitude > MAX_LATITUDE) {
91             mLatitude = MAX_LATITUDE;
92         }
93     }
94 
95     /**
96      * Create a location JSONObject that has the ellipse fields for AFC server queries.
97      */
98     @Override
toJson()99     public JSONObject toJson() {
100         try {
101             JSONObject location = new JSONObject();
102             JSONObject ellipse = new JSONObject();
103             JSONObject center = new JSONObject();
104 
105             center.put("latitude", mLatitude);
106             center.put("longitude", mLongitude);
107             ellipse.put("center", center);
108             ellipse.put("majorAxis", mSemiMajorAxis);
109             ellipse.put("minorAxis", mSemiMinorAxis);
110             ellipse.put("orientation", mOrientation);
111             location.put("ellipse", ellipse);
112 
113             JSONObject elevation = new JSONObject();
114             elevation.put("height", mHeight);
115             elevation.put("verticalUncertainty", mVerticalUncertainty);
116             elevation.put("height_type", mHeightType);
117             location.put("elevation", elevation);
118             location.put("indoorDeployment", mLocationType);
119 
120             return location;
121         } catch (Exception e) {
122             Log.e(TAG, "Encountered error when building JSON object: " + e);
123             return null;
124         }
125     }
126 
127     /**
128      * Determine if location comparingLocation is within bounds of this AfcEllipseLocation object.
129      */
130     @Override
checkLocation(Location comparingLocation)131     public AfcLocationUtil.InBoundsCheckResult checkLocation(Location comparingLocation) {
132         double comparingLatitude = comparingLocation.getLatitude();
133         double comparingLongitude = comparingLocation.getLongitude();
134         AfcLocationUtil.InBoundsCheckResult inBoundsResult;
135 
136         double semiMinorAxisDegrees = mSemiMinorAxis / ONE_DEGREE_LONGITUDE_IN_METERS;
137         double semiMajorAxisDegrees = mSemiMajorAxis / ONE_DEGREE_LONGITUDE_IN_METERS;
138 
139         // Adjust comparingLongitude if the ellipse goes above the maximum longitude or below the
140         // minimum longitude
141         if (mLongitude + semiMajorAxisDegrees > MAX_LONGITUDE && comparingLongitude < 0) {
142             comparingLongitude = comparingLongitude + 360;
143         } else if (mLongitude - semiMajorAxisDegrees < MIN_LONGITUDE && comparingLongitude > 0) {
144             comparingLongitude = comparingLongitude - 360;
145         }
146 
147         // Equation for circles and ellipses on the cartesian plane that have the major axis
148         // centered on the x-axis. Divide axes by 111139 to convert from meters to degrees which is
149         // the same units as coordinates.
150         double checkPoint = Math.pow((comparingLongitude - mLongitude) / semiMajorAxisDegrees, 2)
151                 + Math.pow((comparingLatitude - mLatitude) / semiMinorAxisDegrees, 2);
152 
153         if (checkPoint > 1.001) {
154             inBoundsResult = AfcLocationUtil.InBoundsCheckResult.OUTSIDE_AFC_LOCATION;
155         } else if (checkPoint < 0.999) {
156             inBoundsResult = AfcLocationUtil.InBoundsCheckResult.INSIDE_AFC_LOCATION;
157         } else {
158             inBoundsResult = AfcLocationUtil.InBoundsCheckResult.ON_BORDER;
159         }
160         return inBoundsResult;
161     }
162 }
163