1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
package tim.prune.function.olc;
import tim.prune.data.DataPoint;
import tim.prune.data.Latitude;
import tim.prune.data.Longitude;
/**
* Decoder of OLC (Open Location Code) strings
*/
public class OlcDecoder
{
/**
* Decode the given String into an OlcArea object
* @param inCode code representing an OLC, either in short form or long form
* @param inGuideLatitude latitude guide to help guess the prefix
* @param inGuideLongitude longitude guide to help guess the prefix
* @return an OlcArea object, or null if parsing failed
*/
public static OlcArea decode(String inCode, double inGuideLatitude, double inGuideLongitude)
{
if (isValidLongForm(inCode)) {
return decode(inCode);
}
if (!isValidShortForm(inCode)) {
return null;
}
return findBestArea(inCode, inGuideLatitude, inGuideLongitude);
}
public static boolean isValidLongForm(String inCode)
{
if (inCode == null) {return false;}
final int plusPos = inCode.indexOf('+');
return inCode.indexOf(' ') < 0 && inCode.indexOf(',') < 0
&& ((inCode.length() == 8 && plusPos < 0) // long form up to (but not including) the +
|| (inCode.length() > 8 && plusPos == 8)); // long form including + at position 8
}
public static boolean isValidShortForm(String inCode)
{
if (inCode == null) {return false;}
final int plusPos = inCode.indexOf('+');
return inCode.indexOf(' ') < 0 && inCode.indexOf(',') < 0
&& (inCode.length() <= 8 && plusPos == 4); // truncated form with + at position 4
}
/**
* Decode the given String into an OlcArea object
* @param inCode code representing an OLC in long form
* @return an OlcArea object, or null if parsing failed
*/
public static OlcArea decode(String inCode)
{
if (!isValidLongForm(inCode)) {
return null;
}
String code = inCode.trim().toUpperCase();
if (code.length() < 8 || code.length() > 12) {
return null;
}
double lat = 0.0, lon = 0.0;
double resolution = 400.0;
int charPos = 0;
int numSteps = 0;
boolean amPadding = false;
try
{
while (charPos < inCode.length())
{
if (charPos == 0 || charPos == 2 || charPos == 4 || charPos == 6 || charPos == 9)
{
// take next two characters, make pair, position += 2
CoordPair pair = CoordPair.decode(code.charAt(charPos), code.charAt(charPos+1));
if (pair == CoordPair.PADDING) {
amPadding = true;
}
else if (amPadding)
{
return null;
}
else
{
// Add to current lat, lon
lat += (pair.lat * resolution);
lon += (pair.lon * resolution);
numSteps++;
resolution /= 20.0;
}
charPos += 2;
}
else if (charPos == 8)
{
if (code.charAt(charPos) != '+')
{
return null;
}
charPos += 1;
}
else if (charPos == 11)
{
// take next character, make pair
CoordPair pair = CoordPair.decode(code.charAt(charPos));
// Add to current lat, lon
lat += (pair.lat * resolution);
lon += (pair.lon * resolution);
charPos += 1;
numSteps++;
resolution /= 20.0;
}
else
{
return null;
}
}
if (numSteps < 1) {
return null;
}
// Make OlcArea object and return it
lat -= 90.0;
lon -= 180.0;
if (numSteps < 6) {
return new OlcArea(lat, lon, lat+resolution, lon+resolution, inCode);
}
else {
// For 6 steps, just return a point, not a rectangle
return new OlcArea(lat, lon, lat, lon, inCode);
}
}
catch (ParseException e) {}
return null;
}
/**
* @param inCode partial OLC code (truncated form with prefix missing
* @param inGuideLatitude latitude of nearby point
* @param inGuideLongitude longitude of nearby point
* @return the nearest OlcArea to the specified guide point
*/
private static OlcArea findBestArea(String inCode, double inGuideLatitude, double inGuideLongitude)
{
// Find the two latitude indexes
double latIndex = (inGuideLatitude + 90.0) / 20.0;
int latIndex1 = (int) Math.floor(latIndex);
int latIndex2 = (int) Math.floor((latIndex - latIndex1) * 20.0);
// And the same for longitude
double lonIndex = ((inGuideLongitude + 180.0)%360.0) / 20.0;
int lonIndex1 = (int) Math.floor(lonIndex);
int lonIndex2 = (int) Math.floor((lonIndex - lonIndex1) * 20.0);
// Now we have 9 possible squares to look through:
DataPoint guidePoint = new DataPoint(Latitude.make(inGuideLatitude),
Longitude.make(inGuideLongitude));
OlcArea bestAnswer = null;
double lowestDistance = 0.0;
for (int dy=-1; dy<=1; dy++)
{
for (int dx=-1; dx<=1; dx++)
{
String longCode = String.valueOf(CoordPair.encode(latIndex1 + getDelta(latIndex2, dy)))
+ CoordPair.encode(lonIndex1 + getDelta(lonIndex2, dx))
+ CoordPair.encode(latIndex2 + dy)
+ CoordPair.encode(lonIndex2 + dx)
+ inCode;
OlcArea probeArea = decode(longCode);
if (probeArea != null)
{
double distance = calcDistance(probeArea, guidePoint);
if (bestAnswer == null || distance < lowestDistance) {
bestAnswer = probeArea;
lowestDistance = distance;
}
}
}
}
return bestAnswer;
}
/**
* @param secondIndex lower-order index
* @param increment change in lower-order index from [-1, 0, 1]
* @return delta of higher-order index caused by carry
*/
private static int getDelta(int secondIndex, int increment)
{
if (secondIndex == 0 && increment < 0) {return -1;}
if (secondIndex == 19 && increment > 0) {return 1;}
return 0;
}
/**
* @param probeArea possible OLC area
* @param inGuidePoint guide point which should be nearby
* @return distance in radians from the given guide point to the centre of the olc area
*/
private static double calcDistance(OlcArea probeArea, DataPoint inGuidePoint)
{
DataPoint probePoint = new DataPoint(Latitude.make(probeArea.middleLat()),
Longitude.make(probeArea.middleLon()));
return DataPoint.calculateRadiansBetween(probePoint, inGuidePoint);
}
}
|