Created
April 20, 2020 12:44
-
-
Save ianturton/aae8c1101a54726c30188283a059d5db to your computer and use it in GitHub Desktop.
OS Grid Reference conversion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package spike; | |
import org.locationtech.jts.geom.Coordinate; | |
import org.locationtech.jts.geom.GeometryFactory; | |
import org.locationtech.jts.geom.Point; | |
/** | |
* A class to allow the conversion of coordinates to and from full OS Grid | |
* References Based on the macros embedded in | |
* https://www.ordnancesurvey.co.uk/documents/resources/maptile-gridref-conversion.xlsm | |
* accessed from | |
* https://www.ordnancesurvey.co.uk/business-government/tools-support/os-net/coordinates | |
* | |
* @author ian | |
* | |
*/ | |
public class OSGridref { | |
static GeometryFactory GF = new GeometryFactory(); | |
static String[][] gridLetters = { { "SV", "SQ", "SL", "SF", "SA", "NV", "NQ", "NL", "NF", "NA", "HV", "HQ", "HL" }, | |
{ "SW", "SR", "SM", "SG", "SB", "NW", "NR", "NM", "NG", "NB", "HW", "HR", "HM" }, | |
{ "SX", "SS", "SN", "SH", "SC", "NX", "NS", "NN", "NH", "NC", "HX", "HS", "HN" }, | |
{ "SY", "ST", "SO", "SJ", "SD", "NY", "NT", "NO", "NJ", "ND", "HY", "HT", "HO" }, | |
{ "SZ", "SU", "SP", "SK", "SE", "NZ", "NU", "NP", "NK", "NE", "HZ", "HU", "HP" }, | |
{ "TV", "TQ", "TL", "TF", "TA", "OV", "OQ", "OL", "OF", "OA", "JV", "JQ", "JL" }, | |
{ "TW", "TR", "TM", "TG", "TB", "OW", "OR", "OM", "OG", "OB", "JW", "JR", "JM" } }; | |
/** | |
* Convert an OS Grid Reference to a JTS Point | |
* | |
* @param gridref | |
* - a String like NN166712 (any value from SE (100km sq) to | |
* SE2700020000 (1m sq) | |
* @return Point representation of the location pointed to by the grid | |
* reference in EPSG:27700 | |
*/ | |
static public Point convertGridRef(String gridref) { | |
String ref = gridref.toUpperCase(); | |
int easting = getEasting(ref); | |
int northing = getNorthing(ref); | |
return GF.createPoint(new Coordinate(easting, northing)); | |
} | |
/** | |
* | |
* @param p | |
* a Point in EPSG:27700 | |
* @param size | |
* the size of the square to be returned in km (100, 10, 5, 1, 0.5f, | |
* 0.1f, 0.01f, 0.001f) | |
* @return The grid reference including 100km grid square letters | |
*/ | |
static public String gridSquare(Point p, float size) { | |
return gridSquare(p.getCentroid(), size); | |
} | |
/** | |
* | |
* @param c | |
* a Coordinate in EPSG:27700 | |
* @param size | |
* the size of the square to be returned in km (100, 10, 5, 1, 0.5f, | |
* 0.1f, 0.01f, 0.001f) | |
* @return The grid reference including 100km grid square letters | |
*/ | |
static public String gridSquare(Coordinate c, float size) { | |
double x = c.x; | |
double y = c.y; | |
if (x < 0 || x >= 700000) | |
throw new RuntimeException("Easting " + x + " is not within the OS Grid"); | |
if (y < 0 || y >= 1300000) | |
throw new RuntimeException("Northing " + y + " is not within the OS Grid"); | |
String xText = String.format("%06d", (int) x); | |
String yText = String.format("%07d", (int) y); | |
String res = getSquare(size, xText, yText); | |
return res; | |
} | |
private static String getSquare(float size, String east, String north) { | |
int x = Integer.parseInt(east.substring(0, 1)); | |
int y = Integer.parseInt(north.substring(0, 2)); | |
String res = ""; | |
String quadrant = ""; | |
int mx, my; | |
int tSize = (int) (size * 1000); | |
String gl = gridLetters[x][y]; | |
switch (tSize) { | |
case 100000: | |
res = gl; | |
break; | |
case 10000: | |
res = gl + mid(east, 2, 1) + mid(north, 3, 1); | |
break; | |
case 5000: | |
mx = Integer.parseInt(mid(east, 3, 1)); | |
my = Integer.parseInt(mid(north, 4, 1)); | |
if (mx < 5 && my < 5) { | |
quadrant = "SW"; | |
} else if (mx < 5 && my >= 5) { | |
quadrant = "NW"; | |
} else if (mx >= 5 && my < 5) { | |
quadrant = "SE"; | |
} else if (mx >= 5 && my >= 5) { | |
quadrant = "NE"; | |
} | |
res = gl + mid(east, 2, 1) + mid(north, 3, 1) + quadrant; | |
break; | |
case 1000: | |
res = gl + mid(east, 2, 2) + mid(north, 3, 2); | |
break; | |
case 500: | |
mx = Integer.parseInt(mid(east, 4, 1)); | |
my = Integer.parseInt(mid(north, 5, 1)); | |
if (mx < 5 && my < 5) { | |
quadrant = "SW"; | |
} else if (mx < 5 && my >= 5) { | |
quadrant = "NW"; | |
} else if (mx >= 5 && my < 5) { | |
quadrant = "SE"; | |
} else if (mx >= 5 && my >= 5) { | |
quadrant = "NE"; | |
} | |
res = gl + mid(east, 2, 2) + mid(north, 3, 2) + quadrant; | |
break; | |
case 100: | |
res = gl + mid(east, 2, 3) + mid(north, 3, 3); | |
break; | |
case 10: | |
res = gl + mid(east, 2, 4) + mid(north, 3, 4); | |
break; | |
case 1: | |
res = gl + mid(east, 2, 5) + mid(north, 3, 5); | |
break; | |
default: | |
throw new RuntimeException("Unexpected size " + size + "expected on of 100, 10, 5, 1, .5, .1, .01, .001"); | |
} | |
return res; | |
} | |
private static String mid(String in, int start, int length) { | |
start--; | |
return in.substring(start, start + length); | |
} | |
/** | |
* @param Full | |
* grid reference | |
* @return Easting in metres | |
*/ | |
public static int getEasting(String ref) { | |
char first = ref.charAt(0); | |
if (first != 'H' && first != 'J' && first != 'N' && first != 'O' && first != 'S' && first != 'T') { | |
throw new IllegalArgumentException("Invalid grid reference starting letter " + ref); | |
} | |
char second = ref.charAt(1); | |
if (second < 65 || second > 90 || second == 'I') { | |
throw new IllegalArgumentException("Invalid grid reference second letter " + ref); | |
} | |
int len = ref.length(); | |
if (len < 2 || len > 12 || len % 2 != 0) { | |
throw new IllegalArgumentException("Invalid grid reference wrong length " + ref); | |
} | |
boolean quadExists = false; | |
String quad = ""; | |
if (len > 6 && (ref.endsWith("SW") || ref.endsWith("NW") || ref.endsWith("NE") || ref.endsWith("SE"))) { | |
quadExists = true; | |
quad = ref.substring(len - 2); | |
} | |
String numbs = ref; | |
if (!quadExists && len > 2) { | |
numbs = ref.substring(2); | |
} else if (quadExists) { | |
numbs = ref.substring(2, len - 2); | |
} | |
if (len > 2) { | |
double coords; | |
try { | |
coords = Double.parseDouble(numbs); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
} | |
// convert to a coordinate | |
String hundreds = Character.toString(first) + Character.toString(second); | |
boolean match = false; | |
int easting1st = 0; | |
for (int x = 0; x < 6; x++) { | |
for (int y = 0; y < 12; y++) { | |
if (gridLetters[x][y].equalsIgnoreCase(hundreds)) { | |
easting1st = x; | |
match = true; | |
} | |
} | |
} | |
if (!match) | |
throw new IllegalArgumentException("Invalid tile square " + hundreds); | |
String eastings; | |
int lenNum = numbs.length(); | |
switch (len) { | |
case 2: | |
// 100km square | |
return 100_000 * easting1st; | |
case 4: | |
// 10km Square | |
eastings = Integer.toString(easting1st) + numbs.substring(0, lenNum / 2); | |
try { | |
return Integer.parseInt(eastings) * 10_000; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 6: | |
// 5 or 1km square | |
String qDigit = ""; | |
if ("SW".equalsIgnoreCase(quad) || "NW".equalsIgnoreCase(quad)) { | |
qDigit = "0"; | |
} else if ("SE".equalsIgnoreCase(quad) || "NE".equalsIgnoreCase(quad)) { | |
qDigit = "5"; | |
} | |
eastings = Integer.toString(easting1st) + numbs.substring(0, lenNum / 2) + qDigit; | |
try { | |
return Integer.parseInt(eastings) * 1000; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 8: | |
// 500 or 100m square | |
qDigit = ""; | |
if ("SW".equalsIgnoreCase(quad) || "NW".equalsIgnoreCase(quad)) { | |
qDigit = "0"; | |
} else if ("SE".equalsIgnoreCase(quad) || "NE".equalsIgnoreCase(quad)) { | |
qDigit = "5"; | |
} | |
eastings = Integer.toString(easting1st) + numbs.substring(0, lenNum / 2) + qDigit; | |
try { | |
return Integer.parseInt(eastings) * 100; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 10: | |
// 10m square | |
eastings = numbs.substring(0, lenNum / 2); | |
try { | |
return Integer.parseInt(eastings) * 10; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 12: | |
// 1m square | |
eastings = numbs.substring(0, lenNum / 2); | |
try { | |
return Integer.parseInt(eastings); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
default: | |
throw new IllegalArgumentException("Unexpected input in getEasting " + ref); | |
} | |
} | |
/** | |
* @param Full | |
* grid reference | |
* @return Northing in metres | |
*/ | |
public static int getNorthing(String ref) { | |
char first = ref.charAt(0); | |
if (first != 'H' && first != 'J' && first != 'N' && first != 'O' && first != 'S' && first != 'T') { | |
throw new IllegalArgumentException("Invalid grid reference starting letter " + ref); | |
} | |
char second = ref.charAt(1); | |
if (second < 65 || second > 90 || second == 'I') { | |
throw new IllegalArgumentException("Invalid grid reference second letter " + ref); | |
} | |
int len = ref.length(); | |
if (len < 2 || len > 12 || len % 2 != 0) { | |
throw new IllegalArgumentException("Invalid grid reference wrong length " + ref); | |
} | |
boolean quadExists = false; | |
String quad = ""; | |
if (len > 6 && (ref.endsWith("SW") || ref.endsWith("NW") || ref.endsWith("NE") || ref.endsWith("SE"))) { | |
quadExists = true; | |
quad = ref.substring(len - 2); | |
} | |
String numbs = ref; | |
if (!quadExists && len > 2) { | |
numbs = ref.substring(2); | |
} else if (quadExists) { | |
numbs = ref.substring(2, len - 2); | |
} | |
if (len > 2) { | |
double coords; | |
try { | |
coords = Double.parseDouble(numbs); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
} | |
// convert to a coordinate | |
String hundreds = Character.toString(first) + Character.toString(second); | |
boolean match = false; | |
int northing1st = 0; | |
for (int x = 0; x < 6; x++) { | |
for (int y = 0; y < 12; y++) { | |
if (gridLetters[x][y].equalsIgnoreCase(hundreds)) { | |
northing1st = y; | |
match = true; | |
} | |
} | |
} | |
if (!match) | |
throw new IllegalArgumentException("Invalid tile square " + hundreds); | |
String northings; | |
int lenNum = numbs.length(); | |
switch (len) { | |
case 2: | |
// 100km square | |
return 100_000 * northing1st; | |
case 4: | |
// 10km Square | |
northings = Integer.toString(northing1st) + numbs.substring(lenNum / 2); | |
try { | |
return Integer.parseInt(northings) * 10_000; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 6: | |
// 5 or 1km square | |
String qDigit = ""; | |
if ("SW".equalsIgnoreCase(quad) || "SE".equalsIgnoreCase(quad)) { | |
qDigit = "0"; | |
} else if ("NW".equalsIgnoreCase(quad) || "NE".equalsIgnoreCase(quad)) { | |
qDigit = "5"; | |
} | |
northings = Integer.toString(northing1st) + numbs.substring(lenNum / 2) + qDigit; | |
try { | |
return Integer.parseInt(northings) * 1000; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 8: | |
// 500 or 100m square | |
qDigit = ""; | |
if ("SW".equalsIgnoreCase(quad) || "NW".equalsIgnoreCase(quad)) { | |
qDigit = "0"; | |
} else if ("SE".equalsIgnoreCase(quad) || "NE".equalsIgnoreCase(quad)) { | |
qDigit = "5"; | |
} | |
northings = Integer.toString(northing1st) + numbs.substring(lenNum / 2) + qDigit; | |
try { | |
return Integer.parseInt(northings) * 100; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 10: | |
// 10m square | |
northings = numbs.substring(lenNum / 2); | |
try { | |
return Integer.parseInt(northings) * 10; | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
case 12: | |
// 1m square | |
northings = numbs.substring(lenNum / 2); | |
try { | |
return Integer.parseInt(northings); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("bad numeric format in " + ref, e); | |
} | |
default: | |
throw new IllegalArgumentException("Unexpected input in getEasting " + ref); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package spike; | |
import static org.junit.Assert.assertEquals; | |
import org.junit.Test; | |
import org.locationtech.jts.geom.Coordinate; | |
public class OSGridrefTest { | |
@Test | |
public void testConvertGridRef() { | |
assertEquals("POINT (427000 420000)", OSGridref.convertGridRef("SE2720").toString()); | |
assertEquals("POINT (216600 771200)", OSGridref.convertGridRef("NN166712").toString()); // Ben | |
// Nevis | |
} | |
@Test | |
public void testGetEasting() { | |
assertEquals(427000, OSGridref.getEasting("SE2720")); | |
assertEquals(400000, OSGridref.getEasting("SE")); | |
assertEquals(400000, OSGridref.getEasting("SE00")); | |
assertEquals(400000, OSGridref.getEasting("SE0000")); | |
assertEquals(400000, OSGridref.getEasting("SE0000SW")); | |
assertEquals(400000, OSGridref.getEasting("SE000000")); | |
} | |
@Test | |
public void testGetNorthing() { | |
assertEquals(420000, OSGridref.getNorthing("SE2720")); | |
assertEquals(400000, OSGridref.getNorthing("SE")); | |
assertEquals(400000, OSGridref.getNorthing("SE00")); | |
assertEquals(400000, OSGridref.getNorthing("SE0000")); | |
assertEquals(400000, OSGridref.getNorthing("SE0000SW")); | |
assertEquals(400000, OSGridref.getNorthing("SE000000")); | |
} | |
@Test | |
public void testGridSquare() { | |
Coordinate c = new Coordinate(427000, 420000); | |
assertEquals("SE", OSGridref.gridSquare(c, 100)); | |
assertEquals("SE22", OSGridref.gridSquare(c, 10)); | |
assertEquals("SE2720", OSGridref.gridSquare(c, 1)); | |
assertEquals("SE270200", OSGridref.gridSquare(c, .1f)); | |
assertEquals("SE27002000", OSGridref.gridSquare(c, .01f)); | |
assertEquals("SE2700020000", OSGridref.gridSquare(c, .001f)); | |
c = new Coordinate(400000, 400000); | |
assertEquals("SE", OSGridref.gridSquare(c, 100)); | |
assertEquals("SE00", OSGridref.gridSquare(c, 10)); | |
assertEquals("SE00SW", OSGridref.gridSquare(c, 5)); | |
assertEquals("SE0000", OSGridref.gridSquare(c, 1)); | |
assertEquals("SE0000SW", OSGridref.gridSquare(c, .5f)); | |
assertEquals("SE000000", OSGridref.gridSquare(c, .1f)); | |
assertEquals("SE00000000", OSGridref.gridSquare(c, .01f)); | |
assertEquals("SE0000000000", OSGridref.gridSquare(c, .001f)); | |
c = new Coordinate(216600, 771200); | |
assertEquals("NN", OSGridref.gridSquare(c, 100)); | |
assertEquals("NN17", OSGridref.gridSquare(c, 10)); | |
assertEquals("NN1671", OSGridref.gridSquare(c, 1)); | |
assertEquals("NN166712", OSGridref.gridSquare(c, .1f)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment