-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMapcode.java.html
More file actions
429 lines (390 loc) · 21.6 KB
/
Mapcode.java.html
File metadata and controls
429 lines (390 loc) · 21.6 KB
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Mapcode.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Mapcode Java Library</a> > <a href="index.source.html" class="el_package">com.mapcode</a> > <span class="el_source">Mapcode.java</span></div><h1>Mapcode.java</h1><pre class="source lang-java linenums">/*
* Copyright (C) 2014-2017, Stichting Mapcode Foundation (http://www.mapcode.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mapcode;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.mapcode.CheckArgs.checkMapcodeCode;
import static com.mapcode.CheckArgs.checkNonnull;
/**
* This class defines a single mapcode encoding result, including the alphanumeric code and the
* territory definition.
*
* On terminology, mapcode territory and mapcode code:
*
* In written form. a mapcode is defined as an alphanumeric code, optionally preceded by a
* territory code.
*
* For example: "NLD 49.4V" is a mapcode, but "49.4V" is a mapcode as well, The latter is called
* a "local" mapcode, because it is not internationally unambiguous unless preceded by a territory
* code.
*
* For "NLD 49.4V" the "NLD"-part is called "the territory" and the "49.4V"-part is called
* "the code" (which are both part of "the mapcode").
*
* This distinction between "territory" and "code" in a mapcode is why the interface of this class
* has been changed from version 1.50.0 to reflect this terminology.
*
* On alphabets:
*
* Mapcode codes can be represented in different alphabets. Note that an alphabet is something else
* than a locale or a language. The supported alphabets for mapcodes are listed in {@link Alphabet}.
*
* Mapcode objects provide methods to obtain the mapcode code in a specific alphabet. By default,
* the {@link Alphabet#ROMAN} is used.
*/
<span class="pc bpc" id="L55" title="1 of 2 branches missed.">public final class Mapcode {</span>
@Nonnull
private final Territory territory;
@Nonnull
private final String codePrecision8; // Internally, codes are always stored at precision 8.
/**
* Create a mapcode object. Normally, mapcodes are created be encoding a lat/lon pair
* using {@link MapcodeCodec#encode(double, double)} rather than creating them yourself.
*
* Note that it is possible to create invalid mapcodes this way, which are syntactically
* correct.
*
* Note that the constructor will throw an {@link IllegalArgumentException} if the syntax of the mapcode
* is not correct. The mapcode is not checked for validity, other than its syntax.
*
* @param code Code of mapcode.
* @param territory Territory.
* @throws IllegalArgumentException Thrown if syntax not valid or if the mapcode string contains
* territory information.
*/
public Mapcode(@Nonnull final String code,
<span class="fc" id="L79"> @Nonnull final Territory territory) throws IllegalArgumentException {</span>
<span class="fc" id="L81"> checkMapcodeCode("code", code);</span>
<span class="fc" id="L82"> final String ascii = convertStringToPlainAscii(code);</span>
<span class="pc bpc" id="L83" title="1 of 2 branches missed."> if (containsTerritory(ascii)) {</span>
<span class="nc" id="L84"> throw new IllegalArgumentException("Must not contain territory: " + code);</span>
}
// Build codeUppercase with exactly eight precision digits.
<span class="fc" id="L88"> String codeUppercase = ascii.toUpperCase();</span>
<span class="fc" id="L89"> final int hyphenPos = codeUppercase.indexOf('-');</span>
<span class="pc bpc" id="L90" title="1 of 2 branches missed."> if (hyphenPos < 0) {</span>
<span class="nc" id="L91"> codeUppercase = codeUppercase + "-K3000000";</span>
} else {
<span class="fc" id="L93"> final int extensionLength = codeUppercase.length() - 1 - hyphenPos;</span>
<span class="pc bpc" id="L94" title="1 of 2 branches missed."> if (extensionLength < 8) {</span>
<span class="nc bnc" id="L95" title="All 2 branches missed."> if ((extensionLength % 2) == 1) {</span>
// Odd extension.
<span class="nc" id="L97"> codeUppercase = codeUppercase + ("HH000000".substring(0, 8 - extensionLength));</span>
} else {
// Even extension.
<span class="nc" id="L100"> codeUppercase = codeUppercase + ("K3000000".substring(0, 8 - extensionLength));</span>
}
<span class="pc bpc" id="L102" title="1 of 2 branches missed."> } else if (extensionLength > 8) {</span>
// Cut to 8 characters.
<span class="nc" id="L104"> codeUppercase = codeUppercase.substring(0, hyphenPos + 9);</span>
}
}
<span class="fc" id="L108"> this.codePrecision8 = codeUppercase;</span>
<span class="fc" id="L109"> this.territory = territory;</span>
<span class="fc" id="L110"> }</span>
/**
* Get the Mapcode string (without territory information) with standard precision.
* The returned mapcode does not include the '-' separator and additional digits.
*
* A mapcode defines an area of approximately 10 x 10 meters (100 m2) and will decode
* to the center of that area. On average, the original coordinate will be 3.6 meters
* from this center: the average inaccuracy of a mapcode.
*
* @param alphabet Alphabet.
* @return Mapcode string.
*/
@Nonnull
public String getCode(@Nullable final Alphabet alphabet) {
<span class="fc" id="L125"> return getCode(0, alphabet);</span>
}
@Nonnull
public String getCode() {
<span class="fc" id="L130"> return getCode(0, null);</span>
}
/**
* Get the mapcode code (without territory information) with a specified precision.
* The returned mapcode includes a '-' separator and additional digits for precisions 1 to 8.
*
* The precision defines the size of a geographical area a single mapcode covers. This means It also defines
* the maximum distance to the location, a (latitude, longitude) pair, that encoded to this mapcode.
*
* Precision 0: area is approx 10 x 10 meters (100 m2); max. distance from original location less than 7.5 meters.
* Precision 1: area is approx 3.33 m2; max. distance from original location less than 1.5 meters.
* Precision 1: area is approx 0.11 m2; max. distance from original location less than 0.4 meters.
* etc. (each level reduces the area by a factor of 30)
*
* The accuracy is slightly better than the figures above, but these figures are safe assumptions.
*
* @param precision Precision. Range: 0..8.
* @param alphabet Alphabet.
* @return Mapcode code.
* @throws IllegalArgumentException Thrown if precision is out of range (must be in [0, 8]).
*/
@Nonnull
public String getCode(final int precision, @Nullable final Alphabet alphabet) {
<span class="fc bfc" id="L154" title="All 2 branches covered."> if (precision == 0) {</span>
<span class="fc" id="L155"> return convertStringToAlphabet(codePrecision8.substring(0, codePrecision8.length() - 9), alphabet);</span>
<span class="pc bpc" id="L156" title="1 of 2 branches missed."> } else if (precision <= 8) {</span>
<span class="fc" id="L157"> return convertStringToAlphabet(codePrecision8.substring(0, (codePrecision8.length() - 8) + precision),</span>
alphabet);
} else {
<span class="nc" id="L160"> throw new IllegalArgumentException("getCodePrecision: precision must be in [0, 8]");</span>
}
}
@Nonnull
public String getCode(final int precision) throws IllegalArgumentException {
<span class="fc" id="L166"> return getCode(precision, null);</span>
}
/**
* Return the full international mapcode, including the full name of the territory and the mapcode code itself.
* The format of the string is:
* full-territory-name cde
*
* Example:
* Netherlands 49.4V (regular code)
* Netherlands 49.4V-K2 (high precision code)
*
* @param precision Precision specifier. Range: [0, 8].
* @param alphabet Alphabet.
* @return Full international mapcode.
* @throws IllegalArgumentException Thrown if precision is out of range (must be in [0, 8]).
*/
@Nonnull
public String getCodeWithTerritoryFullname(final int precision, @Nullable final Alphabet alphabet) throws IllegalArgumentException {
<span class="nc" id="L185"> return territory.getFullName() + ' ' + getCode(precision, alphabet);</span>
}
@Nonnull
public String getCodeWithTerritoryFullname(final int precision) throws IllegalArgumentException {
<span class="nc" id="L190"> return getCodeWithTerritoryFullname(precision, null);</span>
}
@Nonnull
public String getCodeWithTerritoryFullname(@Nullable final Alphabet alphabet) {
<span class="nc" id="L195"> return getCodeWithTerritoryFullname(0, alphabet);</span>
}
@Nonnull
public String getCodeWithTerritoryFullname() {
<span class="nc" id="L200"> return getCodeWithTerritoryFullname(0, null);</span>
}
/**
* Return the international mapcode as a shorter version using the ISO territory codes where possible.
* International codes use a territory code "AAA".
* The format of the code is:
* short-territory-name mapcode
*
* Example:
* NLD 49.4V (regular code)
* NLD 49.4V-K2 (high-precision code)
*
* @param precision Precision specifier. Range: [0, 8].
* @param alphabet Alphabet.
* @return Short-hand international mapcode.
* @throws IllegalArgumentException Thrown if precision is out of range (must be in [0, 8]).
*/
@Nonnull
public String getCodeWithTerritory(final int precision, @Nullable final Alphabet alphabet) throws IllegalArgumentException {
<span class="fc" id="L220"> return territory.toString() + ' ' + getCode(precision, alphabet);</span>
}
@Nonnull
public String getCodeWithTerritory(final int precision) throws IllegalArgumentException {
<span class="fc" id="L225"> return getCodeWithTerritory(precision, null);</span>
}
@Nonnull
public String getCodeWithTerritory(@Nonnull final Alphabet alphabet) {
<span class="nc" id="L230"> return getCodeWithTerritory(0, alphabet);</span>
}
@Nonnull
public String getCodeWithTerritory() {
<span class="fc" id="L235"> return getCodeWithTerritory(0, null);</span>
}
/**
* Get the territory information.
*
* @return Territory information.
*/
@Nonnull
public Territory getTerritory() {
<span class="fc" id="L245"> return territory;</span>
}
/**
* These patterns and matchers are used internally in this module to match mapcodes. They are
* provided as statics to only compile these patterns once.
*/
@Nonnull
static final String REGEX_TERRITORY = "[\\p{L}\\p{N}]{2,3}+([-_][\\p{L}\\p{N}]{2,3}+)?";
@Nonnull
static final String REGEX_CODE_PREFIX = "[\\p{L}\\p{N}]{2,5}+";
@Nonnull
static final String REGEX_CODE_POSTFIX = "[\\p{L}\\p{N}]{2,4}+";
@Nonnull
static final String REGEX_CODE_PRECISION = "[-][\\p{L}\\p{N}&&[^zZ]]{1,8}+";
/**
* This patterns/regular expressions is used for checking mapcode format strings.
* They've been made public to allow others to use the correct regular expressions as well.
*/
@Nonnull
public static final String REGEX_MAPCODE = '(' + REGEX_TERRITORY + "[ ]+)?" +
REGEX_CODE_PREFIX + "[.]" + REGEX_CODE_POSTFIX + '(' + REGEX_CODE_PRECISION + ")?";
@Nonnull
<span class="fc" id="L270"> static final Pattern PATTERN_MAPCODE = Pattern.compile('^' + REGEX_MAPCODE + '$');</span>
@Nonnull
<span class="fc" id="L272"> static final Pattern PATTERN_TERRITORY = Pattern.compile('^' + REGEX_TERRITORY + ' ');</span>
@Nonnull
<span class="fc" id="L274"> static final Pattern PATTERN_PRECISION = Pattern.compile(REGEX_CODE_PRECISION + '$');</span>
/**
* This method return the mapcode type, given a mapcode string. If the mapcode string has an invalid
* format, an exception is thrown.
*
* Note that this method only checks the syntactic validity of the mapcode, the string format. It does not
* check if the mapcode is really a valid mapcode representing a position on Earth.
*
* @param mapcode Mapcode (optionally with a territory).
* @return Type of mapcode code format.
* @throws UnknownPrecisionFormatException If precision format is incorrect.
*/
public static int getPrecisionFormat(@Nonnull final String mapcode) throws UnknownPrecisionFormatException {
// First, decode to ASCII.
<span class="fc" id="L290"> final String decodedMapcode = convertStringToPlainAscii(mapcode).toUpperCase();</span>
// Syntax needs to be OK.
<span class="fc bfc" id="L293" title="All 2 branches covered."> if (!PATTERN_MAPCODE.matcher(decodedMapcode).matches()) {</span>
<span class="fc" id="L294"> throw new UnknownPrecisionFormatException(decodedMapcode + " is not a correctly formatted mapcode code; " +</span>
"the regular expression for the mapcode code syntax is: " + REGEX_MAPCODE);
}
// Precision part should be OK.
<span class="fc" id="L299"> final Matcher matcherPrecision = PATTERN_PRECISION.matcher(decodedMapcode);</span>
<span class="fc bfc" id="L300" title="All 2 branches covered."> if (!matcherPrecision.find()) {</span>
<span class="fc" id="L301"> return 0;</span>
}
<span class="fc" id="L303"> final int length = matcherPrecision.end() - matcherPrecision.start() - 1;</span>
<span class="pc bpc" id="L304" title="3 of 6 branches missed."> assert (1 <= length) && (length <= 8);</span>
<span class="fc" id="L305"> return length;</span>
}
/**
* This method provides a shortcut to checking if a mapcode string is formatted properly or not at all.
*
* @param mapcode Mapcode (optionally with a territory).
* @return True if the mapcode format, the syntax, is correct. This does not mean the mapcode code is
* actually a valid mapcode representing a location on Earth.
* @throws IllegalArgumentException If mapcode is null.
*/
public static boolean isValidMapcodeFormat(@Nonnull final String mapcode) throws IllegalArgumentException {
<span class="fc" id="L317"> checkNonnull("mapcode", mapcode);</span>
try {
// Throws an exception if the format is incorrect.
<span class="fc" id="L320"> getPrecisionFormat(mapcode.toUpperCase());</span>
<span class="fc" id="L321"> return true;</span>
<span class="fc" id="L322"> } catch (final UnknownPrecisionFormatException ignored) {</span>
<span class="fc" id="L323"> return false;</span>
}
}
/**
* Returns whether the mapcode contains territory information or not.
*
* @param mapcode Mapcode string, optionally with territory information.
* @return True if mapcode contains territory information.
* @throws IllegalArgumentException If mapcode has incorrect syntax.
*/
public static boolean containsTerritory(@Nonnull final String mapcode) throws IllegalArgumentException {
<span class="fc" id="L335"> checkMapcodeCode("mapcode", mapcode);</span>
<span class="fc" id="L336"> return PATTERN_TERRITORY.matcher(mapcode.toUpperCase().trim()).find();</span>
}
/**
* This array defines the safe maximum offset between a decoded mapcode and its original
* location used for encoding the mapcode.
*/
<span class="fc" id="L343"> private static final double[] PRECISION_0_MAX_OFFSET_METERS = {</span>
7.49, // PRECISION_0: 7.49 meters or less +/- 7.5 m
1.39, // PRECISION_1: 1.39 meters or less +/- 1.4 m
0.251, // PRECISION_2: 25.1 cm or less +/- 25 cm
0.0462, // PRECISION_3: 4.62 cm or less +/- 5 cm
0.00837, // PRECISION_4: 8.37 mm or less +/- 1 cm
0.00154, // PRECISION_5: 1.54 mm or less +/- 2 mm
0.000279, // PRECISION_6: 279 micrometer or less +/- 1/3 mm
0.0000514, // PRECISION_7: 51.4 micrometer or less +/- 1/20 mm
0.0000093 // PRECISION_8: 9.3 micrometer or less +/- 1/100 mm
};
/**
* Get a safe maximum for the distance between a decoded mapcode and its original
* location used for encoding the mapcode. The actual accuracy (resolution) of mapcodes is
* better than this, but these are safe values to use under normal circumstances.
*
* Do not make any other assumptions on these numbers than that mapcodes are never more off
* by this distance.
*
* @param precision Precision of mapcode.
* @return Maximum offset in meters.
*/
public static double getSafeMaxOffsetInMeters(final int precision) {
<span class="pc bpc" id="L367" title="2 of 4 branches missed."> if ((precision < 0) || (precision > 8)) {</span>
<span class="nc" id="L368"> throw new IllegalArgumentException("precision must be in [0, 8]");</span>
}
<span class="fc" id="L370"> return PRECISION_0_MAX_OFFSET_METERS[precision];</span>
}
/**
* Convert a string which potentially contains Unicode characters, to an ASCII variant.
*
* @param string Any string.
* @return ASCII, non-Unicode string.
*/
@Nonnull
static String convertStringToPlainAscii(@Nonnull final String string) {
<span class="fc" id="L381"> return Decoder.decodeUTF16(string.toUpperCase());</span>
}
/**
* Convert a string into the same string using a different (or the same) alphabet.
*
* @param string Any string.
* @param alphabet Alphabet to convert to, may contain Unicode characters.
* @return Converted mapcode.
* @throws IllegalArgumentException Thrown if string has incorrect syntax or if the string cannot be encoded in
* the specified alphabet.
*/
@Nonnull
static String convertStringToAlphabet(@Nonnull final String string, @Nullable final Alphabet alphabet) throws IllegalArgumentException {
<span class="fc bfc" id="L395" title="All 2 branches covered."> return (alphabet != null) ? Decoder.encodeUTF16(string.toUpperCase(), alphabet.getNumber()) :</span>
<span class="fc" id="L396"> string.toUpperCase();</span>
}
/**
* This method is defined as returning the mapcode code including its territory,
* with normal precision (precision 0).
*
* @return Mapcode, including territory and code. Plain ASCII, non-Unicode.
*/
@Nonnull
@Override
public String toString() {
<span class="fc" id="L408"> return getCodeWithTerritory();</span>
}
@Override
public int hashCode() {
<span class="nc" id="L413"> return Arrays.deepHashCode(new Object[]{codePrecision8, territory});</span>
}
@Override
public boolean equals(@Nullable final Object obj) {
<span class="pc bpc" id="L418" title="1 of 2 branches missed."> if (this == obj) {</span>
<span class="nc" id="L419"> return true;</span>
}
<span class="pc bpc" id="L421" title="1 of 2 branches missed."> if (!(obj instanceof Mapcode)) {</span>
<span class="nc" id="L422"> return false;</span>
}
<span class="fc" id="L424"> final Mapcode that = (Mapcode) obj;</span>
<span class="fc bfc" id="L425" title="All 2 branches covered."> return (this.territory == that.territory) &&</span>
<span class="fc bfc" id="L426" title="All 2 branches covered."> this.codePrecision8.equals(that.codePrecision8);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.1.201803210924</span></div></body></html>