File: OlcDecoder.java

package info (click to toggle)
gpsprune 26.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,824 kB
  • sloc: java: 52,154; sh: 25; makefile: 21; python: 15
file content (201 lines) | stat: -rw-r--r-- 5,957 bytes parent folder | download | duplicates (4)
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);
	}
}