Skip to content

Instantly share code, notes, and snippets.

@ianturton
Created April 20, 2020 12:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianturton/aae8c1101a54726c30188283a059d5db to your computer and use it in GitHub Desktop.
Save ianturton/aae8c1101a54726c30188283a059d5db to your computer and use it in GitHub Desktop.
OS Grid Reference conversion
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);
}
}
}
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