diff --git a/README.md b/README.md
index 03347ef..5ff8890 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ and to decode mapcodes back to latitude/longitude pairs.
If you wish to use mapcodes in your own application landscape, consider using running an instance of the
Mapcode REST API, which can be found on: **https://github.com/mapcode-foundation/mapcode-rest-service**
+
# License
Licensed under the Apache License, Version 2.0 (the "License");
@@ -37,10 +38,81 @@ 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.
-Original C library created by Pieter Geelen. Work on Java version
-of the mapcode library by Rijn Buve. Initial port by Matthew Lowden.
-# Using the Mapcode Library
+# Contents
+
+1. [What is a Mapcode?](#intro)
+1. [Examples](#examples)
+1. [Using the Mapcode Library](#library)
+ 1. [`MapcodeCodec`](#mapcodec)
+ 1. [`Mapcode`](#mapcode)
+ 1. [`Territory`](#territory)
+ 1. [`Alphabet`](#alphabet)
+ 1. [`Point`](#point)
+ 1. [`Rectangle`](#rectangle)
+1. [Release Notes](#releasenotes)
+
+
+# What Is A Mapcode?
+
+A mapcode represents a location. Every location on Earth can be represented by a mapcode. Mapcodes
+were designed to be short, easy to recognise, remember and communicate. They are precise to a few
+meters, which is good enough for every-day use.
+
+
+## Mapcodes Are Free!
+
+Mapcodes are free. They can be used by anyone, and may be supported, provided or generated by anyone,
+as long as this is done free of charge, conditions or restrictions. Technical details and sources are
+available on our developers page.
+
+
+## what Does A Mapcode Look Like?
+
+A mapcode consists of two groups of letters and digits, separated by a dot. An example of a mapcode is
+
+ 49.4V
+
+This is sufficient as long as it is clear what country or state the mapcode belongs in. On a business card,
+it is therefore a good idea to put it after the country or state name:
+
+ John Smith
+ Oosterdoksstraat 114
+ Amsterdam
+ Netherlands 49.4V
+
+When storing mapcodes in a database, it is recommended to explicitly specify the country:
+
+ Netherlands 49.4V
+
+or via the standard 3-letter abbreviation:
+
+ NLD 49.4V
+
+In eight very large countries (The USA, Canada, Mexico, Brazil, India, Australia, Russia, and China),
+an address has little meaning without knowing the state (just like elsewhere, an address has little meaning
+without knowing the country). For example, there are 27 cities called Washington in the USA. If you want to
+refer to a location in the capital city, you would always refer to "Washington DC".
+
+ DC 18.JQZ
+
+or (in an international database):
+
+ US-DC 18.JQZ
+
+More information on mapcodes and their underlying concepts can be found in our reference material.
+
+
+## Where Did Mapcodes Come From?
+
+Mapcodes were developed in 2001 by Pieter Geelen and Harold Goddijn, soon after the GPS satellite signals
+were opened up for civilian use. It was decided to donate the mapcode system to the public domain in 2008.
+The algorithms and data tables are maintained by the Stichting Mapcode Foundation.
+
+The mapcode system is being filed as a standard at the International Organisation for Standardisation.
+
+
+# Examples
For a description of what mapcodes are, please visit http://mapcode.com.
@@ -53,22 +125,22 @@ library), such as Roman, Greek, Hindi and Arabic.
`Mapcode` objects are returned by the `MapcodeCodec` with encodes coordinates
to mapcodes and decodes mapcodes to coordinates (codec means coder/decoder).
-# Examples
-
Here's an example to `decode()` mapcode within a given territory context.
Note that the territory context (`NLD` is this case) is only used to
disambiguate the code if needed. If the provided code is an international
code, the context is simply ignored, because no disambiguation is needed.
+```
final Territory territory = Territory.fromString("NLD");
final String mapcode = "49.4V";
try {
final Point p = MapcodeCodec.decode(mapcode, territory);
- // p now points at the (lat, lon) for the mapcode.
+ // p now contains the (lat, lon) for mapcode "49.YV".
} catch (final UnknownMapcodeException ignored) {
// The mapcode was not valid.
}
-
+```
+
And to `encode()` a latitude and longitude into a mapcode you would write:
final List results = MapcodeCodec.encode(lat, lon);
@@ -129,12 +201,225 @@ The `Point` class is usually easier to use than individual latitude and
longitude paramters and class makes sure it always wraps latitudes to
a range of `[-90, 90]` and longitude to `[-180, 180>`.
-# Coding Formatting Style
-The code formatting style used is the default code formatting style from IntelliJ IDEA (version 14).
-it is recommended to always use auto-format on any (Java) files before committing to maintain consistent layout.
+# Using the Mapcode Library
+
+Welcome to the Java library to handle mapcodes. The original C library was created by Pieter Geelen.
+The initial port to Java and speed-ups were done by Matthew Lowden.
+Rijn Buve has developed and contributed to the Java version of the Mapcode library since, providing
+a simple and consistent API for other developers. He has also built a number of applications using
+this library, which can also be found in the Github respositories of the Mapcode Foundation.
+
+
+## How To Build The Mapcode Library JAR File
+
+The sources for the Mapcode Library for Java contain everything to build the Mapcode JAR file as well
+as a significant number of unit tests to verify the correctness of the implementation against the
+reference C implementation.
+
+The library requires a minimum Java language revision level 6, but has been tested and verified to work
+with JDK 1.6, JDK 1.7 and JDK 1.8.
+
+To build the library:
+
+ cd
+ mvn clean install
+
+This produces a JAR file in your local Maven repository at `~/.m2/repository/com/mapcode/mapcode//`
+You can include this JAR in your project, or store it in your local Nexus repository, for example.
+
+If you create a Maven project, much simpler than building the library yourself, is to include it from
+Maven Central, adding this dependency to your `pom.xml`:
+
+
+ com.mapcode
+ mapcode
+ {fill in latest version}
+
+
+The latest official version of the libray on Maven Central can be found [**here**](http://search.maven.org/#search%7Cga%7C1%7Cmapcode).
+
+## How To Use This Library In Your Application
+
+There are two classes you interact with as a client of the Mapcode Library. These are:
+
+ MapcodeCodec.java
+ Mapcode.java
+ Point.java
+
+
+### Class `MapcodeCodec`
+
+This class contains the **encoder** and **decoder** for mapcodes. The class exposes two methods (with some
+variants): `encode` and `decode`.
+
+The **encoder** encodes a (latitude, longitude) pair into a result set of mapcodes. A single (latitude,
+longitude) pair may produce multiple mapcodes, some of which are known as **local** mapcodes
+(which are only unique within a given territory) and one which is globally unique in the entire world.
+
+The **decoder** decodes a local or world-wide mapcode string into a (latitude, longitude) pair. For
+local mapcodes a territory may be specified which is used to disambiguate the mapcode resolution.
+
+Note that encoding a (latitude, longitude) pair to a mapcode and then decoding it may result in a
+slightly offset position, as the mapcodes have a limited precision. The library offers "high-precision"
+mapcodes as well, but you van never assume the resulting latitudes and longitudes to exactly match the
+original input.
+
+**`List encode(double latitude, double longitude)`** encodes a (latitude, longitude) pair.
+
+Example:
+
+ double lat = 52.376514;
+ double lon = 4.908542;
+ List results = MapcodeCodec.encode(lat, lon);
+ // Returns a non-empty list of results.
+
+This produces a non-empty list of resulting mapcodes. The shortest (potentially local) mapcodes is always
+the first mapcodes in the list. The last mapcode in the list is always the
+globally unique international mapcode.
+
+**`List encode(double latitude, double longitude, Territory territory)`** encodes a (latitude,
+longitude) pair, where encoding is restricted to a specific territory.
+
+Example:
+
+ List results = MapcodeCodec.encode(lat, lon, Territory.NLD);
+ // Returns an empty list of results if the location is not within territory NLD.
+
+This resticts encoding to a specific territory and produces a potentially empty list of results.
+Again, if encoding succeeded, the first mapcode is the shortest one and the last mapcode in the list is the
+globally unique international mapcode.
+
+Both `encode()` methods are also offered as a `encodeToShortest()` method, which essentially
+returns only the first result of the previous methods (if there are any results).
+
+ Mapcode result = MapcodeCodec.encodeToShortest(lat, lon);
+ // Always returns a mapcode (or valid lat and lon values).
-# Using Git and `.gitignore`
+ try {
+ Mapcode result = MapcodeCodec.encodeToShortest(lat, lon, Territory.NLD);
+ // This may fail.
+ }
+ catch (UnknownMapcodeException e) {
+ // If the location is not within the territory, this exception is thrown.
+ }
+
+**`Point decode(String mapcode)`** decodes a mapcode to a `Point` which contains a location. Example:
+
+ Point p = MapcodeCodec.decode("NLD 49.4V");
+
+**`Point decode(String mapcode, Territory territory)`** decodes a mapcode to a `Point` which contains a
+location, where the mapcode must be located within a specific territory.
+
+Examples of usage:
+
+ Point p = MapcodeCodec.decode("49.4V", Territory.NLD);
+
+
+## Class `Mapcode`
+
+This class represents mapcodes, which consist of a string of characters, digits and a decimal point and
+a territory specification. The territory specification is required for national (local) mapcodes, which
+are not globally unique.
+
+The class also exposes methods to convert mapcodes to proper mapcode strings, usable for printing and
+it allows string-formatted mapcodes to be converted to `Mapcode` objects, where territory information
+is properly parsed and converted to a `Territory` enumeration value.
+
+**`String getCode()`** returns the mapcode string which does not include territory information. You can also
+use `getCode(1)` and `getCode(2)` for more precision, but longer mapcodes.
+
+The default precision offered by `getCode()` is approximately 10m (maximum distance to latitude,
+longitude the mapcode decodes to). This corresponds to an area of 20m x 20m (400m2). These mapcodes include
+no additional precision digits.
+
+The precision offered by `getCode(1)` is approximately 2m.
+This corresponds to an area of 4m x 4m (16m2). These mapcodes include 1 additional precision digit.
+
+The precision offered by `getCode(2)` is approximately 0.40m. This corresponds to an area
+of 0.80m x 0.80m (0.64m2). These mapcodes include 2 additional precision digits.
+
+This goes up to `getCode(8)`, which provides nanometer accuracy. (Please note one of the main advantages
+of mapcodes over WGS84 coordinates is their simplicity and short size, so try to use as little precision
+as required for your application...)
+
+**`Territory getTerritory()`** returns the territory information.
+
+**`toString()`** and **`getCodeWithTerritory()`** return mapcodes string with territory information,
+specified as a ISO code.
+
+
+## Enum `Territory`
+
+This enum defines the territories for which local mapcodes are defined. The added benefit of using
+local mapcodes over international mapcodes is simply that they are shorter and easier to remember.
+
+Rather than writing `WLR9B.RP9P` (to locate a park in Moscow) you can use `MOW HG.4L` (or `MOW НГ.4Л`
+in Cyrillic). And most of the time you can even omit the prefix `MOW`, as in many practical situations
+the territory is given implicitly by the context of usage.
+
+Note that in this case `MOW` is not really a territory, but a sub-territory of `RUS`. It's full
+name is `RU-MOW`.
+
+The following territories are subdivided into subterritories to make sure the territory codes represent
+smaller areas, so mapcodes can remain fairly short:
+
+* `USA`, `US-XXX`: USA
+* `IND`, `IN-XXX`: India
+* `CAN`, `CA-XXX`: Canada
+* `AUS`, `AU-XXX`: Australia
+* `MEX`, `MX-XXX`: Mexico
+* `BRA`, `BR-XXX`: Brasil
+* `RUS`, `RU-XXX`: Russia
+* `CHN`, `CN-XXX`: China
+
+Rather than using the 3-letter territory code for mapcodes in these territories, you'd probably want
+to use the `TT-XXX` form, where `XXX` defines the subterritory (state, province, etc.)
+
+
+## Enum `Alphabet`
+
+This enum defines the alphabets, or rather scripts, that are supported by the Mapcode Library.
+Encoding mapcodes procudes a Unicode string and this enum can be used to identify the script
+for the result.
+
+Note that the character mapping between scripts is based on similarity in appearance, so mapcodes
+in different scripts can be remembered more easily with the help of your visual memory.
+
+
+## Class `Point`
+
+This class represents (latitude, longitude) locations. It offers methods to create locations using
+degrees.
+
+**`Point fromDeg(double latitude, double longitude)`** returns a `Point` for a given (latitude, longitude)
+pair. Note that latitudes are always between **-90** and **90**, and longitudes are
+always between **-180** and **180** (non-inclusive) whenreturned.
+However, values outside these range are correctly limited (latitude) or wrapped (longitude) to these ranges
+when supplied to the class.
+
+The methods **`double getLat()`** and **`getLon()`** return the latitude and longitude respectively, in degrees.
+
+
+## Class `Rectangle`
+
+This class represents a geospatial rectangle. This class only accurately represents areas on the
+surface of the Earth for small rectangles (as the curvature of the surface is not taken into account).
+
+It is used to return the bounding box for a given mapcode. Beware: bounding boxes of mapcodes in a single
+territory and of the same length do not overlap, but others may. The bounding boxes of international
+mapcodes do not overlap with each other and they are smaller territorial mapcodes (but the codes are
+longer).
+
+
+## Code Style Settings for IntelliJ IDEA
+
+The Java code uses the *default* [JetBrains IntelliJ IDEA](https://www.jetbrains.com/idea)
+code style settings for Java, with one exception:
+code blocks are always surround by `{...}` and on separate lines.
+
+
+## Using Git and `.gitignore`
It's good practice to set up a personal global `.gitignore` file on your machine which filters a number of files
on your file systems that you do not wish to submit to the Git repository. You can set up your own global
@@ -160,7 +445,299 @@ The local `.gitignore` file in the Git repository itself to reflect those file o
regular compile, build or release commands, such as:
`target/ out/`
-# Bug Reports and New Feature Requests
+
+## Bug Reports and New Feature Requests
If you encounter any problems with this library, don't hesitate to use the `Issues` session to file your issues.
Normally, one of our developers should be able to comment on them and fix.
+
+
+# Release Notes
+
+These are the release notes for the Java library for mapcodes.
+
+### 2.4.4
+
+* Added calls to decode an international or territorial mapcode to its encompassing
+rectangle using `decodeToRectangle`.
+
+* Minor code hygiene improvements.
+
+* Moved all documentation to `README.md`.
+
+### 2.4.3
+
+* Updated Maven dependencies for latest patches.
+
+### 2.4.2
+
+* Removed secret Coveralls key from POM file.
+
+### 2.4.1
+
+* Added scripts for Tifinagh (Berber), Tamil, Amharic, Telugu, Odia, Kannada, Gujarati.
+
+* Added `getAlphabets()` to `Territory` class, returning the most commonly used languages for the territory.
+
+* Renamed constant `HINDI` to `DEVANAGIRI`.
+
+* Improved some characters for Arabic and Devanagari.
+
+* Fixed Bengali to also support Assamese.
+
+### 2.4.0
+
+* Added scripts for Korean (Choson'gul/Hangul), Burmese, Khmer, Sinhalese, Thaana (Maldivan), Chinese (Zhuyin, Bopomofo).
+
+* Renamed constant `MALAY` to `MALAYALAM`.
+
+### 2.3.1
+
+* Fixed data for some parts of China.
+
+### 2.3.0
+
+* Added Arabic support.
+
+* Fixed Greek, Hebrew and Hindi support.
+
+### 2.2.5
+
+* Updated documentation.
+
+* Cleaned up POM, sorted dependencies.
+
+### 2.2.4
+
+* Added Travis CI and Coveralls badges to `README.md`.
+
+* Replaces static `DataAccess` class with singleton `DataModel` to allow testing
+of incorrect data model files.
+
+* Fixed error handling for incorrect data model files.
+
+* Fix error to info logging in `aeuUnpack`.
+
+* Updated all POM dependencies.
+
+* Updated copyright messages.
+
+* Improved test coverage of unit tests.
+
+### 2.2.3
+
+* Issue #23: Fixed `Territory.fromString` to make sure the parent territory is valid for
+ input like "CHE-GR". This returned "MX-GRO" instead of throwing `UnknownTerritoryException`.
+ Added unit test for this type of case.
+
+* Fixed minor JavaDoc issues.
+
+### 2.2.2
+
+* Fixed error in `Point` which in rare cases would allow longitudes outside proper range.
+
+### 2.2.1
+
+* Fixed unit test. Reduced size of files for unit tests considerably. Improved unit test speed.
+
+* Fixed `Point` interface.
+
+* Cleaned up `Boundary` and `DataAccess`.
+
+### 2.2.0
+
+* Solved 1-microdegree gap in a few spots on Earth, noticable now extreme precision is possible.
+
+* Replaced floating point by fixed point math.
+
+* Improved speed.
+
+* Enforce `Mencode(decode(M)) == M`, except at territory border corners.
+
+* Cleaned up source; moved hard-coded data into `mminfo.dat`.
+
+### 2.1.0
+
+* Added micro-meter precision (mapcodes can now have eight precision digits).
+
+* Assure that encode(decode(m)) delivers m.
+
+* Renames to bring source more in line with other implementations.
+
+### 2.0.2
+
+* Renamed `isValidPrecisionFormat` to `isValidMapcodeFormat`.
+
+* Removed public microdegree references from `Point` class. Everything is degrees now.
+
+* Removed `ParentTerritory` class.
+
+### 2.0.1
+
+* Reverted Java JDK level to 1.6 (Java 6) from 1.8 (Java 8), so the library can be used on
+ Android platforms operating at Java 6 as well.
+
+* Use multi-threading for long running test to speed them up (uses all CPU cores now).
+
+* Added the ability to use a country name for `Territory.fromString()`.
+
+### 2.0.0
+
+* Fixes to the data rectangles (primarily intended for ISO proposal).
+
+* Removed functionality to use numeric territory codes; only alpha codes are accepted.
+
+* Note that this release only allows high-precision mapcodes up to 2 additional suffix characters.
+A future release will be scheduled to allow up to 8 suffix characters (nanometer accuracy).
+
+### 1.50.3
+
+* This release breaks compatiblity with earlier releases, to clean up the interface significantly.
+
+* Removed `Mapcode.encodeToShortest(lat, lon))` as this will produce a randomly chosen territory.
+ You must specify a `restrictToTerritory` now.
+
+* Renamed `Territory.code` to `Territory.number`.
+
+* Renamed `fromTerritoryCode())` to `fromNumber())`.
+
+* Renamed `Territory.isState())` to `Territory.isSubdivision())` and
+
+* Renamed `Territory.hasStates())` to `Territory.hasSubdivision())`.
+
+* Renamed `Alphabet.code` to `Alphabet.number`.
+
+* Renamed `fromCode())` to `fromNumber())`.
+
+* Renamed `MapcodeFormat` to `PrecisionFormat`.
+
+* Deprecated methods have been removed.
+
+### 1.50.2
+
+* Cleaned up Unicode handling a bit.
+
+* Speed up of reading initialization data.
+
+* Rename `toNameFormat` into `toAlphaFormat` and `NAME_FORMAT` to `ALPHA_FORMAT`.
+
+### 1.50.1
+
+* Bugfix for mapcodes in IN-DD (in India).
+
+### 1.50
+
+* Major release. This version is not backwards compatible with mapcode 1.4x: is has dropped support for
+ Antartica AT0-8 codes and has a changed (improved) way of dealing with the Greek alphabet.
+
+* Added 22-chararcter post-processing of all-digit mapcodes for the Greek alphabet.
+
+* Retired legacy aliases EAZ and SKM, AU-QL, AU-TS, AU-NI and AU-JB.
+
+* Retired legacy Antarctica claims AT0 through AT8.
+
+* Added convencience methods for `MapcodeCodec` to accept `Point` for all encode functions
+ as well (not just `latDeg`, `lonDeg`).
+
+* Added alphabet support to convert mapcodes (both codes and territories) between `Alphabet`s.
+
+* Exceptions have been corrected and documented in code.
+
+* Allowed nullable values in `MapcodeCodec` encode and decode methods to assume reasonable defaults.
+
+* Microdegrees are no longer support publicly in `Point`. Only degrees.
+
+* Latitudes are limited to -90..90 and longitudes are wrapped to -180..180 (non inclusive).
+
+### 1.42.3
+
+* To be done.
+
+### 1.42.2
+
+* Upper- and lowercase mapcodes always allowed.
+
+### 1.42.1
+
+* Cleaned up source. Removed all pending IntelliJ IDEA inspection warnings and reformatted code
+ using default IDEA code style to maintain consistent layout.
+
+* Add additional unit tests to check for correct handling of international mapcode handling.
+
+* Added safe constants for the maximum delta distance in meters for mapcode accuracy.
+
+### 1.42
+
+* Fixed a bug in `MapcodeCodec.encodeToShortest` which would not always return the shortest code (see
+ next bullet). Reproducible with `curl -X GET http://localhost:8080/mapcode/to/47.1243/-111.28564/local`.
+
+* Fixed a bug where `Encoder.encode` would sometime retrieve more than one result even if result set
+ was limited to 1 result.
+
+### 1.41.1
+
+* Added convenience method to Mapcode.
+
+### 1.41
+
+* Added the India state Telangana (IN-TG), until 2014 a region in Adhra Pradesh.
+
+* Updated POM dependencies to latest library versions of standard components.
+
+### 1.40.3
+
+* Minor code clean-up with no functional effect.
+
+* (Issue #6) Removed non-project specific unwanted files out of `.gitignore`. These should be listed in the
+developer's own global `~/.gitignore` file instead.
+
+### 1.40.2
+
+* Added `getMapcodeFormatType` and `isValidMapcodeFormat` to check validity of mapcode strings. Added
+unit tests for these methods as well.
+
+* Constructor of `Mapcode` now checks for validity of mapcode string.
+
+* Added Unicode handling of high precision mapcodes and added check to throw an `IllegalArgumentException`
+if the character 'Z' or equivalent Unicode character is contained in the high precision part according to
+the Mapcode documenation.
+
+* Added method `convertToAscii` which produces the ASCII, non-Unicode variant of a mapcode which contains
+Unicode characters§.
+
+### 1.40.1
+
+* Deprecated names `getMapcodeHighPrecision` and `getMapcodeMediumPrecision`.
+ Replaced those with `getMapcodePrecision1` and `getMapcodePrecision2`.
+
+* Fixed all occurences of incorrectly cased Mapcode vs. mapcode.
+
+### 1.40
+
+* Renamed class `Mapcode` to `MapcodeCodec`.
+
+* Renamed class `MapcodeInfo` to `Mapcode`.
+
+* Added high precision Mapcodes, with methods `getMapcodeHighPrecision`
+
+* Seriously reduced test set size.
+
+* Replaced Unicode characters in source code to escapes.
+
+* Added explicit character encoding to `pom.xml`.
+
+* Fixed issues with decoder at some boundaries.
+
+### 1.33.2
+
+* Clean-up of release 1.33.2.
+
+* Added release notes.
+
+* Removed GSON dependency from production (now scope 'test' only).
+
+* Added robustness with respect to Unicode characters.
+
+### 1.33.1
+
+* First release of Java library for MapCodes. Includes extensive test suite.
diff --git a/pom.xml b/pom.xml
index 089bfb6..08b3281 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
mapcode
jar
- 2.4.3
+ 2.4.4
Mapcode Java Library
@@ -36,7 +36,7 @@
Rijn Buve
Mapcode Foundation
- Managing Director, development
+ Managing Director, main contributor
diff --git a/src/main/java/com/mapcode/Boundary.java b/src/main/java/com/mapcode/Boundary.java
index 73a1367..6fa3ab8 100644
--- a/src/main/java/com/mapcode/Boundary.java
+++ b/src/main/java/com/mapcode/Boundary.java
@@ -26,32 +26,28 @@
* This class handles territory rectangles for mapcodes.
*/
class Boundary {
- private int lonMicroDegMin; // Minimum longitude (in microdegrees). Inclusive.
- private int lonMicroDegMax; // Maximum longitude (in microdegrees). Exclusive.
private int latMicroDegMin; // Minimum latitude (in microdegrees). Inclusive.
+ private int lonMicroDegMin; // Minimum longitude (in microdegrees). Inclusive.
private int latMicroDegMax; // Minimum latitude (in microdegrees). Exclusive.
+ private int lonMicroDegMax; // Maximum longitude (in microdegrees). Exclusive.
+ // Get the singleton for the data model.
private static final DataModel DATA_MODEL = DataModel.getInstance();
- private Boundary(
- final int lonMicroDegMin,
- final int lonMicroDegMax,
- final int latMicroDegMin,
- final int latMicroDegMax) {
+ private Boundary(final int latMicroDegMin, final int lonMicroDegMin, final int latMicroDegMax, final int lonMicroDegMax) {
this.lonMicroDegMin = lonMicroDegMin;
this.latMicroDegMin = latMicroDegMin;
- this.lonMicroDegMax = lonMicroDegMax;
this.latMicroDegMax = latMicroDegMax;
+ this.lonMicroDegMax = lonMicroDegMax;
}
// You have to use this factory method instead of a ctor.
@Nonnull
- static Boundary createFromTerritoryRecord(final int territoryRecord) {
+ static Boundary createBoundaryForTerritoryRecord(final int territoryRecord) {
return new Boundary(
- DATA_MODEL.getLonMicroDegMin(territoryRecord),
- DATA_MODEL.getLonMicroDegMax(territoryRecord),
- DATA_MODEL.getLatMicroDegMin(territoryRecord),
- DATA_MODEL.getLatMicroDegMax(territoryRecord));
+ DATA_MODEL.getLatMicroDegMin(territoryRecord), DATA_MODEL.getLonMicroDegMin(territoryRecord),
+ DATA_MODEL.getLatMicroDegMax(territoryRecord), DATA_MODEL.getLonMicroDegMax(territoryRecord)
+ );
}
int getLonMicroDegMin() {
@@ -98,7 +94,7 @@ boolean containsPoint(@Nonnull final Point p) {
}
final int lonMicroDeg = p.getLonMicroDeg();
- // Longitude boundaries can extend (slightly) outside the [-180,180) range
+ // Longitude boundaries can extend (slightly) outside the [-180,180) range.
if (lonMicroDeg < lonMicroDegMin) {
return (lonMicroDegMin <= (lonMicroDeg + Point.MICRO_DEG_360)) && ((lonMicroDeg + Point.MICRO_DEG_360) < lonMicroDegMax);
} else if (lonMicroDeg >= lonMicroDegMax) {
diff --git a/src/main/java/com/mapcode/CheckArgs.java b/src/main/java/com/mapcode/CheckArgs.java
index 755bf0d..5bd9f6d 100644
--- a/src/main/java/com/mapcode/CheckArgs.java
+++ b/src/main/java/com/mapcode/CheckArgs.java
@@ -23,10 +23,10 @@
/**
* ----------------------------------------------------------------------------------------------
- * Package private implementation class. For internal use within the Mapcode implementation only.
+ * Package private implementation class. For internal use within the mapcode implementation only.
+ * ----------------------------------------------------------------------------------------------
*
* This class provides a number of helper methods to check (runtime) arguments.
- * ----------------------------------------------------------------------------------------------
*/
@SuppressWarnings("OverlyBroadThrowsClause")
class CheckArgs {
diff --git a/src/main/java/com/mapcode/Common.java b/src/main/java/com/mapcode/Common.java
index 3485dec..270f6a5 100644
--- a/src/main/java/com/mapcode/Common.java
+++ b/src/main/java/com/mapcode/Common.java
@@ -29,16 +29,23 @@
class Common {
private static final Logger LOG = LoggerFactory.getLogger(Common.class);
- static final int[] nc = {
+ // TODO: Need better name and explanation.
+ static final int[] NC = {
1, 31, 961, 29791, 923521, 28629151, 887503681
};
- static final int[] xSide = {
+
+ // TODO: Need better name and explanation.
+ static final int[] X_SIDE = {
0, 5, 31, 168, 961, 5208, 29791, 165869, 923521, 5141947
};
- static final int[] ySide = {
+
+ // TODO: Need better name and explanation.
+ static final int[] Y_SIDE = {
0, 6, 31, 176, 961, 5456, 29791, 165869, 923521, 5141947
};
- private static final int[] xDivider19 = {
+
+ // TODO: Need better name and explanation.
+ private static final int[] X_DIVIDER_19 = {
360, 360, 360, 360, 360, 360, 361, 361, 361, 361,
362, 362, 362, 363, 363, 363, 364, 364, 365, 366,
366, 367, 367, 368, 369, 370, 370, 371, 372, 373,
@@ -66,6 +73,9 @@ private Common() {
static {
// This code shows a message when assertions are active or disabled. It (ab)uses assert for that...
+ // Some of the methods (and tests) take considerably longer with assertions checking, so it's useful
+ // to have this information in the log file.
+
//noinspection UnusedAssignment
boolean debug = false;
//noinspection AssertWithSideEffects
@@ -73,59 +83,51 @@ private Common() {
//noinspection ConstantConditions
if (debug) {
LOG.info("Common: assertions are active (JVM runtime option '-ea')");
- }
- else {
+ } else {
LOG.debug("Common: assertions are not active, they are bypassed");
}
}
- /**
- * This method returns a divider for longitude (multiplied by 4), for a given latitude.
- *
- * @param minY Latitude.
- * @param maxY Longitude.
- * @return Divider.
- */
- static int xDivider(final int minY, final int maxY) {
- assert minY < maxY;
- if (minY >= 0) {
- // maxY > minY > 0
- assert (maxY > minY) && (minY > 0);
- return xDivider19[minY >> 19];
- } else if (maxY >= 0) {
- // maxY > 0 > minY
- assert (maxY > 0) && (0 > minY);
- return xDivider19[0];
+ // This method returns a divider for longitude (multiplied by 4), for a given latitude.
+ // TODO: Need better names for minY and maxY.
+ static int xDivider(final int latMin, final int latMax) {
+ assert latMin < latMax;
+ if (latMin >= 0) {
+ assert (latMax > latMin) && (latMin > 0);
+ return X_DIVIDER_19[latMin >> 19];
+ } else if (latMax >= 0) {
+ assert (latMax > 0) && (0 > latMin);
+ return X_DIVIDER_19[0];
} else {
- // 0 > maxY > minY
- assert (0 > maxY) && (maxY > minY);
- return xDivider19[(-maxY) >> 19];
+ assert (0 > latMax) && (latMax > latMin);
+ return X_DIVIDER_19[(-latMax) >> 19];
}
}
+ // TODO: Need to explain what a codex is.
static int countCityCoordinatesForCountry(final int codex, final int territoryRecord, final int firstTerritoryRecord) {
assert codex >= 0;
assert territoryRecord >= 0;
assert firstTerritoryRecord >= 0;
- final int i = getFirstNamelessRecord(codex, territoryRecord, firstTerritoryRecord);
- int e = territoryRecord;
- while (Data.getCodex(e) == codex) {
- e++;
+ final int firstRecord = getFirstNamelessRecord(codex, territoryRecord, firstTerritoryRecord);
+ int record = territoryRecord;
+ while (Data.getCodex(record) == codex) {
+ record++;
}
- assert i <= e;
- return e - i;
+ assert firstRecord <= record;
+ return record - firstRecord;
}
static int getFirstNamelessRecord(final int codex, final int territoryRecord, final int firstTerritoryRecord) {
assert codex >= 0;
assert territoryRecord >= 0;
assert firstTerritoryRecord >= 0;
- int i = territoryRecord;
- while ((i >= firstTerritoryRecord) && Data.isNameless(i) && (Data.getCodex(i) == codex)) {
- i--;
+ int record = territoryRecord;
+ while ((record >= firstTerritoryRecord) && Data.isNameless(record) && (Data.getCodex(record) == codex)) {
+ record--;
}
- i++;
- assert i <= territoryRecord;
- return i;
+ record++;
+ assert record <= territoryRecord;
+ return record;
}
}
diff --git a/src/main/java/com/mapcode/Data.java b/src/main/java/com/mapcode/Data.java
index 66c3c20..b389b0f 100644
--- a/src/main/java/com/mapcode/Data.java
+++ b/src/main/java/com/mapcode/Data.java
@@ -26,6 +26,8 @@
* This class the data class for Mapcode codex items.
*/
class Data {
+
+ // TODO: Need explanation what this is and how this is used.
static final char[] ENCODE_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // Numerals.
'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', // Consonants.
@@ -33,32 +35,40 @@ class Data {
'A', 'E', 'U' // Vowels.
};
+ // Get direct access to the data model.
private static final DataModel DATA_MODEL = DataModel.getInstance();
private Data() {
// Disabled.
}
+ // TODO: Explain what these values are. Can we make this an enum instead (safer)?
+ static final int TERRITORY_RECORD_TYPE_NONE = 0;
+ static final int TERRITORY_RECORD_TYPE_PIPE = 1;
+ static final int TERRITORY_RECORD_TYPE_PLUS = 2;
+ static final int TERRITORY_RECORD_TYPE_STAR = 3;
+
+ // TODO: Need to explain what "nameless" means and what a territoryRecord is (different from territoryNumber).
static boolean isNameless(final int territoryRecord) {
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
return (DATA_MODEL.getDataFlags(territoryRecord) & 64) != 0;
}
+ // TODO: Need to explain what "special shape" means.
static boolean isSpecialShape(final int territoryRecord) {
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
+
+ // TODO: The "magic" of binary operators and bit shifting should be in class DataModel, not here.
return (DATA_MODEL.getDataFlags(territoryRecord) & 1024) != 0;
}
- static final int TERRITORY_RECORD_TYPE_NONE = 0;
- static final int TERRITORY_RECORD_TYPE_PIPE = 1;
- static final int TERRITORY_RECORD_TYPE_PLUS = 2;
- static final int TERRITORY_RECORD_TYPE_STAR = 3;
-
+ // TODO: Explain what territory record types are. Can they be an enum instead?
static int getTerritoryRecordType(final int territoryRecord) {
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
return (DATA_MODEL.getDataFlags(territoryRecord) >> 7) & 3; // 1=pipe 2=plus 3=star
}
+ // TODO: Explain what "restricted" means.
static boolean isRestricted(final int territoryRecord) {
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
return (DATA_MODEL.getDataFlags(territoryRecord) & 512) != 0;
@@ -70,17 +80,15 @@ static int getCodex(final int territoryRecord) {
return (10 * (codexflags / 5)) + (codexflags % 5) + 1;
}
+ // TODO: What does this method do? What is parameter i (rename)?
@Nonnull
static String headerLetter(final int i) {
final int flags = DATA_MODEL.getDataFlags(i);
+
+ // TODO: The "magic" of how to interpret flags must be in DataModel, not here.
if (((flags >> 7) & 3) == 1) {
return Character.toString(ENCODE_CHARS[(flags >> 11) & 31]);
}
return "";
}
-
- @Nonnull
- static Boundary getBoundary(final int territoryRecord) {
- return Boundary.createFromTerritoryRecord(territoryRecord);
- }
}
diff --git a/src/main/java/com/mapcode/DataModel.java b/src/main/java/com/mapcode/DataModel.java
index a571657..14ec522 100644
--- a/src/main/java/com/mapcode/DataModel.java
+++ b/src/main/java/com/mapcode/DataModel.java
@@ -34,6 +34,7 @@
class DataModel {
private static final Logger LOG = LoggerFactory.getLogger(DataModel.class);
+ // TODO: This class needs a thorough description of what the data file format looks like and what all bit fields means exactly.
private static final int HEADER_ID_1 = 0;
private static final int HEADER_ID_2 = 1;
private static final int HEADER_VERSION_LO = 2;
@@ -196,11 +197,13 @@ int getNrTerritories() {
*
* @return Number of rectangles per territory.
*/
+ // TODO: Explain what territory records contain exactly.
int getNrTerritoryRecords() {
return nrTerritoryRecords;
}
@SuppressWarnings("PointlessArithmeticExpression")
+ // TODO: Explain what this does exactly, why not return a Point or Rectangle?
int getLonMicroDegMin(final int territoryRecord) {
return data[((territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LON_MICRO_DEG_MIN)];
}
@@ -221,17 +224,21 @@ int getDataFlags(final int territoryRecord) {
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] & MASK_DATA_DATA_FLAGS;
}
+ // TODO: Explain what a "div" and "smart div" is and how you use, and why you need to use it.
int getSmartDiv(final int territoryRecord) {
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] >> SHIFT_POS_DATA_SMART_DIV;
}
+ // TODO: Explain what these methods do exactly.
// Low-level routines for data access.
@SuppressWarnings("PointlessArithmeticExpression")
int getDataFirstRecord(final int territoryNumber) {
+ assert (0 <= territoryNumber) && (territoryNumber <= Territory.AAA.getNumber());
return index[territoryNumber + POS_INDEX_FIRST_RECORD];
}
int getDataLastRecord(final int territoryNumber) {
+ assert (0 <= territoryNumber) && (territoryNumber <= Territory.AAA.getNumber());
return index[territoryNumber + POS_INDEX_LAST_RECORD] - 1;
}
}
diff --git a/src/main/java/com/mapcode/Decoder.java b/src/main/java/com/mapcode/Decoder.java
index e957487..96efff0 100644
--- a/src/main/java/com/mapcode/Decoder.java
+++ b/src/main/java/com/mapcode/Decoder.java
@@ -21,14 +21,20 @@
import javax.annotation.Nonnull;
-import static com.mapcode.Boundary.createFromTerritoryRecord;
+import static com.mapcode.Boundary.createBoundaryForTerritoryRecord;
+/**
+ * ----------------------------------------------------------------------------------------------
+ * Package private implementation class. For internal use within the Mapcode implementation only.
+ * ----------------------------------------------------------------------------------------------
+ *
+ * This class contains decoder for mapcodes.
+ */
class Decoder {
private static final Logger LOG = LoggerFactory.getLogger(Decoder.class);
- private static final char GREEK_CAPITAL_ALPHA = '\u0391';
-
- private static final DataModel dataModel = DataModel.getInstance();
+ // Get direct access to the data model singleton.
+ private static final DataModel DATA_MODEL = DataModel.getInstance();
private Decoder() {
// Prevent instantiation.
@@ -39,90 +45,98 @@ private Decoder() {
// ----------------------------------------------------------------------
@Nonnull
- static Point decode(@Nonnull final String argMapcode,
- @Nonnull final Territory argTerritory)
+ static MapcodeZone decodeToMapcodeZone(@Nonnull final String argMapcode,
+ @Nonnull final Territory argTerritory)
throws UnknownMapcodeException {
LOG.trace("decode: mapcode={}, territory={}", argMapcode, argTerritory.name());
String mapcode = argMapcode;
Territory territory = argTerritory;
- String extrapostfix = "";
-
- final int minpos = mapcode.indexOf('-');
- if (minpos > 0) {
- extrapostfix = decodeUTF16(mapcode.substring(minpos + 1).trim());
- if (extrapostfix.contains("Z")) {
- throw new UnknownMapcodeException("Invalid character Z");
+ String precisionPostfix = "";
+ final int positionOfDash = mapcode.indexOf('-');
+ if (positionOfDash > 0) {
+ precisionPostfix = decodeUTF16(mapcode.substring(positionOfDash + 1).trim());
+ if (precisionPostfix.contains("Z")) {
+ throw new UnknownMapcodeException("Invalid character Z, mapcode=" + argMapcode + ", territory=" + argTerritory);
}
- mapcode = mapcode.substring(0, minpos);
+
+ // Cut the precision postfix from the mapcode.
+ mapcode = mapcode.substring(0, positionOfDash);
}
+ assert !mapcode.contains("-");
+ // TODO: Explain what AEU unpack does.
mapcode = aeuUnpack(mapcode).trim();
if (mapcode.isEmpty()) {
- LOG.info("decode: Failed to aeuUnpack {}", argMapcode);
- return Point.undefined(); // failed to aeuUnpack
+ // TODO: Is this a useful log message?
+ LOG.debug("decode: Failed to aeuUnpack {}", argMapcode);
+ throw new UnknownMapcodeException("Failed to AEU unpack, mapcode=" + argMapcode + ", territory=" + argTerritory);
}
- final int incodexlen = mapcode.length() - 1;
+ final int codexLen = mapcode.length() - 1;
// *** long codes in states are handled by the country
- if (incodexlen >= 9) {
+ if (codexLen >= 9) {
+ // International codes are 9 characters.
+ assert codexLen == 9;
territory = Territory.AAA;
} else {
final Territory parentTerritory = territory.getParentTerritory();
- if (((incodexlen >= 8) && ((parentTerritory == Territory.USA) || (parentTerritory == Territory.CAN)
- || (parentTerritory == Territory.AUS) || (parentTerritory == Territory.BRA)
- || (parentTerritory == Territory.CHN) || (parentTerritory == Territory.RUS)))
- || ((incodexlen >= 7) &&
- ((parentTerritory == Territory.IND) || (parentTerritory == Territory.MEX)))) {
-
+ if (((codexLen >= 8) &&
+ ((parentTerritory == Territory.USA) || (parentTerritory == Territory.CAN) ||
+ (parentTerritory == Territory.AUS) || (parentTerritory == Territory.BRA) ||
+ (parentTerritory == Territory.CHN) || (parentTerritory == Territory.RUS))) ||
+ ((codexLen >= 7) &&
+ ((parentTerritory == Territory.IND) || (parentTerritory == Territory.MEX)))) {
territory = parentTerritory;
}
}
+ final int territoryNumber = territory.getNumber();
- final int ccode = territory.getNumber();
-
- final int fromTerritoryRecord = dataModel.getDataFirstRecord(ccode);
- final int uptoTerritoryRecord = dataModel.getDataLastRecord(ccode);
-
- final int incodexhi = mapcode.indexOf('.');
- final int incodex = (incodexhi * 10) + (incodexlen - incodexhi);
+ final int fromTerritoryRecord = DATA_MODEL.getDataFirstRecord(territoryNumber);
+ final int uptoTerritoryRecord = DATA_MODEL.getDataLastRecord(territoryNumber);
- MapcodeZone mapcodeZone = MapcodeZone.empty();
+ // Determine the codex pattern as 2-digits: length-of-left-part * 10 + length-of-right-part.
+ final int positionOfDot = mapcode.indexOf('.');
+ final int codex = (positionOfDot * 10) + (codexLen - positionOfDot);
+ MapcodeZone mapcodeZone = new MapcodeZone();
for (int territoryRecord = fromTerritoryRecord; territoryRecord <= uptoTerritoryRecord; territoryRecord++) {
- final int codexi = Data.getCodex(territoryRecord);
- Boundary boundary = createFromTerritoryRecord(territoryRecord);
+ final int codexOfTerritory = Data.getCodex(territoryRecord);
+ final Boundary boundaryOfTerritory = createBoundaryForTerritoryRecord(territoryRecord);
if (Data.getTerritoryRecordType(territoryRecord) == Data.TERRITORY_RECORD_TYPE_NONE) {
+
if (Data.isNameless(territoryRecord)) {
// i = nameless
- if (((codexi == 21) && (incodex == 22)) ||
- ((codexi == 22) && (incodex == 32)) ||
- ((codexi == 13) && (incodex == 23))) {
- mapcodeZone = decodeNameless(mapcode, territoryRecord, extrapostfix);
+ if (((codexOfTerritory == 21) && (codex == 22)) ||
+ ((codexOfTerritory == 22) && (codex == 32)) ||
+ ((codexOfTerritory == 13) && (codex == 23))) {
+ mapcodeZone = decodeNameless(mapcode, territoryRecord, precisionPostfix);
break;
}
} else {
+
// i = grid without headerletter
- if ((codexi == incodex) || ((incodex == 22) && (codexi == 21))) {
+ if ((codexOfTerritory == codex) ||
+ ((codex == 22) && (codexOfTerritory == 21))) {
mapcodeZone = decodeGrid(mapcode,
- boundary.getLonMicroDegMin(), boundary.getLatMicroDegMin(),
- boundary.getLonMicroDegMax(), boundary.getLatMicroDegMax(),
- territoryRecord, extrapostfix);
+ boundaryOfTerritory.getLonMicroDegMin(), boundaryOfTerritory.getLatMicroDegMin(),
+ boundaryOfTerritory.getLonMicroDegMax(), boundaryOfTerritory.getLatMicroDegMax(),
+ territoryRecord, precisionPostfix);
// first of all, make sure the zone fits the country
- mapcodeZone = mapcodeZone.restrictZoneTo(createFromTerritoryRecord(uptoTerritoryRecord));
+ mapcodeZone = mapcodeZone.restrictZoneTo(createBoundaryForTerritoryRecord(uptoTerritoryRecord));
if (Data.isRestricted(territoryRecord) && !mapcodeZone.isEmpty()) {
int nrZoneOverlaps = 0;
int j;
- Point result = mapcodeZone.getMidPoint();
+ final Point result = mapcodeZone.getCenter();
// see if midpoint of mapcode zone is in any sub-area...
for (j = territoryRecord - 1; j >= fromTerritoryRecord; j--) {
if (!Data.isRestricted(j)) {
- if (createFromTerritoryRecord(j).containsPoint(result)) {
+ if (createBoundaryForTerritoryRecord(j).containsPoint(result)) {
nrZoneOverlaps++;
break;
}
@@ -131,15 +145,15 @@ static Point decode(@Nonnull final String argMapcode,
if (nrZoneOverlaps == 0) {
// see if mapcode zone OVERLAPS any sub-area...
- MapcodeZone zfound = MapcodeZone.empty();
+ MapcodeZone zfound = new MapcodeZone();
for (j = fromTerritoryRecord; j < territoryRecord; j++) { // try all smaller rectangles j
if (!Data.isRestricted(j)) {
- MapcodeZone z = mapcodeZone.restrictZoneTo(createFromTerritoryRecord(j));
+ final MapcodeZone z = mapcodeZone.restrictZoneTo(createBoundaryForTerritoryRecord(j));
if (!z.isEmpty()) {
nrZoneOverlaps++;
if (nrZoneOverlaps == 1) {
// first fit! remember...
- zfound.copyFrom(z);
+ zfound = new MapcodeZone(z);
} else { // nrZoneOverlaps > 1
// more than one hit
break; // give up!
@@ -148,12 +162,12 @@ static Point decode(@Nonnull final String argMapcode,
}
}
if (nrZoneOverlaps == 1) { // intersected exactly ONE sub-area?
- mapcodeZone.copyFrom(zfound); // use the intersection found...
+ mapcodeZone = new MapcodeZone(zfound); // use the intersection found...
}
}
if (nrZoneOverlaps == 0) {
- mapcodeZone.setEmpty();
+ mapcodeZone = new MapcodeZone();
}
}
break;
@@ -161,30 +175,29 @@ static Point decode(@Nonnull final String argMapcode,
}
} else if (Data.getTerritoryRecordType(territoryRecord) == Data.TERRITORY_RECORD_TYPE_PIPE) {
// i = grid with headerletter
- if ((incodex == (codexi + 10)) && (Data.headerLetter(territoryRecord).charAt(0) == mapcode.charAt(0))) {
+ if ((codex == (codexOfTerritory + 10)) &&
+ (Data.headerLetter(territoryRecord).charAt(0) == mapcode.charAt(0))) {
mapcodeZone = decodeGrid(mapcode.substring(1),
- boundary.getLonMicroDegMin(), boundary.getLatMicroDegMin(),
- boundary.getLonMicroDegMax(), boundary.getLatMicroDegMax(),
- territoryRecord, extrapostfix);
+ boundaryOfTerritory.getLonMicroDegMin(), boundaryOfTerritory.getLatMicroDegMin(),
+ boundaryOfTerritory.getLonMicroDegMax(), boundaryOfTerritory.getLatMicroDegMax(),
+ territoryRecord, precisionPostfix);
break;
}
} else {
+ assert (Data.getTerritoryRecordType(territoryRecord) == Data.TERRITORY_RECORD_TYPE_PLUS) ||
+ (Data.getTerritoryRecordType(territoryRecord) == Data.TERRITORY_RECORD_TYPE_STAR);
// i = autoheader
- if (((incodex == 23) && (codexi == 22)) || ((incodex == 33) && (codexi == 23))) {
- mapcodeZone = decodeAutoHeader(mapcode, territoryRecord, extrapostfix);
+ if (((codex == 23) && (codexOfTerritory == 22)) ||
+ ((codex == 33) && (codexOfTerritory == 23))) {
+ mapcodeZone = decodeAutoHeader(mapcode, territoryRecord, precisionPostfix);
break;
}
}
}
- mapcodeZone = mapcodeZone.restrictZoneTo(createFromTerritoryRecord(uptoTerritoryRecord));
-
- final Point result = mapcodeZone.getMidPoint();
-
- LOG.trace("decode: result=({}, {})",
- result.isDefined() ? result.getLatDeg() : Double.NaN,
- result.isDefined() ? result.getLonDeg() : Double.NaN);
- return result;
+ mapcodeZone = mapcodeZone.restrictZoneTo(createBoundaryForTerritoryRecord(uptoTerritoryRecord));
+ LOG.trace("decode: zone={}", mapcodeZone);
+ return mapcodeZone;
}
// ----------------------------------------------------------------------
@@ -224,6 +237,9 @@ public Unicode2Ascii(final char min, final char max, @Nonnull final String conve
}
}
+ // Greek character A.
+ private static final char GREEK_CAPITAL_ALPHA = '\u0391';
+
// Special character '?' indicating missing character in alphabet.
private static final char MISSCODE = '?';
@@ -308,8 +324,14 @@ public Unicode2Ascii(final char min, final char max, @Nonnull final String conve
};
@Nonnull
- private static MapcodeZone decodeGrid(final String str, final int minx, final int miny, final int maxx, final int maxy,
- final int m, final String extrapostfix) {
+ private static MapcodeZone decodeGrid(
+ @Nonnull final String str,
+ final int minx,
+ final int miny,
+ final int maxx,
+ final int maxy,
+ final int m,
+ @Nonnull final String extrapostfix) {
// for a well-formed result, and integer variables
String result = str;
int relx;
@@ -325,12 +347,12 @@ private static MapcodeZone decodeGrid(final String str, final int minx, final in
final int divx;
int divy;
- divy = dataModel.getSmartDiv(m);
+ divy = DATA_MODEL.getSmartDiv(m);
if (divy == 1) {
- divx = Common.xSide[prelen];
- divy = Common.ySide[prelen];
+ divx = Common.X_SIDE[prelen];
+ divy = Common.Y_SIDE[prelen];
} else {
- divx = Common.nc[prelen] / divy;
+ divx = Common.NC[prelen] / divy;
}
if ((prelen == 4) && (divx == 961) && (divy == 961)) {
@@ -355,9 +377,9 @@ private static MapcodeZone decodeGrid(final String str, final int minx, final in
rely = miny + (rely * ygridsize);
relx = minx + (relx * xgridsize);
- final int yp = Common.ySide[postlen];
+ final int yp = Common.Y_SIDE[postlen];
final int dividery = ((ygridsize + yp) - 1) / yp;
- final int xp = Common.xSide[postlen];
+ final int xp = Common.X_SIDE[postlen];
final int dividerx = ((xgridsize + xp) - 1) / xp;
String rest = result.substring(prelen + 1);
@@ -384,10 +406,10 @@ private static MapcodeZone decodeGrid(final String str, final int minx, final in
final int cornery = rely + (dify * dividery);
final int cornerx = relx + (difx * dividerx);
- Point pt = Point.fromMicroDeg(cornery, cornerx);
- if (!(createFromTerritoryRecord(m).containsPoint(pt))) {
- LOG.info("decodeGrid: Failed decodeGrid({}): {} not in {}", str, pt, createFromTerritoryRecord(m));
- return MapcodeZone.empty(); // already out of range
+ final Point pt = Point.fromMicroDeg(cornery, cornerx);
+ if (!(createBoundaryForTerritoryRecord(m).containsPoint(pt))) {
+ LOG.info("decodeGrid: Failed decodeGrid({}): {} not in {}", str, pt, createBoundaryForTerritoryRecord(m));
+ return new MapcodeZone(); // already out of range
}
final int decodeMaxx = ((relx + xgridsize) < maxx) ? (relx + xgridsize) : maxx;
@@ -397,7 +419,10 @@ private static MapcodeZone decodeGrid(final String str, final int minx, final in
}
@Nonnull
- private static MapcodeZone decodeNameless(final String str, final int firstrec, final String extrapostfix) {
+ private static MapcodeZone decodeNameless(
+ @Nonnull final String str,
+ final int firstrec,
+ @Nonnull final String extrapostfix) {
String result = str;
final int codexm = Data.getCodex(firstrec);
if (codexm == 22) {
@@ -406,7 +431,7 @@ private static MapcodeZone decodeNameless(final String str, final int firstrec,
result = result.substring(0, 2) + result.substring(3);
}
- int a = Common.countCityCoordinatesForCountry(codexm, firstrec, firstrec);
+ final int a = Common.countCityCoordinatesForCountry(codexm, firstrec, firstrec);
final int p = 31 / a;
final int r = 31 % a;
@@ -464,15 +489,15 @@ private static MapcodeZone decodeNameless(final String str, final int firstrec,
}
if (nrX > a) { // past end!
- return MapcodeZone.empty();
+ return new MapcodeZone();
}
final int territoryRecord = firstrec + nrX;
- int side = dataModel.getSmartDiv(territoryRecord);
+ int side = DATA_MODEL.getSmartDiv(territoryRecord);
int xSIDE = side;
- Boundary boundary = createFromTerritoryRecord(territoryRecord);
+ final Boundary boundary = createBoundaryForTerritoryRecord(territoryRecord);
final int maxx = boundary.getLonMicroDegMax();
final int maxy = boundary.getLatMicroDegMax();
final int minx = boundary.getLonMicroDegMin();
@@ -497,7 +522,7 @@ private static MapcodeZone decodeNameless(final String str, final int firstrec,
if (dx >= xSIDE) // else out-of-range!
{
LOG.error("decodeGrid: Failed, decodeNameless({}): dx {} > xSIDE {}", str, dx, xSIDE);
- return MapcodeZone.empty(); // return undefined (out of range!)
+ return new MapcodeZone(); // return undefined (out of range!)
}
final int dividerx4 = Common.xDivider(miny, maxy); // 4 times too large!
@@ -510,7 +535,10 @@ private static MapcodeZone decodeNameless(final String str, final int firstrec,
}
@Nonnull
- private static MapcodeZone decodeAutoHeader(final String input, final int m, final String extrapostfix) {
+ private static MapcodeZone decodeAutoHeader(
+ final String input,
+ final int m,
+ @Nonnull final String extrapostfix) {
// returns Point.isUndefined() in case or error
int storageStart = 0;
final int codexm = Data.getCodex(m);
@@ -525,13 +553,13 @@ private static MapcodeZone decodeAutoHeader(final String input, final int m, fin
while (true) {
if ((Data.getTerritoryRecordType(i) < Data.TERRITORY_RECORD_TYPE_PLUS) || (Data.getCodex(i) != codexm)) {
LOG.error("decodeGrid: Failed, decodeAutoHeader({}): out of {} records", input, codexm);
- return MapcodeZone.empty(); // return undefined
+ return new MapcodeZone(); // return undefined
}
- final int maxx = createFromTerritoryRecord(i).getLonMicroDegMax();
- final int maxy = createFromTerritoryRecord(i).getLatMicroDegMax();
- final int minx = createFromTerritoryRecord(i).getLonMicroDegMin();
- final int miny = createFromTerritoryRecord(i).getLatMicroDegMin();
+ final int maxx = createBoundaryForTerritoryRecord(i).getLonMicroDegMax();
+ final int maxy = createBoundaryForTerritoryRecord(i).getLatMicroDegMax();
+ final int minx = createBoundaryForTerritoryRecord(i).getLonMicroDegMin();
+ final int miny = createBoundaryForTerritoryRecord(i).getLatMicroDegMin();
int h = ((maxy - miny) + 89) / 90;
final int xdiv = Common.xDivider(miny, maxy);
@@ -564,7 +592,7 @@ private static MapcodeZone decodeAutoHeader(final String input, final int m, fin
if ((cornerx < minx) || (cornerx >= maxx) || (cornery < miny) || (cornery > maxy)) {
LOG.error("decodeGrid: Failed, decodeAutoHeader({}): corner {}, {} out of bounds", input, cornery, cornerx);
- return MapcodeZone.empty(); // corner out of bounds
+ return new MapcodeZone(); // corner out of bounds
}
return decodeExtension(cornery, cornerx, dividerx << 2, -dividery, extrapostfix,
@@ -576,7 +604,7 @@ private static MapcodeZone decodeAutoHeader(final String input, final int m, fin
}
@Nonnull
- private static String aeuUnpack(final String argStr) {
+ private static String aeuUnpack(@Nonnull final String argStr) {
// unpack encoded into all-digit
// (assume str already uppercase!), returns "" in case of error
String str = decodeUTF16(argStr);
@@ -663,7 +691,8 @@ private static String aeuUnpack(final String argStr) {
* @param mapcode Unicode string.
* @return ASCII string.
*/
- static String decodeUTF16(final String mapcode) {
+ @Nonnull
+ static String decodeUTF16(@Nonnull final String mapcode) {
String result;
final StringBuilder asciiBuf = new StringBuilder();
for (final char ch : mapcode.toCharArray()) {
@@ -706,7 +735,10 @@ static String decodeUTF16(final String mapcode) {
}
}
- static String encodeUTF16(final String mapcodeInput, final int alphabetCode) throws IllegalArgumentException {
+ @Nonnull
+ static String encodeUTF16(
+ @Nonnull final String mapcodeInput,
+ final int alphabetCode) throws IllegalArgumentException {
final String mapcode;
if ((alphabetCode == Alphabet.GREEK.getNumber()) ||
@@ -747,7 +779,7 @@ static String encodeUTF16(final String mapcodeInput, final int alphabetCode) thr
}
@Nonnull
- private static Point decodeTriple(final String str) {
+ private static Point decodeTriple(@Nonnull final String str) {
final int c1 = DECODE_CHARS[(int) str.charAt(0)];
final int x = decodeBase31(str.substring(1));
if (c1 < 24) {
@@ -757,8 +789,11 @@ private static Point decodeTriple(final String str) {
}
@Nonnull
- private static Point decodeSixWide(final int v, final int width, final int height) {
- int d;
+ private static Point decodeSixWide(
+ final int v,
+ final int width,
+ final int height) {
+ final int d;
int col = v / (height * 6);
final int maxcol = (width - 4) / 6;
if (col >= maxcol) {
@@ -774,7 +809,7 @@ private static Point decodeSixWide(final int v, final int width, final int heigh
// / lowest level encode/decode routines
// decode up to dot or EOS;
// returns negative in case of error
- private static int decodeBase31(final String code) {
+ private static int decodeBase31(@Nonnull final String code) {
int value = 0;
for (final char c : code.toCharArray()) {
if (c == '.') {
@@ -789,9 +824,16 @@ private static int decodeBase31(final String code) {
}
@Nonnull
- private static MapcodeZone decodeExtension(final int y, final int x, final int dividerx0, final int dividery0,
- final String extrapostfix, final int lon_offset4, final int extremeLatMicroDeg, final int maxLonMicroDeg) {
- MapcodeZone mapcodeZone = new MapcodeZone();
+ private static MapcodeZone decodeExtension(
+ final int y,
+ final int x,
+ final int dividerx0,
+ final int dividery0,
+ @Nonnull final String extrapostfix,
+ final int lon_offset4,
+ final int extremeLatMicroDeg,
+ final int maxLonMicroDeg) {
+ final MapcodeZone mapcodeZone = new MapcodeZone();
double dividerx4 = (double) dividerx0;
double dividery = (double) dividery0;
double processor = 1;
@@ -807,7 +849,7 @@ private static MapcodeZone decodeExtension(final int y, final int x, final int d
c1 = DECODE_CHARS[c1];
if ((c1 < 0) || (c1 == 30)) {
LOG.error("decodeGrid; Failed, decodeExtension({}): illegal c1 {}", extrapostfix, c1);
- return MapcodeZone.empty();
+ return new MapcodeZone();
}
final int y1 = c1 / 5;
final int x1 = c1 % 5;
@@ -819,7 +861,7 @@ private static MapcodeZone decodeExtension(final int y, final int x, final int d
c2 = DECODE_CHARS[c2];
if ((c2 < 0) || (c2 == 30)) {
LOG.error("decodeGrid: Failed, decodeExtension({}): illegal c2 {}", extrapostfix, c2);
- return MapcodeZone.empty();
+ return new MapcodeZone();
}
y2 = c2 / 6;
x2 = c2 % 6;
@@ -840,8 +882,8 @@ private static MapcodeZone decodeExtension(final int y, final int x, final int d
processor *= 30;
}
- double lon4 = (x * 4 * Point.MAX_PRECISION_FACTOR) + (lon32 * dividerx4) + (lon_offset4 * Point.MAX_PRECISION_FACTOR);
- double lat1 = (y * Point.MAX_PRECISION_FACTOR) + (lat32 * dividery);
+ final double lon4 = (x * 4 * Point.MAX_PRECISION_FACTOR) + (lon32 * dividerx4) + (lon_offset4 * Point.MAX_PRECISION_FACTOR);
+ final double lat1 = (y * Point.MAX_PRECISION_FACTOR) + (lat32 * dividery);
// determine the range of coordinates that are encode to this mapcode
if (odd) { // odd
@@ -866,9 +908,9 @@ private static MapcodeZone decodeExtension(final int y, final int x, final int d
return mapcodeZone;
}
- private static boolean isAbjadScript(final String argStr) {
+ private static boolean isAbjadScript(@Nonnull final String argStr) {
for (final char ch : argStr.toCharArray()) {
- int c = (int) ch;
+ final int c = (int) ch;
if ((c >= 0x0628) && (c <= 0x0649)) {
return true; // Arabic
}
@@ -878,14 +920,15 @@ private static boolean isAbjadScript(final String argStr) {
if ((c >= 0x388) && (c <= 0x3C9)) {
return true; // Greek uppercase and lowecase
}
- if ((c >= 0x1100) && (c <= 0x1174) || (c >= 0xad6c) && (c <= 0xd314)) {
+ if (((c >= 0x1100) && (c <= 0x1174)) || ((c >= 0xad6c) && (c <= 0xd314))) {
return true; // Korean
}
}
return false;
}
- private static String convertFromAbjad(final String mapcode) {
+ @Nonnull
+ private static String convertFromAbjad(@Nonnull final String mapcode) {
// split into prefix, s, postfix
int p = mapcode.lastIndexOf(' ');
if (p < 0) {
@@ -894,7 +937,7 @@ private static String convertFromAbjad(final String mapcode) {
p++;
}
final String prefix = mapcode.substring(0, p);
- String remainder = mapcode.substring(p);
+ final String remainder = mapcode.substring(p);
final String postfix;
final int h = remainder.indexOf('-');
final String s;
@@ -961,7 +1004,8 @@ private static String convertFromAbjad(final String mapcode) {
}
@SuppressWarnings("NumericCastThatLosesPrecision")
- private static String convertToAbjad(final String mapcode) {
+ @Nonnull
+ private static String convertToAbjad(@Nonnull final String mapcode) {
String str;
final String rest;
final int h = mapcode.indexOf('-');
diff --git a/src/main/java/com/mapcode/Encoder.java b/src/main/java/com/mapcode/Encoder.java
index e3522be..8e9638a 100644
--- a/src/main/java/com/mapcode/Encoder.java
+++ b/src/main/java/com/mapcode/Encoder.java
@@ -24,13 +24,21 @@
import java.util.ArrayList;
import java.util.List;
-import static com.mapcode.Boundary.createFromTerritoryRecord;
+import static com.mapcode.Boundary.createBoundaryForTerritoryRecord;
import static com.mapcode.Common.*;
+/**
+ * ----------------------------------------------------------------------------------------------
+ * Package private implementation class. For internal use within the Mapcode implementation only.
+ * ----------------------------------------------------------------------------------------------
+ *
+ * This class contains encoder for mapcodes.
+ */
class Encoder {
private static final Logger LOG = LoggerFactory.getLogger(Encoder.class);
- private static final DataModel dataModel = DataModel.getInstance();
+ // Get direct access to data model singleton.
+ private static final DataModel DATA_MODEL = DataModel.getInstance();
private Encoder() {
// Prevent instantiation.
@@ -45,9 +53,9 @@ static List encode(
final double latDeg,
final double lonDeg,
@Nullable final Territory territory,
- final boolean stopWithOneResult) {
+ final boolean limitToOneResult) {
- return encode(latDeg, lonDeg, territory, stopWithOneResult, null);
+ return encode(latDeg, lonDeg, territory, limitToOneResult, null);
}
// ----------------------------------------------------------------------
@@ -58,68 +66,88 @@ static List encode(
'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'A', 'E', 'U'};
@Nonnull
- private static List encode(final double argLatDeg, final double argLonDeg,
- @Nullable final Territory territory, final boolean limitToOneResult,
- @Nullable final Territory argStateOverride) {
+ private static List encode(
+ final double argLatDeg,
+ final double argLonDeg,
+ @Nullable final Territory territory,
+ final boolean limitToOneResult,
+ @Nullable final Territory argStateOverride) {
LOG.trace("encode: latDeg={}, lonDeg={}, territory={}, limitToOneResult={}",
argLatDeg, argLonDeg, (territory == null) ? null : territory.name(), limitToOneResult);
final Point pointToEncode = Point.fromDeg(argLatDeg, argLonDeg);
-
final List results = new ArrayList();
+ int lastBaseSubTerritoryNumber = -1;
- int lastbasesubareaID = -1;
+ // Determine whether to walk through all records, or just for one (given) territory.
+ final int firstTerritoryRecord = (territory != null) ? territory.getNumber() : 0;
+ final int lastTerritoryRecord = (territory != null) ? territory.getNumber() : Territory.AAA.getNumber();
+ for (int territoryRecord = firstTerritoryRecord; territoryRecord <= lastTerritoryRecord; territoryRecord++) {
- final int firstNr = (territory != null) ? territory.getNumber() : 0;
- final int lastNr = (territory != null) ? territory.getNumber() : Territory.AAA.getNumber();
- for (int ccode = firstNr; ccode <= lastNr; ccode++) {
+ // Check if the point to encode is covered by the last data record.
+ final int firstSubTerritoryRecord = DATA_MODEL.getDataLastRecord(territoryRecord);
+ if (createBoundaryForTerritoryRecord(firstSubTerritoryRecord).containsPoint(pointToEncode)) {
- final int uptoTerritoryRecord = dataModel.getDataLastRecord(ccode);
- if (!createFromTerritoryRecord(uptoTerritoryRecord).containsPoint(pointToEncode)) {
- continue;
- }
- final int fromTerritoryRecord = dataModel.getDataFirstRecord(ccode);
- final Territory currentEncodeTerritory = Territory.fromNumber(ccode);
-
- for (int territoryRecord = fromTerritoryRecord; territoryRecord <= uptoTerritoryRecord; territoryRecord++) {
- if (createFromTerritoryRecord(territoryRecord).containsPoint(pointToEncode)) {
- String mapcode = "";
- if (Data.isNameless(territoryRecord)) {
- mapcode = encodeNameless(pointToEncode, territoryRecord, fromTerritoryRecord);
- } else if (Data.getTerritoryRecordType(territoryRecord) > Data.TERRITORY_RECORD_TYPE_PIPE) {
- mapcode = encodeAutoHeader(pointToEncode, territoryRecord);
- } else if ((territoryRecord == uptoTerritoryRecord) && (currentEncodeTerritory.getParentTerritory() != null)) {
- results.addAll(encode(argLatDeg, argLonDeg, currentEncodeTerritory.getParentTerritory(), limitToOneResult,
- currentEncodeTerritory));
- continue;
- } else if (!Data.isRestricted(territoryRecord) || (lastbasesubareaID == fromTerritoryRecord)) {
- if (Data.getCodex(territoryRecord) < 54) {
- mapcode = encodeGrid(territoryRecord, pointToEncode);
- }
- }
+ final int lastSubTerritoryRecord = DATA_MODEL.getDataFirstRecord(territoryRecord);
+ final Territory currentEncodeTerritory = Territory.fromNumber(territoryRecord);
- if (mapcode.length() > 4) {
- mapcode = aeuPack(mapcode, false);
+ for (int subTerritoryRecord = lastSubTerritoryRecord; subTerritoryRecord <= firstSubTerritoryRecord; subTerritoryRecord++) {
- final Territory encodeTerritory = (argStateOverride != null) ? argStateOverride : currentEncodeTerritory;
+ // Check if the point to encode is contained within the boundary.
+ if (createBoundaryForTerritoryRecord(subTerritoryRecord).containsPoint(pointToEncode)) {
- // Create new result.
- final Mapcode newResult = new Mapcode(mapcode, encodeTerritory);
+ // All fine, proceed with creating a mapcode.
+ String mapcode = "";
+ if (Data.isNameless(subTerritoryRecord)) {
+ mapcode = encodeNameless(pointToEncode, subTerritoryRecord, lastSubTerritoryRecord);
- // The result should not be stored yet.
- if (!results.contains(newResult)) {
- if (limitToOneResult) {
- results.clear();
+ } else if (Data.getTerritoryRecordType(subTerritoryRecord) > Data.TERRITORY_RECORD_TYPE_PIPE) {
+ mapcode = encodeAutoHeader(pointToEncode, subTerritoryRecord);
+
+ } else if ((subTerritoryRecord == firstSubTerritoryRecord) &&
+ (currentEncodeTerritory.getParentTerritory() != null)) {
+ results.addAll(encode(argLatDeg, argLonDeg, currentEncodeTerritory.getParentTerritory(),
+ limitToOneResult, currentEncodeTerritory));
+ continue;
+
+ } else if (!Data.isRestricted(subTerritoryRecord) || (lastBaseSubTerritoryNumber == lastSubTerritoryRecord)) {
+ if (Data.getCodex(subTerritoryRecord) < 54) {
+ mapcode = encodeGrid(subTerritoryRecord, pointToEncode);
}
- results.add(newResult);
} else {
- LOG.error("encode: Duplicate results found, newResult={}, results={} items",
- newResult.getCodeWithTerritory(), results.size());
+ // Skip this record.
}
- lastbasesubareaID = fromTerritoryRecord;
- if (limitToOneResult) {
- return results;
+ // Check if we created a mapcode.
+ if (!mapcode.isEmpty()) {
+ assert mapcode.length() > 4;
+ mapcode = aeuPack(mapcode, false);
+
+ final Territory encodeTerritory = (argStateOverride != null) ?
+ argStateOverride : currentEncodeTerritory;
+
+ // Create new result.
+ final Mapcode newResult = new Mapcode(mapcode, encodeTerritory);
+
+ // The result should not be stored yet.
+ if (!results.contains(newResult)) {
+
+ // Remove existing results (if there was a parent territory).
+ if (limitToOneResult) {
+ results.clear();
+ }
+ results.add(newResult);
+ } else {
+ LOG.error("encode: Duplicate results found, newResult={}, results={} items",
+ newResult.getCodeWithTerritory(), results.size());
+ }
+
+ lastBaseSubTerritoryNumber = lastSubTerritoryRecord;
+
+ // Stop if we only need a single result anyway.
+ if (limitToOneResult) {
+ return results;
+ }
}
}
}
@@ -130,7 +158,14 @@ private static List encode(final double argLatDeg, final double argLonD
return results;
}
- private static String encodeExtension(final Point pointToEncode, final int extrax4, final int extray, final int dividerx4, final int dividery, final int ydirection) {
+ @Nonnull
+ private static String encodeExtension(
+ final Point pointToEncode,
+ final int extrax4,
+ final int extray,
+ final int dividerx4,
+ final int dividery,
+ final int ydirection) {
int extraDigits = 8; // always generate 8 digits
double factorx = Point.MAX_PRECISION_FACTOR * dividerx4;
@@ -168,8 +203,11 @@ private static String encodeExtension(final Point pointToEncode, final int extra
return sb.toString();
}
- private static String encodeGrid(final int m, final Point pointToEncode) {
- int codexm = Data.getCodex(m);
+ @Nonnull
+ private static String encodeGrid(
+ final int territoryNumber,
+ @Nonnull final Point pointToEncode) {
+ int codexm = Data.getCodex(territoryNumber);
final int orgcodex = codexm;
if (codexm == 21) {
codexm = 22;
@@ -179,18 +217,18 @@ private static String encodeGrid(final int m, final Point pointToEncode) {
final int prelen = codexm / 10;
final int postlen = codexm % 10;
final int divx;
- int divy = dataModel.getSmartDiv(m);
+ int divy = DATA_MODEL.getSmartDiv(territoryNumber);
if (divy == 1) {
- divx = xSide[prelen];
- divy = ySide[prelen];
+ divx = X_SIDE[prelen];
+ divy = Y_SIDE[prelen];
} else {
- divx = nc[prelen] / divy;
+ divx = NC[prelen] / divy;
}
- final int minx = createFromTerritoryRecord(m).getLonMicroDegMin();
- final int miny = createFromTerritoryRecord(m).getLatMicroDegMin();
- final int maxx = createFromTerritoryRecord(m).getLonMicroDegMax();
- final int maxy = createFromTerritoryRecord(m).getLatMicroDegMax();
+ final int minx = createBoundaryForTerritoryRecord(territoryNumber).getLonMicroDegMin();
+ final int miny = createBoundaryForTerritoryRecord(territoryNumber).getLatMicroDegMin();
+ final int maxx = createBoundaryForTerritoryRecord(territoryNumber).getLonMicroDegMax();
+ final int maxy = createBoundaryForTerritoryRecord(territoryNumber).getLatMicroDegMax();
final int ygridsize = (((maxy - miny) + divy) - 1) / divy;
int rely = pointToEncode.getLatMicroDeg() - miny;
@@ -230,8 +268,8 @@ private static String encodeGrid(final int m, final Point pointToEncode) {
rely = miny + (rely * ygridsize);
relx = minx + (relx * xgridsize);
- final int dividery = ((ygridsize + ySide[postlen]) - 1) / ySide[postlen];
- final int dividerx = ((xgridsize + xSide[postlen]) - 1) / xSide[postlen];
+ final int dividery = ((ygridsize + Y_SIDE[postlen]) - 1) / Y_SIDE[postlen];
+ final int dividerx = ((xgridsize + X_SIDE[postlen]) - 1) / X_SIDE[postlen];
result += '.';
@@ -244,12 +282,12 @@ private static String encodeGrid(final int m, final Point pointToEncode) {
difx = difx / dividerx;
dify = dify / dividery;
- dify = ySide[postlen] - 1 - dify;
+ dify = Y_SIDE[postlen] - 1 - dify;
if (postlen == 3) {
result += encodeTriple(difx, dify);
} else {
- String postfix = encodeBase31(((difx) * ySide[postlen]) + dify, postlen);
+ String postfix = encodeBase31(((difx) * Y_SIDE[postlen]) + dify, postlen);
if (postlen == 4) {
postfix = String.valueOf(postfix.charAt(0)) + postfix.charAt(2) + postfix.charAt(1) + postfix.charAt(3);
}
@@ -262,16 +300,19 @@ private static String encodeGrid(final int m, final Point pointToEncode) {
result += encodeExtension(pointToEncode, extrax << 2, extray, dividerx << 2, dividery, 1); // grid
- return Data.headerLetter(m) + result;
+ return Data.headerLetter(territoryNumber) + result;
}
- private static String encodeAutoHeader(final Point pointToEncode, final int thisindex) {
- final StringBuilder autoheader_result = new StringBuilder();
- final int codexm = Data.getCodex(thisindex);
+ @Nonnull
+ private static String encodeAutoHeader(
+ @Nonnull final Point pointToEncode,
+ final int territoryRecord) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ final int codexm = Data.getCodex(territoryRecord);
int storageStart = 0;
// search back to first pipe star
- int firstindex = thisindex;
+ int firstindex = territoryRecord;
while ((Data.getTerritoryRecordType(firstindex - 1) > Data.TERRITORY_RECORD_TYPE_PIPE) && (Data.getCodex(firstindex - 1) == codexm)) {
firstindex--;
}
@@ -279,10 +320,10 @@ private static String encodeAutoHeader(final Point pointToEncode, final int this
int i = firstindex;
while (true) {
- final int maxx = createFromTerritoryRecord(i).getLonMicroDegMax();
- final int maxy = createFromTerritoryRecord(i).getLatMicroDegMax();
- final int minx = createFromTerritoryRecord(i).getLonMicroDegMin();
- final int miny = createFromTerritoryRecord(i).getLatMicroDegMin();
+ final int maxx = createBoundaryForTerritoryRecord(i).getLonMicroDegMax();
+ final int maxy = createBoundaryForTerritoryRecord(i).getLatMicroDegMax();
+ final int minx = createBoundaryForTerritoryRecord(i).getLonMicroDegMin();
+ final int miny = createBoundaryForTerritoryRecord(i).getLatMicroDegMin();
int h = ((maxy - miny) + 89) / 90;
final int xdiv = xDivider(miny, maxy);
@@ -300,7 +341,7 @@ private static String encodeAutoHeader(final Point pointToEncode, final int this
((((storageStart + product + goodRounder) - 1) / goodRounder) * goodRounder) - storageStart;
}
- if (i == thisindex) {
+ if (i == territoryRecord) {
final int dividerx = (((maxx - minx) + w) - 1) / w;
final int vx = (pointToEncode.getLonMicroDeg() - minx) / dividerx;
final int extrax = (pointToEncode.getLonMicroDeg() - minx) % dividerx;
@@ -317,13 +358,13 @@ private static String encodeAutoHeader(final Point pointToEncode, final int this
value += (vy / 176);
final int codexlen = (codexm / 10) + (codexm % 10);
- autoheader_result.append(encodeBase31((storageStart / (961 * 31)) + value, codexlen - 2));
- autoheader_result.append('.');
- autoheader_result.append(encodeTriple(vx % 168, vy % 176));
+ stringBuilder.append(encodeBase31((storageStart / (961 * 31)) + value, codexlen - 2));
+ stringBuilder.append('.');
+ stringBuilder.append(encodeTriple(vx % 168, vy % 176));
- autoheader_result.append(
+ stringBuilder.append(
encodeExtension(pointToEncode, extrax << 2, extray, dividerx << 2, dividery, -1)); // AutoHeader
- return autoheader_result.toString();
+ return stringBuilder.toString();
}
storageStart += product;
@@ -331,17 +372,20 @@ private static String encodeAutoHeader(final Point pointToEncode, final int this
}
}
- // mid-level encode/decode
- private static String encodeNameless(final Point pointToEncode, final int index, final int firstcode)
- // returns "" in case of (argument) error
- {
- final int codexm = Data.getCodex(index);
+ @Nonnull
+ private static String encodeNameless(
+ @Nonnull final Point pointToEncode,
+ final int territoryRecord,
+ final int firstTerritoryRecord) {
+ // mid-level encode/decode
+ // returns "" in case of (argument) error
+ final int codexm = Data.getCodex(territoryRecord);
final int codexlen = (codexm / 10) + (codexm % 10);
- final int first_nameless_record = getFirstNamelessRecord(codexm, index, firstcode);
- final int a = countCityCoordinatesForCountry(codexm, index, firstcode);
+ final int firstNamelessRecord = getFirstNamelessRecord(codexm, territoryRecord, firstTerritoryRecord);
+ final int a = countCityCoordinatesForCountry(codexm, territoryRecord, firstTerritoryRecord);
final int p = 31 / a;
final int r = 31 % a;
- final int nrX = index - first_nameless_record;
+ final int nrX = territoryRecord - firstNamelessRecord;
int storage_offset;
@@ -368,13 +412,13 @@ private static String encodeNameless(final Point pointToEncode, final int index,
storage_offset = nrX * basePowerA;
}
- int side = dataModel.getSmartDiv(index);
+ int side = DATA_MODEL.getSmartDiv(territoryRecord);
final int orgSide = side;
int xSide = side;
- final int maxy = createFromTerritoryRecord(index).getLatMicroDegMax();
- final int minx = createFromTerritoryRecord(index).getLonMicroDegMin();
- final int miny = createFromTerritoryRecord(index).getLatMicroDegMin();
+ final int maxy = createBoundaryForTerritoryRecord(territoryRecord).getLatMicroDegMax();
+ final int minx = createBoundaryForTerritoryRecord(territoryRecord).getLonMicroDegMin();
+ final int miny = createBoundaryForTerritoryRecord(territoryRecord).getLatMicroDegMin();
final int dividerx4 = xDivider(miny, maxy);
final int xFracture = pointToEncode.getLonFraction() / 810000;
@@ -393,7 +437,7 @@ private static String encodeNameless(final Point pointToEncode, final int index,
}
int v = storage_offset;
- if (Data.isSpecialShape(index)) {
+ if (Data.isSpecialShape(territoryRecord)) {
xSide *= side;
side = 1 + ((maxy - miny) / 90);
xSide = xSide / side;
@@ -407,7 +451,7 @@ private static String encodeNameless(final Point pointToEncode, final int index,
if (codexlen == 3) {
result = result.substring(0, 2) + '.' + result.substring(2);
} else if (codexlen == 4) {
- if ((codexm == 22) && (a < 62) && (orgSide == 961) && !Data.isSpecialShape(index)) {
+ if ((codexm == 22) && (a < 62) && (orgSide == 961) && !Data.isSpecialShape(territoryRecord)) {
result = result.substring(0, 2) + result.charAt(3) + result.charAt(2) + result.charAt(4);
}
if (codexm == 13) {
@@ -421,8 +465,11 @@ private static String encodeNameless(final Point pointToEncode, final int index,
return result;
}
- // add vowels to prevent all-digit mapcodes
- static String aeuPack(final String argStr, final boolean argShort) {
+ @Nonnull
+ static String aeuPack(
+ @Nonnull final String argStr,
+ final boolean argShort) {
+ // add vowels to prevent all-digit mapcodes
String str = argStr;
int dotpos = -9;
int rlen = str.length();
@@ -457,7 +504,10 @@ static String aeuPack(final String argStr, final boolean argShort) {
return str + rest;
}
- private static String encodeBase31(final int argValue, final int argNrChars) {
+ @Nonnull
+ private static String encodeBase31(
+ final int argValue,
+ final int argNrChars) {
int value = argValue;
int nrChars = argNrChars;
final StringBuilder result = new StringBuilder();
@@ -469,8 +519,12 @@ private static String encodeBase31(final int argValue, final int argNrChars) {
return result.toString();
}
- private static int encodeSixWide(final int x, final int y, final int width, final int height) {
- int d;
+ private static int encodeSixWide(
+ final int x,
+ final int y,
+ final int width,
+ final int height) {
+ final int d;
int col = x / 6;
final int maxcol = (width - 4) / 6;
if (col >= maxcol) {
@@ -482,7 +536,10 @@ private static int encodeSixWide(final int x, final int y, final int width, fina
return ((height * 6 * col) + ((height - 1 - y) * d) + x) - (col * 6);
}
- private static String encodeTriple(final int difx, final int dify) {
+ @Nonnull
+ private static String encodeTriple(
+ final int difx,
+ final int dify) {
if (dify < (4 * 34)) {
return ENCODE_CHARS[((difx / 28) + (6 * (dify / 34)))] + encodeBase31(((difx % 28) * 34) + (dify % 34), 2);
} else {
diff --git a/src/main/java/com/mapcode/Mapcode.java b/src/main/java/com/mapcode/Mapcode.java
index c7adfcb..f7ef818 100644
--- a/src/main/java/com/mapcode/Mapcode.java
+++ b/src/main/java/com/mapcode/Mapcode.java
@@ -75,9 +75,8 @@ public final class Mapcode {
* @throws IllegalArgumentException Thrown if syntax not valid or if the mapcode string contains
* territory information.
*/
- public Mapcode(
- @Nonnull final String code,
- @Nonnull final Territory territory) throws IllegalArgumentException {
+ public Mapcode(@Nonnull final String code,
+ @Nonnull final Territory territory) throws IllegalArgumentException {
checkMapcodeCode("code", code);
final String ascii = convertStringToPlainAscii(code);
@@ -90,20 +89,17 @@ public Mapcode(
final int hyphenPos = codeUppercase.indexOf('-');
if (hyphenPos < 0) {
codeUppercase = codeUppercase + "-K3000000";
- }
- else {
+ } else {
final int extensionLength = codeUppercase.length() - 1 - hyphenPos;
if (extensionLength < 8) {
if ((extensionLength % 2) == 1) {
// Odd extension.
codeUppercase = codeUppercase + ("HH000000".substring(0, 8 - extensionLength));
- }
- else {
+ } else {
// Even extension.
codeUppercase = codeUppercase + ("K3000000".substring(0, 8 - extensionLength));
}
- }
- else if (extensionLength > 8) {
+ } else if (extensionLength > 8) {
// Cut to 8 characters.
codeUppercase = codeUppercase.substring(0, hyphenPos + 9);
}
@@ -157,12 +153,10 @@ public String getCode() {
public String getCode(final int precision, @Nullable final Alphabet alphabet) {
if (precision == 0) {
return convertStringToAlphabet(codePrecision8.substring(0, codePrecision8.length() - 9), alphabet);
- }
- else if (precision <= 8) {
+ } else if (precision <= 8) {
return convertStringToAlphabet(codePrecision8.substring(0, (codePrecision8.length() - 8) + precision),
- alphabet);
- }
- else {
+ alphabet);
+ } else {
throw new IllegalArgumentException("getCodePrecision: precision must be in [0, 8]");
}
}
@@ -256,11 +250,11 @@ public Territory getTerritory() {
* 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}+)?";
+ 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}+";
+ 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}+";
+ 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}+";
@@ -270,10 +264,10 @@ public Territory getTerritory() {
*/
@Nonnull
public static final String REGEX_MAPCODE = '(' + REGEX_TERRITORY + "[ ]+)?" +
- REGEX_CODE_PREFIX + "[.]" + REGEX_CODE_POSTFIX + '(' + REGEX_CODE_PRECISION + ")?";
+ REGEX_CODE_PREFIX + "[.]" + REGEX_CODE_POSTFIX + '(' + REGEX_CODE_PRECISION + ")?";
@Nonnull
- static final Pattern PATTERN_MAPCODE = Pattern.compile('^' + REGEX_MAPCODE + '$');
+ static final Pattern PATTERN_MAPCODE = Pattern.compile('^' + REGEX_MAPCODE + '$');
@Nonnull
static final Pattern PATTERN_TERRITORY = Pattern.compile('^' + REGEX_TERRITORY + ' ');
@Nonnull
@@ -298,7 +292,7 @@ public static int getPrecisionFormat(@Nonnull final String mapcode) throws Unkno
// Syntax needs to be OK.
if (!PATTERN_MAPCODE.matcher(decodedMapcode).matches()) {
throw new UnknownPrecisionFormatException(decodedMapcode + " is not a correctly formatted mapcode code; " +
- "the regular expression for the mapcode code syntax is: " + REGEX_MAPCODE);
+ "the regular expression for the mapcode code syntax is: " + REGEX_MAPCODE);
}
// Precision part should be OK.
@@ -325,8 +319,7 @@ public static boolean isValidMapcodeFormat(@Nonnull final String mapcode) throws
// Throws an exception if the format is incorrect.
getPrecisionFormat(mapcode.toUpperCase());
return true;
- }
- catch (final UnknownPrecisionFormatException ignored) {
+ } catch (final UnknownPrecisionFormatException ignored) {
return false;
}
}
@@ -348,15 +341,15 @@ public static boolean containsTerritory(@Nonnull final String mapcode) throws Il
* location used for encoding the mapcode.
*/
private static final double[] PRECISION_0_MAX_OFFSET_METERS = {
- 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
+ 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
};
/**
@@ -400,7 +393,7 @@ static String convertStringToPlainAscii(@Nonnull final String string) {
@Nonnull
static String convertStringToAlphabet(@Nonnull final String string, @Nullable final Alphabet alphabet) throws IllegalArgumentException {
return (alphabet != null) ? Decoder.encodeUTF16(string.toUpperCase(), alphabet.getNumber()) :
- string.toUpperCase();
+ string.toUpperCase();
}
/**
@@ -430,6 +423,6 @@ public boolean equals(@Nullable final Object obj) {
}
final Mapcode that = (Mapcode) obj;
return this.territory.equals(that.territory) &&
- this.codePrecision8.equals(that.codePrecision8);
+ this.codePrecision8.equals(that.codePrecision8);
}
}
diff --git a/src/main/java/com/mapcode/MapcodeCodec.java b/src/main/java/com/mapcode/MapcodeCodec.java
index b3a57d7..385d8ed 100644
--- a/src/main/java/com/mapcode/MapcodeCodec.java
+++ b/src/main/java/com/mapcode/MapcodeCodec.java
@@ -34,7 +34,8 @@
*/
public final class MapcodeCodec {
- private static final DataModel dataModel = DataModel.getInstance();
+ // Get direct access to the data model.
+ private static final DataModel DATA_MODEL = DataModel.getInstance();
private MapcodeCodec() {
// Prevent instantiation.
@@ -98,15 +99,13 @@ public static List encode(@Nonnull final Point point)
public static List encode(final double latDeg, final double lonDeg,
@Nullable final Territory restrictToTerritory)
throws IllegalArgumentException {
- // Call Mapcode encoder.
- final List results = Encoder.encode(latDeg, lonDeg, restrictToTerritory, /* Stop with one result: */ false);
+ final List results = Encoder.encode(latDeg, lonDeg, restrictToTerritory, false);
assert results != null;
return results;
}
@Nonnull
- public static List encode(@Nonnull final Point point,
- @Nullable final Territory restrictToTerritory)
+ public static List encode(@Nonnull final Point point, @Nullable final Territory restrictToTerritory)
throws IllegalArgumentException {
checkNonnull("point", point);
return encode(point.getLatDeg(), point.getLonDeg(), restrictToTerritory);
@@ -220,50 +219,62 @@ public static Point decode(@Nonnull final String mapcode)
*/
@SuppressWarnings("DuplicateThrows")
@Nonnull
- public static Point decode(
- @Nonnull final String mapcode,
- @Nullable final Territory defaultTerritoryContext)
+ public static Point decode(@Nonnull final String mapcode, @Nullable final Territory defaultTerritoryContext)
throws UnknownMapcodeException, IllegalArgumentException, UnknownPrecisionFormatException {
checkNonnull("mapcode", mapcode);
- // Clean up mapcode.
- String mapcodeClean = Mapcode.convertStringToPlainAscii(mapcode.trim()).toUpperCase();
-
- // Determine territory from mapcode.
- final Territory territory;
- final Matcher matcherTerritory = Mapcode.PATTERN_TERRITORY.matcher(mapcodeClean);
- if (!matcherTerritory.find()) {
-
- // No territory code was supplied in the string, use specified territory context parameter.
- territory = (defaultTerritoryContext != null) ? defaultTerritoryContext : Territory.AAA;
- } else {
-
- // Use the territory code from the string.
- final String territoryName = mapcodeClean.substring(matcherTerritory.start(), matcherTerritory.end()).trim();
- try {
- territory = Territory.fromString(territoryName);
- } catch (final UnknownTerritoryException ignored) {
- throw new UnknownMapcodeException("Wrong territory code: " + territoryName);
- }
-
- // Cut off the territory part.
- mapcodeClean = mapcodeClean.substring(matcherTerritory.end()).trim();
+ final MapcodeZone mapcodeZone = decodeToMapcodeZone(mapcode, defaultTerritoryContext);
+ if (mapcodeZone.isEmpty()) {
+ throw new UnknownMapcodeException("Unknown mapcode, mapcode=" + mapcode + ", territoryContext=" + defaultTerritoryContext);
}
+ return mapcodeZone.getCenter();
+ }
- // Throws an exception if the format is incorrect.
- getPrecisionFormat(mapcodeClean);
-
- @Nonnull final Point point = Decoder.decode(mapcodeClean, territory);
- assert point != null;
+ /**
+ * Decode a mapcode to a Rectangle, which defines the valid zone for a mapcode. The boundaries of the
+ * mapcode zone are inclusive for the South and West borders and exclusive for the North and East borders.
+ * This is essentially the same call as a 'decode', except it returns a rectangle, rather than its center point.
+ *
+ * @param mapcode Mapcode.
+ * @return Rectangle Mapcode zone. South/West borders are inclusive, North/East borders exclusive.
+ * @throws UnknownMapcodeException Thrown if the mapcode has the correct syntax,
+ * but cannot be decoded into a point.
+ * @throws UnknownPrecisionFormatException Thrown if the precision format is incorrect.
+ * @throws IllegalArgumentException Thrown if arguments are null, or if the syntax of the mapcode is incorrect.
+ */
+ @Nonnull
+ public static Rectangle decodeToRectangle(@Nonnull final String mapcode)
+ throws UnknownMapcodeException, IllegalArgumentException, UnknownPrecisionFormatException {
+ return decodeToRectangle(mapcode, Territory.AAA);
+ }
- // Points can only be undefined within the mapcode implementation. Throw an exception here if undefined.
- if (!point.isDefined()) {
- throw new UnknownMapcodeException("Unknown Mapcode: " + mapcodeClean +
- ", territoryContext=" + defaultTerritoryContext);
- }
- return point;
+ /**
+ * Decode a mapcode to a Rectangle, which defines the valid zone for a mapcode. The boundaries of the
+ * mapcode zone are inclusive for the South and West borders and exclusive for the North and East borders.
+ * This is essentially the same call as a 'decode', except it returns a rectangle, rather than its center point.
+ *
+ * @param mapcode Mapcode.
+ * @param defaultTerritoryContext Default territory context for disambiguation purposes. May be null.
+ * @return Rectangle Mapcode zone. South/West borders are inclusive, North/East borders exclusive.
+ * @throws UnknownMapcodeException Thrown if the mapcode has the correct syntax,
+ * but cannot be decoded into a point.
+ * @throws UnknownPrecisionFormatException Thrown if the precision format is incorrect.
+ * @throws IllegalArgumentException Thrown if arguments are null, or if the syntax of the mapcode is incorrect.
+ */
+ @Nonnull
+ public static Rectangle decodeToRectangle(@Nonnull final String mapcode, @Nullable final Territory defaultTerritoryContext)
+ throws UnknownMapcodeException, IllegalArgumentException, UnknownPrecisionFormatException {
+ checkNonnull("mapcode", mapcode);
+ final MapcodeZone mapcodeZone = decodeToMapcodeZone(mapcode, defaultTerritoryContext);
+ final Point southWest = Point.fromLatLonFractions(mapcodeZone.getLatFractionMin(), mapcodeZone.getLonFractionMin());
+ final Point northEast = Point.fromLatLonFractions(mapcodeZone.getLatFractionMax(), mapcodeZone.getLonFractionMax());
+ final Rectangle rectangle = new Rectangle(southWest, northEast);
+ assert rectangle.isDefined();
+ return rectangle;
}
+
+ // TODO: Explain when this method is needed/used.
/**
* Is coordinate near multiple territory borders?
*
@@ -274,7 +285,7 @@ public static Point decode(
public static boolean isNearMultipleBorders(@Nonnull final Point point, @Nonnull final Territory territory) {
checkDefined("point", point);
if (territory != Territory.AAA) {
- final int ccode = territory.getNumber();
+ final int territoryNumber = territory.getNumber();
if (territory.getParentTerritory() != null) {
// There is a parent! check its borders as well...
if (isNearMultipleBorders(point, territory.getParentTerritory())) {
@@ -282,11 +293,11 @@ public static boolean isNearMultipleBorders(@Nonnull final Point point, @Nonnull
}
}
int nrFound = 0;
- final int fromTerritoryRecord = dataModel.getDataFirstRecord(ccode);
- final int uptoTerritoryRecord = dataModel.getDataLastRecord(ccode);
+ final int fromTerritoryRecord = DATA_MODEL.getDataFirstRecord(territoryNumber);
+ final int uptoTerritoryRecord = DATA_MODEL.getDataLastRecord(territoryNumber);
for (int territoryRecord = uptoTerritoryRecord; territoryRecord >= fromTerritoryRecord; territoryRecord--) {
if (!Data.isRestricted(territoryRecord)) {
- final Boundary boundary = Boundary.createFromTerritoryRecord(territoryRecord);
+ final Boundary boundary = Boundary.createBoundaryForTerritoryRecord(territoryRecord);
final int xdiv8 = Common.xDivider(boundary.getLatMicroDegMin(), boundary.getLatMicroDegMax()) / 4;
if (boundary.extendBoundary(60, xdiv8).containsPoint(point)) {
if (!boundary.extendBoundary(-60, -xdiv8).containsPoint(point)) {
@@ -301,4 +312,36 @@ public static boolean isNearMultipleBorders(@Nonnull final Point point, @Nonnull
}
return false;
}
+
+ @Nonnull
+ private static MapcodeZone decodeToMapcodeZone(@Nonnull final String mapcode, @Nullable final Territory defaultTerritoryContext)
+ throws UnknownMapcodeException, IllegalArgumentException, UnknownPrecisionFormatException {
+ checkNonnull("mapcode", mapcode);
+ String mapcodeClean = Mapcode.convertStringToPlainAscii(mapcode.trim()).toUpperCase();
+
+ // Determine territory from mapcode.
+ final Territory territory;
+ final Matcher matcherTerritory = Mapcode.PATTERN_TERRITORY.matcher(mapcodeClean);
+ if (!matcherTerritory.find()) {
+
+ // No territory code was supplied in the string, use specified territory context parameter.
+ territory = (defaultTerritoryContext != null) ? defaultTerritoryContext : Territory.AAA;
+ } else {
+
+ // Use the territory code from the string.
+ final String territoryName = mapcodeClean.substring(matcherTerritory.start(), matcherTerritory.end()).trim();
+ try {
+ territory = Territory.fromString(territoryName);
+ } catch (final UnknownTerritoryException ignored) {
+ throw new UnknownMapcodeException("Wrong territory code: " + territoryName);
+ }
+
+ // Cut off the territory part.
+ mapcodeClean = mapcodeClean.substring(matcherTerritory.end()).trim();
+ }
+
+ // Throws an exception if the format is incorrect.
+ getPrecisionFormat(mapcodeClean);
+ return Decoder.decodeToMapcodeZone(mapcodeClean, territory);
+ }
}
diff --git a/src/main/java/com/mapcode/MapcodeZone.java b/src/main/java/com/mapcode/MapcodeZone.java
index cf13ed1..208da94 100644
--- a/src/main/java/com/mapcode/MapcodeZone.java
+++ b/src/main/java/com/mapcode/MapcodeZone.java
@@ -22,10 +22,12 @@
* ----------------------------------------------------------------------------------------------
* Package private implementation class. For internal use within the Mapcode implementation only.
* ----------------------------------------------------------------------------------------------
+ *
* Simple class to represent all the coordinates that would deliver a particular mapcode.
*/
class MapcodeZone {
+ // TODO: Explain why you need these fractions and how they work exactly.
// Longitudes in LonFractions ("1/3240 billionths").
private double lonFractionMin;
private double lonFractionMax;
@@ -34,35 +36,20 @@ class MapcodeZone {
private double latFractionMin;
private double latFractionMax;
- // Construct an (empty) zone.
- MapcodeZone() {
- setEmpty();
- }
-
- // Construct an (empty) zone.
- void setEmpty() {
- latFractionMin = 0;
- latFractionMax = 0;
- lonFractionMin = 0;
- lonFractionMax = 0;
- }
-
- // Construct a copy of an existing zone.
- MapcodeZone(@Nonnull final MapcodeZone zone) {
- copyFrom(zone);
+ MapcodeZone(final double latFractionMin, final double latFractionMax,
+ final double lonFractionMin, final double lonFractionMax) {
+ this.latFractionMin = latFractionMin;
+ this.latFractionMax = latFractionMax;
+ this.lonFractionMin = lonFractionMin;
+ this.lonFractionMax = lonFractionMax;
}
- // construct a copy of an existing zone
- void copyFrom(@Nonnull final MapcodeZone other) {
- latFractionMin = other.latFractionMin;
- latFractionMax = other.latFractionMax;
- lonFractionMin = other.lonFractionMin;
- lonFractionMax = other.lonFractionMax;
+ MapcodeZone(@Nonnull final MapcodeZone mapcodeZone) {
+ this(mapcodeZone.latFractionMin, mapcodeZone.latFractionMax, mapcodeZone.lonFractionMin, mapcodeZone.lonFractionMax);
}
- @Nonnull
- static MapcodeZone empty() {
- return new MapcodeZone();
+ MapcodeZone() {
+ this(0.0, 0.0, 0.0, 0.0);
}
double getLonFractionMin() {
@@ -97,10 +84,10 @@ void setLatFractionMax(final double latFractionMax) {
this.latFractionMax = latFractionMax;
}
+ // TODO: Use of this method is unclear.
// Generate upper and lower limits based on x and y, and delta's.
- void setFromFractions(
- final double latFraction, final double lonFraction,
- final double latFractionDelta, final double lonFractionDelta) {
+ void setFromFractions(final double latFraction, final double lonFraction,
+ final double latFractionDelta, final double lonFractionDelta) {
assert (lonFractionDelta >= 0.0);
assert (latFractionDelta != 0.0);
lonFractionMin = lonFraction;
@@ -118,55 +105,58 @@ boolean isEmpty() {
return ((lonFractionMax <= lonFractionMin) || (latFractionMax <= latFractionMin));
}
+ // TODO: Explain what this is (geo point of mapcode).
@Nonnull
- Point getMidPoint() {
+ Point getCenter() {
if (isEmpty()) {
return Point.undefined();
} else {
- final double latFrac = Math.floor((latFractionMin + latFractionMax) / 2);
- final double lonFrac = Math.floor((lonFractionMin + lonFractionMax) / 2);
+ final double latFrac = Math.floor((latFractionMin + latFractionMax) / 2.0);
+ final double lonFrac = Math.floor((lonFractionMin + lonFractionMax) / 2.0);
return Point.fromLatLonFractions(latFrac, lonFrac);
}
}
+ // TODO: Explain when this is used. It clips a zone to an encompassing boundary.
// Returns a non-empty intersection of a mapcode zone and a territory area.
// Returns null if no such intersection exists.
@Nonnull
MapcodeZone restrictZoneTo(@Nonnull final Boundary area) {
- MapcodeZone z = new MapcodeZone(this);
+ final MapcodeZone mapcodeZone = new MapcodeZone(latFractionMin, latFractionMax, lonFractionMin, lonFractionMax);
final double latMin = area.getLatMicroDegMin() * Point.LAT_MICRODEG_TO_FRACTIONS_FACTOR;
- if (z.latFractionMin < latMin) {
- z.latFractionMin = latMin;
+ if (mapcodeZone.latFractionMin < latMin) {
+ mapcodeZone.latFractionMin = latMin;
}
final double latMax = area.getLatMicroDegMax() * Point.LAT_MICRODEG_TO_FRACTIONS_FACTOR;
- if (z.latFractionMax > latMax) {
- z.latFractionMax = latMax;
+ if (mapcodeZone.latFractionMax > latMax) {
+ mapcodeZone.latFractionMax = latMax;
}
- if (z.latFractionMin < z.latFractionMax) {
+ if (mapcodeZone.latFractionMin < mapcodeZone.latFractionMax) {
double lonMin = area.getLonMicroDegMin() * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR;
double lonMax = area.getLonMicroDegMax() * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR;
- if ((lonMax < 0) && (z.lonFractionMin > 0)) {
+ if ((lonMax < 0) && (mapcodeZone.lonFractionMin > 0)) {
lonMin += (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
lonMax += (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
- } else if ((lonMin > 1) && (z.lonFractionMax < 0)) {
+ } else if ((lonMin > 1) && (mapcodeZone.lonFractionMax < 0)) {
lonMin -= (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
lonMax -= (Point.MICRO_DEG_360 * Point.LON_MICRODEG_TO_FRACTIONS_FACTOR);
}
- if (z.lonFractionMin < lonMin) {
- z.lonFractionMin = lonMin;
+ if (mapcodeZone.lonFractionMin < lonMin) {
+ mapcodeZone.lonFractionMin = lonMin;
}
- if (z.lonFractionMax > lonMax) {
- z.lonFractionMax = lonMax;
+ if (mapcodeZone.lonFractionMax > lonMax) {
+ mapcodeZone.lonFractionMax = lonMax;
}
}
- return z;
+ return mapcodeZone;
}
@Nonnull
@Override
public String toString() {
- return isEmpty() ? "empty" :
- ("[" + (latFractionMin / Point.LAT_TO_FRACTIONS_FACTOR) + ", " + (latFractionMax / Point.LAT_TO_FRACTIONS_FACTOR) +
- "), [" + (lonFractionMin / Point.LON_TO_FRACTIONS_FACTOR) + ", " + (lonFractionMax / Point.LON_TO_FRACTIONS_FACTOR) + ')');
+ return isEmpty() ? "empty" : ("[" + (latFractionMin / Point.LAT_TO_FRACTIONS_FACTOR) + ", " +
+ (latFractionMax / Point.LAT_TO_FRACTIONS_FACTOR) +
+ "), [" + (lonFractionMin / Point.LON_TO_FRACTIONS_FACTOR) + ", " +
+ (lonFractionMax / Point.LON_TO_FRACTIONS_FACTOR) + ')');
}
}
diff --git a/src/main/java/com/mapcode/Point.java b/src/main/java/com/mapcode/Point.java
index 7de9ccb..2d6e408 100644
--- a/src/main/java/com/mapcode/Point.java
+++ b/src/main/java/com/mapcode/Point.java
@@ -220,7 +220,7 @@ public String toString() {
@SuppressWarnings("NonFinalFieldReferencedInHashCode")
@Override
public int hashCode() {
- return Arrays.hashCode(new Object[]{getLatDeg(), getLonDeg(), defined});
+ return Arrays.hashCode(new Object[]{latMicroDeg, lonMicroDeg, latFractionOnlyDeg, lonFractionOnlyDeg, defined});
}
@SuppressWarnings("NonFinalFieldReferenceInEquals")
diff --git a/src/main/java/com/mapcode/Rectangle.java b/src/main/java/com/mapcode/Rectangle.java
new file mode 100644
index 0000000..426a89a
--- /dev/null
+++ b/src/main/java/com/mapcode/Rectangle.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016-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 java.util.Arrays;
+
+/**
+ * This class defines a geospatial rectangle, defined by a South/West and a North/East
+ * point. The rectangle is undefined if either of the corner points are undefined.
+ */
+public class Rectangle {
+
+ private final Point southWest;
+ private final Point northEast;
+
+ Rectangle(@Nonnull final Point southWest, @Nonnull final Point northEast) {
+ this.southWest = southWest;
+ this.northEast = northEast;
+ }
+
+ @Nonnull
+ public Point getSouthWest() {
+ return southWest;
+ }
+
+ @Nonnull
+ public Point getNorthEast() {
+ return northEast;
+ }
+
+ @Nonnull
+ public Point getCenter() {
+ if (!isDefined()) {
+ return Point.undefined();
+ }
+ final double centerLat = ((southWest.getLatDeg() + northEast.getLatDeg()) / 2.0);
+ final double centerLon = ((southWest.getLonDeg() + northEast.getLonDeg()) / 2.0);
+ return Point.fromDeg(centerLat, centerLon);
+ }
+
+ boolean isDefined() {
+ return southWest.isDefined() && northEast.isDefined();
+ }
+
+ @Nonnull
+ @Override
+ public String toString() {
+ return "[" + southWest + ", " + northEast + ']';
+ }
+
+ @SuppressWarnings("NonFinalFieldReferencedInHashCode")
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[]{southWest, northEast});
+ }
+
+ @SuppressWarnings("NonFinalFieldReferenceInEquals")
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Rectangle)) {
+ return false;
+ }
+ final Rectangle that = (Rectangle) obj;
+ return (this.southWest.equals(that.southWest) &&
+ this.northEast.equals(that.northEast));
+ }
+}
diff --git a/src/main/java/com/mapcode/Territory.java b/src/main/java/com/mapcode/Territory.java
index 043a06e..d7c23f1 100644
--- a/src/main/java/com/mapcode/Territory.java
+++ b/src/main/java/com/mapcode/Territory.java
@@ -18,14 +18,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import static com.mapcode.CheckArgs.checkNonnull;
@@ -702,9 +695,8 @@ public static Territory fromString(@Nonnull final String alphaCode) throws Unkno
* @throws UnknownTerritoryException Thrown if the territory is not found given the parentTerritory.
*/
@Nonnull
- public static Territory fromString(
- @Nonnull final String alphaCode,
- @Nonnull final Territory parentTerritory) throws UnknownTerritoryException {
+ public static Territory fromString(@Nonnull final String alphaCode,
+ @Nonnull final Territory parentTerritory) throws UnknownTerritoryException {
checkNonnull("alphaCode", alphaCode);
checkNonnull("parentTerritory", parentTerritory);
if (!PARENT_TERRITORIES.contains(parentTerritory)) {
@@ -803,43 +795,38 @@ public boolean hasSubdivisions() {
/**
* Private constructors to create a territory code.
*/
- private Territory(
- final int number,
- @Nonnull final String fullName) {
+ private Territory(final int number,
+ @Nonnull final String fullName) {
this(number, fullName, null, null, null, null);
}
- private Territory(
- final int number,
- @Nonnull final String fullName,
- @Nullable final Alphabet[] alphabets) {
+ private Territory(final int number,
+ @Nonnull final String fullName,
+ @Nullable final Alphabet[] alphabets) {
this(number, fullName, alphabets, null, null, null);
}
- private Territory(
- final int number,
- @Nonnull final String fullName,
- @Nullable final Alphabet[] alphabets,
- @Nullable final Territory parentTerritory) {
+ private Territory(final int number,
+ @Nonnull final String fullName,
+ @Nullable final Alphabet[] alphabets,
+ @Nullable final Territory parentTerritory) {
this(number, fullName, alphabets, parentTerritory, null, null);
}
- private Territory(
- final int number,
- @Nonnull final String fullName,
- @Nullable final Alphabet[] alphabets,
- @Nullable final Territory parentTerritory,
- @Nullable final String[] aliases) {
+ private Territory(final int number,
+ @Nonnull final String fullName,
+ @Nullable final Alphabet[] alphabets,
+ @Nullable final Territory parentTerritory,
+ @Nullable final String[] aliases) {
this(number, fullName, alphabets, parentTerritory, aliases, null);
}
- private Territory(
- final int number,
- @Nonnull final String fullName,
- @Nullable final Alphabet[] alphabets,
- @Nullable final Territory parentTerritory,
- @Nullable final String[] aliases,
- @Nullable final String[] fullNameAliases) {
+ private Territory(final int number,
+ @Nonnull final String fullName,
+ @Nullable final Alphabet[] alphabets,
+ @Nullable final Territory parentTerritory,
+ @Nullable final String[] aliases,
+ @Nullable final String[] fullNameAliases) {
assert number >= 0;
this.number = number;
this.fullName = fullName;
@@ -866,7 +853,7 @@ private Territory(
parentList = new ArrayList();
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
- final Set territoryCodes = new HashSet();
+ final Set territoryNumbers = new HashSet();
final Set namesSet = new HashSet();
for (final Territory territory : Territory.values()) {
@@ -879,10 +866,10 @@ private Territory(
}
// Check if territory code was already used.
- if (territoryCodes.contains(territoryNumber)) {
+ if (territoryNumbers.contains(territoryNumber)) {
throw new ExceptionInInitializerError(errorPrefix + "non-unique territory number: " + territoryNumber);
}
- territoryCodes.add(territory.getNumber());
+ territoryNumbers.add(territory.getNumber());
final int initialCodeListSize = codeList.size();
for (int i = initialCodeListSize; i <= territory.number; i++) {
@@ -895,7 +882,7 @@ private Territory(
// Check if territory name is unique.
if (namesSet.contains(territory.toString())) {
- throw new ExceptionInInitializerError(errorPrefix + "non-unique territory name: " + territory.toString());
+ throw new ExceptionInInitializerError(errorPrefix + "non-unique territory name: " + territory);
}
namesSet.add(territory.toString());
addNameWithParentVariants(territory.toString(), territory);
@@ -927,7 +914,7 @@ private Territory(
max = Math.max(max, territory.number);
assert territory.alphabets.length > 0;
}
- assert territoryCodes.size() == Territory.values().length;
+ assert territoryNumbers.size() == Territory.values().length;
// Check that territory has at least one alphabet.
@@ -946,9 +933,8 @@ private Territory(
* @throws UnknownTerritoryException Thrown if the territory is not found.
*/
@Nonnull
- private static Territory createFromString(
- @Nonnull final String alphaCode,
- @Nullable final Territory parentTerritory) throws UnknownTerritoryException {
+ private static Territory createFromString(@Nonnull final String alphaCode,
+ @Nullable final Territory parentTerritory) throws UnknownTerritoryException {
// Replace '_' with '-', but leave spaces alone (may be part of the name).
final String trimmed = Mapcode.convertStringToPlainAscii(
@@ -992,9 +978,8 @@ private static Territory createFromString(
* @param name Name to add.
* @param territory Territory.
*/
- private static void addNameWithParentVariants(
- @Nonnull final String name,
- @Nonnull final Territory territory) {
+ private static void addNameWithParentVariants(@Nonnull final String name,
+ @Nonnull final Territory territory) {
// Add the name as provided
addNameWithSeperatorVariants(name, territory);
diff --git a/src/site/markdown/ReleaseNotes.md b/src/site/markdown/ReleaseNotes.md
deleted file mode 100755
index 7b79c57..0000000
--- a/src/site/markdown/ReleaseNotes.md
+++ /dev/null
@@ -1,280 +0,0 @@
-# Release Notes
-
-These are the release notes for the Java library for mapcodes.
-
-### 2.4.3
-
-* Updated Maven dependencies for latest patches.
-
-### 2.4.2
-
-* Removed secret Coveralls key from POM file.
-
-### 2.4.1
-
-* Added scripts for Tifinagh (Berber), Tamil, Amharic, Telugu, Odia, Kannada, Gujarati.
-
-* Added `getAlphabets()` to `Territory` class, returning the most commonly used languages for the territory.
-
-* Renamed constant `HINDI` to `DEVANAGIRI`.
-
-* Improved some characters for Arabic and Devanagari.
-
-* Fixed Bengali to also support Assamese.
-
-### 2.4.0
-
-* Added scripts for Korean (Choson'gul/Hangul), Burmese, Khmer, Sinhalese, Thaana (Maldivan), Chinese (Zhuyin, Bopomofo).
-
-* Renamed constant `MALAY` to `MALAYALAM`.
-
-### 2.3.1
-
-* Fixed data for some parts of China.
-
-### 2.3.0
-
-* Added Arabic support.
-
-* Fixed Greek, Hebrew and Hindi support.
-
-### 2.2.5
-
-* Updated documentation.
-
-* Cleaned up POM, sorted dependencies.
-
-### 2.2.4
-
-* Added Travis CI and Coveralls badges to `README.md`.
-
-* Replaces static `DataAccess` class with singleton `DataModel` to allow testing
-of incorrect data model files.
-
-* Fixed error handling for incorrect data model files.
-
-* Fix error to info logging in `aeuUnpack`.
-
-* Updated all POM dependencies.
-
-* Updated copyright messages.
-
-* Improved test coverage of unit tests.
-
-### 2.2.3
-
-* Issue #23: Fixed `Territory.fromString` to make sure the parent territory is valid for
- input like "CHE-GR". This returned "MX-GRO" instead of throwing `UnknownTerritoryException`.
- Added unit test for this type of case.
-
-* Fixed minor JavaDoc issues.
-
-### 2.2.2
-
-* Fixed error in `Point` which in rare cases would allow longitudes outside proper range.
-
-### 2.2.1
-
-* Fixed unit test. Reduced size of files for unit tests considerably. Improved unit test speed.
-
-* Fixed `Point` interface.
-
-* Cleaned up `Boundary` and `DataAccess`.
-
-### 2.2.0
-
-* Solved 1-microdegree gap in a few spots on Earth, noticable now extreme precision is possible.
-
-* Replaced floating point by fixed point math.
-
-* Improved speed.
-
-* Enforce `Mencode(decode(M)) == M`, except at territory border corners.
-
-* Cleaned up source; moved hard-coded data into `mminfo.dat`.
-
-### 2.1.0
-
-* Added micro-meter precision (mapcodes can now have eight precision digits).
-
-* Assure that encode(decode(m)) delivers m.
-
-* Renames to bring source more in line with other implementations.
-
-### 2.0.2
-
-* Renamed `isValidPrecisionFormat` to `isValidMapcodeFormat`.
-
-* Removed public microdegree references from `Point` class. Everything is degrees now.
-
-* Removed `ParentTerritory` class.
-
-### 2.0.1
-
-* Reverted Java JDK level to 1.6 (Java 6) from 1.8 (Java 8), so the library can be used on
- Android platforms operating at Java 6 as well.
-
-* Use multi-threading for long running test to speed them up (uses all CPU cores now).
-
-* Added the ability to use a country name for `Territory.fromString()`.
-
-### 2.0.0
-
-* Fixes to the data rectangles (primarily intended for ISO proposal).
-
-* Removed functionality to use numeric territory codes; only alpha codes are accepted.
-
-* Note that this release only allows high-precision mapcodes up to 2 additional suffix characters.
-A future release will be scheduled to allow up to 8 suffix characters (nanometer accuracy).
-
-### 1.50.3
-
-* This release breaks compatiblity with earlier releases, to clean up the interface significantly.
-
-* Removed `Mapcode.encodeToShortest(lat, lon))` as this will produce a randomly chosen territory.
- You must specify a `restrictToTerritory` now.
-
-* Renamed `Territory.code` to `Territory.number`.
-
-* Renamed `fromTerritoryCode())` to `fromNumber())`.
-
-* Renamed `Territory.isState())` to `Territory.isSubdivision())` and
-
-* Renamed `Territory.hasStates())` to `Territory.hasSubdivision())`.
-
-* Renamed `Alphabet.code` to `Alphabet.number`.
-
-* Renamed `fromCode())` to `fromNumber())`.
-
-* Renamed `MapcodeFormat` to `PrecisionFormat`.
-
-* Deprecated methods have been removed.
-
-### 1.50.2
-
-* Cleaned up Unicode handling a bit.
-
-* Speed up of reading initialization data.
-
-* Rename `toNameFormat` into `toAlphaFormat` and `NAME_FORMAT` to `ALPHA_FORMAT`.
-
-### 1.50.1
-
-* Bugfix for mapcodes in IN-DD (in India).
-
-### 1.50
-
-* Major release. This version is not backwards compatible with mapcode 1.4x: is has dropped support for
- Antartica AT0-8 codes and has a changed (improved) way of dealing with the Greek alphabet.
-
-* Added 22-chararcter post-processing of all-digit mapcodes for the Greek alphabet.
-
-* Retired legacy aliases EAZ and SKM, AU-QL, AU-TS, AU-NI and AU-JB.
-
-* Retired legacy Antarctica claims AT0 through AT8.
-
-* Added convencience methods for `MapcodeCodec` to accept `Point` for all encode functions
- as well (not just `latDeg`, `lonDeg`).
-
-* Added alphabet support to convert mapcodes (both codes and territories) between `Alphabet`s.
-
-* Exceptions have been corrected and documented in code.
-
-* Allowed nullable values in `MapcodeCodec` encode and decode methods to assume reasonable defaults.
-
-* Microdegrees are no longer support publicly in `Point`. Only degrees.
-
-* Latitudes are limited to -90..90 and longitudes are wrapped to -180..180 (non inclusive).
-
-### 1.42.3
-
-* To be done.
-
-### 1.42.2
-
-* Upper- and lowercase mapcodes always allowed.
-
-### 1.42.1
-
-* Cleaned up source. Removed all pending IntelliJ IDEA inspection warnings and reformatted code
- using default IDEA code style to maintain consistent layout.
-
-* Add additional unit tests to check for correct handling of international mapcode handling.
-
-* Added safe constants for the maximum delta distance in meters for mapcode accuracy.
-
-### 1.42
-
-* Fixed a bug in `MapcodeCodec.encodeToShortest` which would not always return the shortest code (see
- next bullet). Reproducible with `curl -X GET http://localhost:8080/mapcode/to/47.1243/-111.28564/local`.
-
-* Fixed a bug where `Encoder.encode` would sometime retrieve more than one result even if result set
- was limited to 1 result.
-
-### 1.41.1
-
-* Added convenience method to Mapcode.
-
-### 1.41
-
-* Added the India state Telangana (IN-TG), until 2014 a region in Adhra Pradesh.
-
-* Updated POM dependencies to latest library versions of standard components.
-
-### 1.40.3
-
-* Minor code clean-up with no functional effect.
-
-* (Issue #6) Removed non-project specific unwanted files out of `.gitignore`. These should be listed in the
-developer's own global `~/.gitignore` file instead.
-
-### 1.40.2
-
-* Added `getMapcodeFormatType` and `isValidMapcodeFormat` to check validity of mapcode strings. Added
-unit tests for these methods as well.
-
-* Constructor of `Mapcode` now checks for validity of mapcode string.
-
-* Added Unicode handling of high precision mapcodes and added check to throw an `IllegalArgumentException`
-if the character 'Z' or equivalent Unicode character is contained in the high precision part according to
-the Mapcode documenation.
-
-* Added method `convertToAscii` which produces the ASCII, non-Unicode variant of a mapcode which contains
-Unicode characters§.
-
-### 1.40.1
-
-* Deprecated names `getMapcodeHighPrecision` and `getMapcodeMediumPrecision`.
- Replaced those with `getMapcodePrecision1` and `getMapcodePrecision2`.
-
-* Fixed all occurences of incorrectly cased Mapcode vs. mapcode.
-
-### 1.40
-
-* Renamed class `Mapcode` to `MapcodeCodec`.
-
-* Renamed class `MapcodeInfo` to `Mapcode`.
-
-* Added high precision Mapcodes, with methods `getMapcodeHighPrecision`
-
-* Seriously reduced test set size.
-
-* Replaced Unicode characters in source code to escapes.
-
-* Added explicit character encoding to `pom.xml`.
-
-* Fixed issues with decoder at some boundaries.
-
-### 1.33.2
-
-* Clean-up of release 1.33.2.
-
-* Added release notes.
-
-* Removed GSON dependency from production (now scope 'test' only).
-
-* Added robustness with respect to Unicode characters.
-
-### 1.33.1
-
-* First release of Java library for MapCodes. Includes extensive test suite.
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
deleted file mode 100644
index 0305f22..0000000
--- a/src/site/markdown/index.md
+++ /dev/null
@@ -1,216 +0,0 @@
-# Mapcode Library for Java
-
-Welcome to the Java library to handle mapcodes. The original C library was created by Pieter Geelen.
-The initial port to Java and considerable speed-ups were done by Matthew Lowden.
-Rijn Buve has further developed and maintained the Java version of the Mapcode library since.
-
-## Release Notes
-
-You can find the release notes for this library at: **[Release Notes](./ReleaseNotes.html)**.
-
-## Code Style Settings for IntelliJ IDEA
-
-The Java code uses the *default* [JetBrains IntelliJ IDEA](https://www.jetbrains.com/idea)
-code style settings for Java, with one exception:
-code blocks are always surround by `{...}` and on separate lines.
-
-### How To Build The Mapcode Library JAR File
-
-The sources for the Mapcode Library for Java contain everything to build the Mapcode JAR file as well
-as a significant number of unit tests to verify the correctness of the implementation against the
-reference C implementation.
-
-The library requires a minimum Java language revision level 6, but has been tested and verified to work
-with JDK 1.6, JDK 1.7 and JDK 1.8.
-
-To build the library:
-
- cd
- mvn clean install
-
-This produces a JAR file in your local Maven repository at `~/.m2/repository/com/mapcode/mapcode//`
-You can include this JAR in your project, or store it in your local Nexus repository, for example.
-
-If you create a Maven project, you can see how to include the JAR in your project in the menu
-item `Dependency Information` in the menu on this page.
-
-The latest official version of the libray on Maven Central can be found [**here**](http://search.maven.org/#search%7Cga%7C1%7Cmapcode).
-
-## How To Use This Library In Your Application
-
-There are two classes you interact with as a client of the Mapcode Library. These are:
-
- MapcodeCodec.java
- Mapcode.java
- Point.java
-
-
-### Class `MapcodeCodec.java`
-
-This class contains the **encoder** and **decoder** for mapcodes. The class exposes two methods (with some
-variants): `encode` and `decode`.
-
-The **encoder** encodes a (latitude, longitude) pair into a result set of mapcodes. A single (latitude,
-longitude) pair may produce multiple mapcodes, some of which are known as **local** mapcodes
-(which are only unique within a given territory) and one which is globally unique in the entire world.
-
-The **decoder** decodes a local or world-wide mapcode string into a (latitude, longitude) pair. For
-local mapcodes a territory may be specified which is used to disambiguate the mapcode resolution.
-
-Note that encoding a (latitude, longitude) pair to a mapcode and then decoding it may result in a
-slightly offset position, as the mapcodes have a limited precision. The library offers "high-precision"
-mapcodes as well, but you van never assume the resulting latitudes and longitudes to exactly match the
-original input.
-
-**`List encode(double latitude, double longitude)`** encodes a (latitude, longitude) pair.
-
-Example:
-
- double lat = 52.376514;
- double lon = 4.908542;
- List**Mapcode** results = MapcodeCodec.encode(lat, lon);
- // Returns a non-empty list of results.
-
-This produces a non-empty list of resulting mapcodes. The shortest (potentially local) mapcodes is always
-the first mapcodes in the list. The last mapcode in the list is always the
-globally unique international mapcode.
-
-**`List encode(double latitude, double longitude, Territory territory)`** encodes a (latitude,
-longitude) pair, where encoding is restricted to a specific territory.
-
-Example:
-
- List**Mapcode** results = MapcodeCodec.encode(lat, lon, Territory.NLD);
- // Returns an empty list of results if the location is not within territory NLD.
-
-This resticts encoding to a specific territory and produces a potentially empty list of results.
-Again, if encoding succeeded, the first mapcode is the shortest one and the last mapcode in the list is the
-globally unique international mapcode.
-
-Both `encode()` methods are also offered as a `encodeToShortest()` method, which essentially
-returns only the first result of the previous methods (if there are any results).
-
- Mapcode result = MapcodeCodec.encodeToShortest(lat, lon);
- // Always returns a mapcode (or valid lat and lon values).
-
- try {
- Mapcode result = MapcodeCodec.encodeToShortest(lat, lon, Territory.NLD);
- // This may fail.
- }
- catch (UnknownMapcodeException e) {
- // If the location is not within the territory, this exception is thrown.
- }
-
-**`Point decode(String mapcode)`** decodes a mapcode to a `Point` which contains a location. Example:
-
- Point p = MapcodeCodec.decode("NLD 49.4V");
-
-**`Point decode(String mapcode, Territory territory)`** decodes a mapcode to a `Point` which contains a
-location, where the mapcode must be located within a specific territory.
-
-Examples of usage:
-
- Point p = MapcodeCodec.decode("49.4V", Territory.NLD);
-
-
-## Class `Mapcode.java`
-
-This class represents mapcodes, which consist of a string of characters, digits and a decimal point and
-a territory specification. The territory specification is required for national (local) mapcodes, which
-are not globally unique.
-
-The class also exposes methods to convert mapcodes to proper mapcode strings, usable for printing and
-it allows string-formatted mapcodes to be converted to `Mapcode` objects, where territory information
-is properly parsed and converted to a `Territory` enumeration value.
-
-**`String getCode()`** returns the mapcode string which does not include territory information. You can also
-use `getCode(1)` and `getCode(2)` for more precision, but longer mapcodes.
-
-The default precision offered by `getCode()` is approximately 10m (maximum distance to latitude,
-longitude the mapcode decodes to). This corresponds to an area of 20m x 20m (400m2). These mapcodes include
-no additional precision digits.
-
-The precision offered by `getCode(1)` is approximately 2m.
-This corresponds to an area of 4m x 4m (16m2). These mapcodes include 1 additional precision digit.
-
-The precision offered by `getCode(2)` is approximately 0.40m. This corresponds to an area
-of 0.80m x 0.80m (0.64m2). These mapcodes include 2 additional precision digits.
-
-This goes up to `getCode(8)`, which provides nanometer accuracy. (Please note one of the main advantages
-of mapcodes over WGS84 coordinates is their simplicity and short size, so try to use as little precision
-as required for your application...)
-
-**`Territory getTerritory()`** returns the territory information.
-
-**`toString()`** and **`getCodeWithTerritory()`** return mapcodes string with territory information,
-specified as a ISO code.
-
-
-## Class `Point.java`
-
-This class represents (latitude, longitude) locations. It offers methods to create locations using
-degrees.
-
-**`Point fromDeg(double latitude, double longitude)`** returns a `Point` for a given (latitude, longitude)
-pair. Note that latitudes are always between **-90** and **90**, and longitudes are
-always between **-180** and **180** (non-inclusive) whenreturned.
-However, values outside these range are correctly limited (latitude) or wrapped (longitude) to these ranges
-when supplied to the class.
-
-The methods **`double getLat()`** and **`getLon()`** return the latitude and longitude respectively, in degrees.
-
-
-# What Is A Mapcode?
-
-A mapcode represents a location. Every location on Earth can be represented by a mapcode. Mapcodes
-were designed to be short, easy to recognise, remember and communicate. They are precise to a few
-meters, which is good enough for every-day use.
-
-## Mapcodes Are Free!
-
-Mapcodes are free. They can be used by anyone, and may be supported, provided or generated by anyone,
-as long as this is done free of charge, conditions or restrictions. Technical details and sources are
-available on our developers page.
-
-## what Does A Mapcode Look Like?
-
-A mapcode consists of two groups of letters and digits, separated by a dot. An example of a mapcode is
-
- 49.4V
-
-This is sufficient as long as it is clear what country or state the mapcode belongs in. On a business card,
-it is therefore a good idea to put it after the country or state name:
-
- John Smith
- Oosterdoksstraat 114
- Amsterdam
- Netherlands 49.4V
-
-When storing mapcodes in a database, it is recommended to explicitly specify the country:
-
- Netherlands 49.4V
-
-or via the standard 3-letter abbreviation:
-
- NLD 49.4V
-
-In eight very large countries (The USA, Canada, Mexico, Brazil, India, Australia, Russia, and China),
-an address has little meaning without knowing the state (just like elsewhere, an address has little meaning
-without knowing the country). For example, there are 27 cities called Washington in the USA. If you want to
-refer to a location in the capital city, you would always refer to "Washington DC".
-
- DC 18.JQZ
-
-or (in an international database):
-
- US-DC 18.JQZ
-
-More information on mapcodes and their underlying concepts can be found in our reference material.
-
-## Where Did Mapcodes Come From?
-
-Mapcodes were developed in 2001 by Pieter Geelen and Harold Goddijn, soon after the GPS satellite signals
-were opened up for civilian use. It was decided to donate the mapcode system to the public domain in 2008.
-The algorithms and data tables are maintained by the Stichting Mapcode Foundation.
-
-The mapcode system is being filed as a standard at the International Organisation for Standardisation.
diff --git a/src/site/site.xml b/src/site/site.xml
deleted file mode 100644
index 4d9b53c..0000000
--- a/src/site/site.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
- org.apache.maven.skins
- maven-default-skin
- 1.0
-
-
-
-
-
- ${reports}
-
-
diff --git a/src/test/java/com/mapcode/DecoderTest.java b/src/test/java/com/mapcode/DecoderTest.java
index 031ae21..e8ad7d8 100644
--- a/src/test/java/com/mapcode/DecoderTest.java
+++ b/src/test/java/com/mapcode/DecoderTest.java
@@ -26,6 +26,64 @@
public class DecoderTest {
private static final Logger LOG = LoggerFactory.getLogger(DecoderTest.class);
+ @SuppressWarnings("JUnitTestMethodWithNoAssertions")
+ @Test
+ public void getInternationalGrid() {
+ final DataModel dataModel = DataModel.getInstance();
+ final int world = Territory.AAA.getNumber();
+ final int to = dataModel.getDataLastRecord(world);
+ final int from = dataModel.getDataFirstRecord(world);
+ for (int index = from; index <= to; index++) {
+ final Boundary boundary = Boundary.createBoundaryForTerritoryRecord(index);
+ LOG.info("{}: ({}, {}), ({}, {})", (index - from) + 1,
+ boundary.getLatMicroDegMin() / 1.0e6,
+ boundary.getLonMicroDegMin() / 1.0e6,
+ boundary.getLatMicroDegMax() / 1.0e6,
+ boundary.getLonMicroDegMax() / 1.0e6
+ );
+ }
+ }
+
+ @Test
+ public void decodeToRectangle() throws Exception {
+ LOG.info("decodeToRectangle");
+ Point center = MapcodeCodec.decode("49.4V", Territory.NLD);
+ Rectangle rectangle = MapcodeCodec.decodeToRectangle("49.4V", Territory.NLD);
+ Point southWest = Point.fromDeg(52.37646900000124, 4.9084705);
+ Point northEast = Point.fromDeg(52.37655900000124, 4.908616250000001);
+ LOG.debug("decodeToRectangle: center={}. rectangle={}", center, rectangle);
+ assertEquals("center", center, rectangle.getCenter());
+ assertEquals("southWest", southWest, rectangle.getSouthWest());
+ assertEquals("northEast", northEast, rectangle.getNorthEast());
+
+ center = MapcodeCodec.decode("VHXGB.1J9J");
+ rectangle = MapcodeCodec.decodeToRectangle("VHXGB.1J9J");
+ southWest = Point.fromDeg(52.376483, 4.908505);
+ northEast = Point.fromDeg(52.376525, 4.908566);
+ LOG.debug("decodeToRectangle: center={}. rectangle={}", center, rectangle);
+ assertEquals("center", center, rectangle.getCenter());
+ assertEquals("southWest", southWest, rectangle.getSouthWest());
+ assertEquals("northEast", northEast, rectangle.getNorthEast());
+
+ center = MapcodeCodec.decode("VHXGB.ZPHH");
+ rectangle = MapcodeCodec.decodeToRectangle("VHXGB.ZPHH");
+ southWest = Point.fromDeg(52.370015, 4.963710);
+ northEast = Point.fromDeg(52.370057, 4.963758);
+ LOG.debug("decodeToRectangle: center={}. rectangle={}", center, rectangle);
+ assertEquals("center", center, rectangle.getCenter());
+ assertEquals("southWest", southWest, rectangle.getSouthWest());
+ assertEquals("northEast", northEast, rectangle.getNorthEast());
+
+ center = MapcodeCodec.decode("VHWK3.Z0HF");
+ rectangle = MapcodeCodec.decodeToRectangle("VHWK3.Z0HF");
+ southWest = Point.fromDeg(52.317869, 4.675245);
+ northEast = Point.fromDeg(52.317881, 4.675293);
+ LOG.debug("decodeToRectangle: center={}. rectangle={}", center, rectangle);
+ assertEquals("center", center, rectangle.getCenter());
+ assertEquals("southWest", southWest, rectangle.getSouthWest());
+ assertEquals("northEast", northEast, rectangle.getNorthEast());
+ }
+
@Test
public void decodeMapcodeWithTerritory() throws Exception {
LOG.info("decodeMapcodeWithTerritory");
diff --git a/src/test/java/com/mapcode/TerritoryTest.java b/src/test/java/com/mapcode/TerritoryTest.java
index aeec034..6b406d4 100644
--- a/src/test/java/com/mapcode/TerritoryTest.java
+++ b/src/test/java/com/mapcode/TerritoryTest.java
@@ -29,8 +29,8 @@ public class TerritoryTest {
private static final Logger LOG = LoggerFactory.getLogger(TerritoryTest.class);
@Test(expected = UnknownTerritoryException.class)
- public void emptyTerritoryCodeTest() {
- LOG.info("emptyCodeTest");
+ public void emptyTerritoryNumberTest() {
+ LOG.info("emptyTerritoryNumberTest");
Territory.fromString("");
}