Skip to content

Commit 18e784e

Browse files
committed
Fixes issue with Clipperton Island CPT/CP
1 parent d8cc5db commit 18e784e

File tree

5 files changed

+165
-93
lines changed

5 files changed

+165
-93
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,15 +296,15 @@ Again, if encoding succeeded, the first mapcode is the shortest one and the last
296296
globally unique international mapcode.
297297

298298
**`List<Mapcode> encodeRestrictToCountryISO2/ISO3(double latitude, double longitude, String countryISO2/ISO3)`** encodes a (latitude,
299-
longitude) pair, where encoding is restricted to a specific country, provided as an ISO 3166-2 or -3 country code.
299+
longitude) pair, where encoding is restricted to a specific country, provided as an ISO 3166 2 or 3 characters country code.
300300

301301
Example:
302302

303303
List<Mapcode> results = MapcodeCodec.encodeRestrictToCountryISO2(lat, lon, "BR");
304-
// Returns a list of mapcodes retricted to Brazil (not: IN-BR).
304+
// Returns a list of mapcodes retricted to Brazil, so their territories start with BR- or BRA.
305305

306306
List<Mapcode> results = MapcodeCodec.encodeRestrictToCountryISO3(lat, lon, "MEX");
307-
// Returns a list of mapcodes retricted to Mexico.
307+
// Returns a list of mapcodes retricted to Mexico, so their therritories start with MX- or MEX.
308308

309309
**Important notice:** The codes used in these methods asume the ISO conversion,
310310
not the `fromString` conversion from `Territory`. For example, `Territory.fromString("BR")`
@@ -397,7 +397,7 @@ smaller areas, so mapcodes can remain fairly short:
397397
Rather than using the 3-letter territory code for mapcodes in these territories, you'd probably want
398398
to use the `TT-XXX` form, where `XXX` defines the subterritory (state, province, etc.)
399399

400-
Two convenience methods are provided to create a territory code from an ISO 3166-2 or -3 code:
400+
Two convenience methods are provided to create a territory code from an ISO 3166 2 or 3 character code:
401401
`Territory.fromCountryISO2(String)` and `Territory.fromCountryISO3(String)`.
402402

403403

@@ -484,7 +484,7 @@ These are the release notes for the Java library for mapcodes.
484484

485485
* General cleanup after running stricter IntelliJ inspections profile.
486486

487-
* Added convenience methods to restrict encoded mapcodes to specific ISO 3166-2 or 3 country codes.
487+
* Added convenience methods to restrict encoded mapcodes to specific ISO 3166 2 or 3 character country codes.
488488

489489
### 2.4.5
490490

src/main/java/com/mapcode/MapcodeCodec.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ public static List<Mapcode> encode(@Nonnull final Point point,
114114

115115
/**
116116
* Encode a lat/lon pair to a list of mapcodes, like {@link #encode(double, double)}.
117-
* The result list is limited to those mapcodes that belong to the provided ISO 3166-2 country code.
117+
* The result list is limited to those mapcodes that belong to the provided ISO 3166 country code, 2 characters.
118118
* For example, if you wish to restrict the list to Mexican mapcodes, use "MX". This would
119119
* produce a result list of mapcodes with territories that start with "MX-" (note that a
120120
* mapcode that starts with "MEX" is not returned in that case.)
121121
*
122122
* @param latDeg Latitude, accepted range: -90..90.
123123
* @param lonDeg Longitude, accepted range: -180..180.
124-
* @param countryISO2 ISO 3166-2 country code.
124+
* @param countryISO2 ISO 3166 country code, 2 characters.
125125
* @return Possibly empty, ordered list of mapcode information records, see {@link Mapcode}.
126126
* @throws IllegalArgumentException Thrown if latitude or longitude are out of range, or if the ISO code is invalid.
127127
*/
@@ -162,14 +162,14 @@ public static List<Mapcode> encodeRestrictToCountryISO2(@Nonnull final Point poi
162162

163163
/**
164164
* Encode a lat/lon pair to a list of mapcodes, like {@link #encode(double, double)}.
165-
* The result list is limited to those mapcodes that belong to the provided ISO 3166-3 country code.
165+
* The result list is limited to those mapcodes that belong to the provided ISO 3166 country code, 3 characters.
166166
* For example, if you wish to restrict the list to Mexican mapcodes, use "MEX". This would
167167
* produce a result list of mapcodes with territories that start with "MEX" (note that
168168
* mapcode that starts with "MX-" are not returned in that case.)
169169
*
170170
* @param latDeg Latitude, accepted range: -90..90.
171171
* @param lonDeg Longitude, accepted range: -180..180.
172-
* @param countryISO3 ISO 3166-3 country code.
172+
* @param countryISO3 ISO 3166 country code, 3 characters.
173173
* @return Possibly empty, ordered list of mapcode information records, see {@link Mapcode}.
174174
* @throws IllegalArgumentException Thrown if latitude or longitude are out of range, or if the ISO code is invalid.
175175
*/
@@ -178,8 +178,7 @@ public static List<Mapcode> encodeRestrictToCountryISO3(final double latDeg, fin
178178
@Nonnull final String countryISO3)
179179
throws IllegalArgumentException {
180180
checkNonnull("countryISO3", countryISO3);
181-
final Territory restrictToTerritory = Territory.fromCountryISO3(countryISO3);
182-
return encode(latDeg, lonDeg, restrictToTerritory);
181+
return encodeRestrictToCountryISO2(latDeg, lonDeg, Territory.getCountryISO2FromISO3(countryISO3));
183182
}
184183

185184
@Nonnull

src/main/java/com/mapcode/Territory.java

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,10 @@ public Alphabet[] getAlphabets() {
655655
*/
656656
@Nonnull
657657
static Territory fromNumber(final int number) throws UnknownTerritoryException {
658-
if ((number < 0) || (number >= codeList.size())) {
658+
if ((number < 0) || (number >= CODE_LIST.size())) {
659659
throw new UnknownTerritoryException(number);
660660
}
661-
return codeList.get(number);
661+
return CODE_LIST.get(number);
662662
}
663663

664664
/**
@@ -704,47 +704,66 @@ public static Territory fromString(@Nonnull final String alphaCode,
704704
return createFromString(alphaCode, parentTerritory);
705705
}
706706

707-
// Keep a mapping from ISO3 to ISO2 codes. This map is used to make sure valid ISO3 codes are being used.
708-
private static final Map<String, String> MAP_ISO3_TO_ISO2;
707+
/**
708+
* Create a Territory object from a valid ISO2 country code.
709+
*
710+
* @param countryISO2 ISO 3166 country code, 2 characters.
711+
* @return Territory object for the country.
712+
* @throws IllegalArgumentException Thrown if the country code is not a valid ISO 3166 code, 2 characters.
713+
*/
714+
@Nonnull
715+
public static Territory fromCountryISO2(@Nonnull final String countryISO2) {
716+
return fromString(getCountryISO3FromISO2(countryISO2));
717+
}
709718

710-
static {
711-
final String[] countries = Locale.getISOCountries();
712-
MAP_ISO3_TO_ISO2 = new HashMap<String, String>(countries.length);
713-
for (final String countryISO2 : countries) {
714-
final String countryISO3 = new Locale("", countryISO2).getISO3Country();
715-
MAP_ISO3_TO_ISO2.put(countryISO3.toUpperCase(), countryISO2.toUpperCase());
719+
/**
720+
* Return the ISO 3166 2 character country code for a ISO 3166 3 character code.
721+
*
722+
* @param countryISO3 ISO 3166 country code, 3 characters.
723+
* @return ISO 3166 country code, 2 characters.
724+
*/
725+
@Nonnull
726+
public static String getCountryISO2FromISO3(@Nonnull final String countryISO3) {
727+
final String countryISO2 = MAP_ISO3_TO_ISO2.get(countryISO3.toUpperCase());
728+
if (countryISO2 == null) {
729+
throw new IllegalArgumentException("Parameter " + countryISO3 + " must be a valid ISO 3166 country code, 3 characters");
716730
}
731+
return countryISO2;
717732
}
718733

719734
/**
720-
* Create a Territory object from a valid ISO2 country code.
735+
* Return the ISO 3166 3 character country code for a ISO 3166 2 character code.
721736
*
722-
* @param countryISO2 ISO 3166-2 country code.
723-
* @return Territory object for the country.
724-
* @throws IllegalArgumentException Thrown if the country code is not a valid ISO 3166-2 code.
737+
* @param countryISO2 ISO 3166 country code, 2 characters.
738+
* @return ISO 3166 country code, characters.
725739
*/
726740
@Nonnull
727-
public static Territory fromCountryISO2(@Nonnull final String countryISO2) {
741+
public static String getCountryISO3FromISO2(@Nonnull final String countryISO2) {
742+
// Clipperton Island not recognized by Java - treat separately.
743+
if ("CP".equalsIgnoreCase(countryISO2)) {
744+
return "CPT";
745+
}
746+
747+
// Otherwise, use Java locales.
728748
final Locale locale = new Locale("", countryISO2);
729749
try {
730-
final String countryISO3 = locale.getISO3Country().toUpperCase();
731-
return fromString(countryISO3);
750+
return locale.getISO3Country().toUpperCase();
732751
} catch (final MissingResourceException ignored) {
733-
throw new IllegalArgumentException("Parameter " + countryISO2 + " must be a valid ISO 3166-2 country code");
752+
throw new IllegalArgumentException("Parameter " + countryISO2 + " must be a valid ISO 3166 country code, 2 characters");
734753
}
735754
}
736755

737756
/**
738-
* Create a Territory object from a valid ISO 3166-3 country code.
757+
* Create a Territory object from a valid ISO 3166 country code, 3 characters.
739758
*
740-
* @param countryISO3 ISO 3166-3 country code.
759+
* @param countryISO3 ISO 3166 country code, 3 characters.
741760
* @return Territory object for the country.
742-
* @throws IllegalArgumentException Thrown if the country code is not a valid ISO 3166-3 code.
761+
* @throws IllegalArgumentException Thrown if the country code is not a valid ISO 3166 code, 3 characters.
743762
*/
744763
@Nonnull
745764
public static Territory fromCountryISO3(@Nonnull final String countryISO3) {
746765
if (!MAP_ISO3_TO_ISO2.containsKey(countryISO3.toUpperCase())) {
747-
throw new IllegalArgumentException("Parameter " + countryISO3 + " must be a valid ISO 3166-3 country code");
766+
throw new IllegalArgumentException("Parameter " + countryISO3 + " must be a valid ISO 3166 country code, 3 characters");
748767
}
749768
return fromString(countryISO3);
750769
}
@@ -798,7 +817,7 @@ public String toAlphaCode(@Nonnull final AlphaCodeFormat format, @Nullable final
798817
if (index != -1) {
799818
assert name().length() > (index + 1);
800819
final String shortName = name().substring(index + 1);
801-
if ((format == AlphaCodeFormat.MINIMAL) || (nameMap.get(shortName).size() == 1)) {
820+
if ((format == AlphaCodeFormat.MINIMAL) || (NAME_MAP.get(shortName).size() == 1)) {
802821
result = shortName;
803822
}
804823
}
@@ -833,7 +852,7 @@ public boolean isSubdivision() {
833852
* @return True if this territory contains other territory subdivisions.
834853
*/
835854
public boolean hasSubdivisions() {
836-
return parentList.contains(this);
855+
return PARENT_LIST.contains(this);
837856
}
838857

839858
/**
@@ -880,21 +899,36 @@ public boolean hasSubdivisions() {
880899
this.fullNameAliases = (fullNameAliases == null) ? new String[]{} : fullNameAliases;
881900
}
882901

902+
// Keep a mapping from ISO3 to ISO2 codes. This map is used to make sure valid ISO3 codes are being used.
883903
@Nonnull
884-
private static final List<Territory> codeList;
904+
private static final Map<String, String> MAP_ISO3_TO_ISO2;
885905
@Nonnull
886-
private static final Map<String, List<Territory>> nameMap;
906+
private static final List<Territory> CODE_LIST;
887907
@Nonnull
888-
private static final List<Territory> parentList;
908+
private static final Map<String, List<Territory>> NAME_MAP;
909+
@Nonnull
910+
private static final List<Territory> PARENT_LIST;
911+
912+
static {
913+
final String[] countries = Locale.getISOCountries();
914+
MAP_ISO3_TO_ISO2 = new HashMap<String, String>(countries.length);
915+
for (final String countryISO2 : countries) {
916+
final String countryISO3 = new Locale("", countryISO2).getISO3Country();
917+
MAP_ISO3_TO_ISO2.put(countryISO3.toUpperCase(), countryISO2.toUpperCase());
918+
}
919+
920+
// Add Clipperton Island (not recognized by Java).
921+
MAP_ISO3_TO_ISO2.put("CPT", "CP");
922+
}
889923

890924
/**
891925
* Static checking of the static data structures.
892926
*/
893927
static {
894928
final String errorPrefix = "Initializing error: ";
895-
codeList = new ArrayList<Territory>();
896-
nameMap = new HashMap<String, List<Territory>>();
897-
parentList = new ArrayList<Territory>();
929+
CODE_LIST = new ArrayList<Territory>();
930+
NAME_MAP = new HashMap<String, List<Territory>>();
931+
PARENT_LIST = new ArrayList<Territory>();
898932
int min = Integer.MAX_VALUE;
899933
int max = Integer.MIN_VALUE;
900934
final Set<Integer> territoryNumbers = new HashSet<Integer>();
@@ -915,13 +949,13 @@ public boolean hasSubdivisions() {
915949
}
916950
territoryNumbers.add(territory.getNumber());
917951

918-
final int initialCodeListSize = codeList.size();
952+
final int initialCodeListSize = CODE_LIST.size();
919953
for (int i = initialCodeListSize; i <= territory.number; i++) {
920-
codeList.add(null);
954+
CODE_LIST.add(null);
921955
}
922-
codeList.set(territory.number, territory);
923-
if ((territory.parentTerritory != null) && !parentList.contains(territory.parentTerritory)) {
924-
parentList.add(territory.parentTerritory);
956+
CODE_LIST.set(territory.number, territory);
957+
if ((territory.parentTerritory != null) && !PARENT_LIST.contains(territory.parentTerritory)) {
958+
PARENT_LIST.add(territory.parentTerritory);
925959
}
926960

927961
// Check if territory name is unique.
@@ -985,7 +1019,7 @@ private static Territory createFromString(@Nonnull final String alphaCode,
9851019
alphaCode.trim().replace('_', '-')).toUpperCase();
9861020

9871021
// Try as alpha code.
988-
final List<Territory> territories = nameMap.get(trimmed);
1022+
final List<Territory> territories = NAME_MAP.get(trimmed);
9891023
if (territories != null) {
9901024
if (parentTerritory == null) {
9911025
return territories.get(0);
@@ -1065,8 +1099,8 @@ private static void addNameWithSeperatorVariants(@Nonnull final String name, @No
10651099
}
10661100

10671101
private static void addName(@Nonnull final String name, @Nonnull final Territory territory) {
1068-
if (nameMap.containsKey(name)) {
1069-
final List<Territory> territories = nameMap.get(name);
1102+
if (NAME_MAP.containsKey(name)) {
1103+
final List<Territory> territories = NAME_MAP.get(name);
10701104

10711105
// Add child territories in the order the parents are declared.
10721106
// This results in consistent decoding of ambiguous territory names.
@@ -1098,7 +1132,7 @@ private static void addName(@Nonnull final String name, @Nonnull final Territory
10981132
} else {
10991133
final ArrayList<Territory> arrayList = new ArrayList<Territory>();
11001134
arrayList.add(territory);
1101-
nameMap.put(name, arrayList);
1135+
NAME_MAP.put(name, arrayList);
11021136
}
11031137
}
11041138
}

src/test/java/com/mapcode/EncoderTest.java

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222

2323
import java.util.List;
2424

25-
import static org.junit.Assert.assertEquals;
26-
import static org.junit.Assert.assertFalse;
25+
import static org.junit.Assert.*;
2726

2827
@SuppressWarnings({"OverlyBroadThrowsClause", "ProhibitedExceptionDeclared", "ValueOfIncrementOrDecrementUsed", "MagicNumber"})
2928
public class EncoderTest {
@@ -274,51 +273,56 @@ public void testEncodeCiudadJuarez() {
274273
}
275274

276275
@Test
277-
public void testEncodeRestrictToCountryISO2CountryWithSubdivision() {
278-
LOG.info("testEncodeRestrictToCountryISO2CountryWithSubdivision");
276+
public void testEncodeRestrictToCountryISOCountryWithSubdivision() {
277+
LOG.info("testEncodeRestrictToCountryISOCountryWithSubdivision");
279278
final List<Mapcode> mapcodesMX = MapcodeCodec.encodeRestrictToCountryISO2(CIUDAD_JUAREZ, "MX");
279+
final List<Mapcode> mapcodesMEX = MapcodeCodec.encodeRestrictToCountryISO3(CIUDAD_JUAREZ, "MEX");
280280
assertEquals(7, mapcodesMX.size());
281+
assertEquals(7, mapcodesMEX.size());
281282
assertEquals("MX-CHH 5S.0G", mapcodesMX.get(0).toString());
283+
assertEquals("MX-CHH 5S.0G", mapcodesMEX.get(0).toString());
282284

283285
final List<Mapcode> mapcodesUS = MapcodeCodec.encodeRestrictToCountryISO2(CIUDAD_JUAREZ, "us");
286+
final List<Mapcode> mapcodesUSA = MapcodeCodec.encodeRestrictToCountryISO3(CIUDAD_JUAREZ, "usa");
284287
assertEquals(5, mapcodesUS.size());
288+
assertEquals(5, mapcodesUSA.size());
285289
assertEquals("US-NM T1DZ.338", mapcodesUS.get(0).toString());
290+
assertEquals("US-NM T1DZ.338", mapcodesUSA.get(0).toString());
286291
}
287292

288293
@Test
289-
public void testEncodeRestrictToCountryISO2CountryWithoutSubdivision() {
294+
public void testEncodeRestrictToCountryISOCountryWithoutSubdivision() {
290295
LOG.info("testEncodeRestrictToCountryISO2CountryWithoutSubdivision");
291296
final List<Mapcode> mapcodesNL = MapcodeCodec.encodeRestrictToCountryISO2(VAALS, "NL");
297+
final List<Mapcode> mapcodesNLD = MapcodeCodec.encodeRestrictToCountryISO3(VAALS, "NLD");
292298
assertEquals(2, mapcodesNL.size());
299+
assertEquals(2, mapcodesNLD.size());
293300
assertEquals("NLD ZNV.W78", mapcodesNL.get(0).toString());
301+
assertEquals("NLD ZNV.W78", mapcodesNLD.get(0).toString());
294302

295303
final List<Mapcode> mapcodesBE = MapcodeCodec.encodeRestrictToCountryISO2(VAALS, "be");
304+
final List<Mapcode> mapcodesBEL = MapcodeCodec.encodeRestrictToCountryISO3(VAALS, "bel");
296305
assertEquals(2, mapcodesBE.size());
306+
assertEquals(2, mapcodesBEL.size());
297307
assertEquals("BEL DRQ.PNK", mapcodesBE.get(0).toString());
308+
assertEquals("BEL DRQ.PNK", mapcodesBEL.get(0).toString());
298309
}
299310

300311
@Test
301-
public void testEncodeRestrictToCountryISO3WithSubdivision() {
302-
LOG.info("testEncodeRestrictToCountryISO3WithSubdivision");
303-
final Point ciudadJuarez = Point.fromDeg(31.7, -106.5);
304-
final List<Mapcode> mapcodesMEX = MapcodeCodec.encodeRestrictToCountryISO3(CIUDAD_JUAREZ, "MEX");
305-
assertEquals(2, mapcodesMEX.size());
306-
assertEquals("MEX 4CMT.DLX", mapcodesMEX.get(0).toString());
307-
308-
final List<Mapcode> mapcodesUSA = MapcodeCodec.encodeRestrictToCountryISO3(CIUDAD_JUAREZ, "usa");
309-
assertEquals(1, mapcodesUSA.size());
310-
assertEquals("USA NG4F.K745", mapcodesUSA.get(0).toString());
311-
}
312-
313-
@Test
314-
public void testEncodeRestrictToCountryISO3CountryWithoutSubdivision() {
315-
LOG.info("testEncodeRestrictToCountryISO3CountryWithoutSubdivision");
316-
final List<Mapcode> mapcodesNLD = MapcodeCodec.encodeRestrictToCountryISO3(VAALS, "NLD");
317-
assertEquals(2, mapcodesNLD.size());
318-
assertEquals("NLD ZNV.W78", mapcodesNLD.get(0).toString());
319-
320-
final List<Mapcode> mapcodesBEL = MapcodeCodec.encodeRestrictToCountryISO3(VAALS, "bel");
321-
assertEquals(2, mapcodesBEL.size());
322-
assertEquals("BEL DRQ.PNK", mapcodesBEL.get(0).toString());
312+
public void testEncodeRestrictToCountryISOForAll() {
313+
LOG.info("testEncodeRestrictToCountryISOForAll");
314+
for (final Territory territory : Territory.values()) {
315+
if (!"AAA".equals(territory.toString())) {
316+
final String countryISO3;
317+
if (territory.getParentTerritory() == null) {
318+
countryISO3 = territory.toString();
319+
} else {
320+
countryISO3 = territory.getParentTerritory().toString();
321+
}
322+
final String countryISO2 = Territory.getCountryISO2FromISO3(countryISO3);
323+
assertNotNull(MapcodeCodec.encodeRestrictToCountryISO2(CIUDAD_JUAREZ, countryISO2));
324+
assertNotNull(MapcodeCodec.encodeRestrictToCountryISO3(CIUDAD_JUAREZ, countryISO3));
325+
}
326+
}
323327
}
324328
}

0 commit comments

Comments
 (0)