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