src/0000777000000000000000000000000014573074321006545 5ustar src/uk/0000777000000000000000000000000014573074157007173 5ustar src/uk/co/0000777000000000000000000000000014573074222007565 5ustar src/uk/co/mccombe/0000777000000000000000000000000014573074317011177 5ustar src/uk/co/mccombe/mapping/0000777000000000000000000000000014573074252012630 5ustar src/uk/co/mccombe/mapping/AustrianM28.java0000777000000000000000000000317514573074222015556 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.mccombe.mapping; /** * * @author Mike */ public class AustrianM28 extends BMN { public AustrianM28(Position p, Ellipsoid e, Datum d){ super(p, e, d); zonename = "M28"; } public AustrianM28(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } public static AustrianM28 makePoint(String gridRef, Ellipsoid e, Datum d)throws GridFormatException { String arg = gridRef.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); if (matcher.find()) { String zoneNumber = matcher.group(1); String eastingNum = matcher.group(2); String northingNum = matcher.group(3); try { double xCoord = Double.parseDouble(eastingNum); double yCoord = Double.parseDouble(northingNum); ENPair pa = new ENPair(xCoord, yCoord); return new AustrianM28(pa, e, d); } catch (NumberFormatException ee) { throw new GridFormatException("Illegal BMN format"); } } throw new GridFormatException("Invalid BMN grid reference"); } @Override public double e0() { return 150000.0 ; } @Override public double lamda0() { return Math.toRadians(10.0 + 1.0/3.0); } private static final String validationRegex1 = "(M28)?\\s*(\\d+\\.?\\d*)\\s*(\\d+\\.?\\d*)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); } src/uk/co/mccombe/mapping/AustrianM31.java0000777000000000000000000000317514573074221015547 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.mccombe.mapping; /** * * @author Mike */ public class AustrianM31 extends BMN { public AustrianM31(Position p, Ellipsoid e, Datum d){ super(p, e, d); zonename = "M31"; } public AustrianM31(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } public static AustrianM31 makePoint(String gridRef, Ellipsoid e, Datum d)throws GridFormatException { String arg = gridRef.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); if (matcher.find()) { String zoneNumber = matcher.group(1); String eastingNum = matcher.group(2); String northingNum = matcher.group(3); try { double xCoord = Double.parseDouble(eastingNum); double yCoord = Double.parseDouble(northingNum); ENPair pa = new ENPair(xCoord, yCoord); return new AustrianM31(pa, e, d); } catch (NumberFormatException ee) { throw new GridFormatException("Illegal BMN format"); } } throw new GridFormatException("Invalid BMN grid reference"); } @Override public double e0() { return 450000.0 ; } @Override public double lamda0() { return Math.toRadians(13.0 + 1.0/3.0); } private static final String validationRegex1 = "(M31)?\\s*(\\d+\\.?\\d*)\\s*(\\d+\\.?\\d*)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); } src/uk/co/mccombe/mapping/AustrianM34.java0000777000000000000000000000317614573074221015553 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.mccombe.mapping; /** * * @author Mike */ public class AustrianM34 extends BMN { public AustrianM34(Position p, Ellipsoid e, Datum d){ super(p, e, d); zonename = "M34"; } public AustrianM34(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } public static AustrianM34 makePoint(String gridRef, Ellipsoid e, Datum d)throws GridFormatException { String arg = gridRef.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); if (matcher.find()) { String zoneNumber = matcher.group(1); String eastingNum = matcher.group(2); String northingNum = matcher.group(3); try { double xCoord = Double.parseDouble(eastingNum); double yCoord = Double.parseDouble(northingNum); ENPair pa = new ENPair(xCoord, yCoord); return new AustrianM34(pa, e, d); } catch (NumberFormatException ee) { throw new GridFormatException("Illegal BMN format"); } } throw new GridFormatException("Invalid BMN grid reference"); } @Override public double e0() { return 750000.0 ; } @Override public double lamda0() { return Math.toRadians(16.0 + 1.0/3.0); } private static final String validationRegex1 = "(M34)?\\s*(\\d+\\.?\\d*)\\s*(\\d+\\.?\\d*)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); } src/uk/co/mccombe/mapping/BMN.java0000777000000000000000000000420114573074222014104 0ustar /* */ package uk.co.mccombe.mapping; /** * Abstract base class for the Austrian BMN (Bundesmeldnetz) coordinate system * * @author Mike */ public abstract class BMN extends TransverseMercator { public BMN(Position p, Ellipsoid e, Datum d){ super(p, e, d); } public BMN(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } @Override public Datum defaultDatum() { return Datum.MGI; } @Override public Ellipsoid defaultEllipsoid() { return Ellipsoid.BESSEL;} /** * Provide a String representation of this UTM point in UTM coordinates * @return A UTM coordinate String */ public String toString() { ENPair pa = toEN(); return String.format("%s %8.0f %8.0f", zonename, pa.east(), pa.north()); } /** * The scale factor on the Central Meridian. Generally, Transverse Mercator * projections increasingly exaggerate distances further from the central * meridian. It is usual to reduce the scale factor at the central meridian * to compensate for this effect and optimise the scale over the area of interest. * @return the value of the ScaleFactor at the central meridian. */ @Override public double f0() { return 1.0000 ; } /** * n0() defines the "false northing" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. n0() is used as an * offset to the grid so that the "true origin" appears to have a northing value * equal to n0(). * * @return The false northing distance (double) */ @Override public double n0() { return -5000000.0; } /** * phi0() defines the latitude of the true origin of the projection. * * Note, however, that many Transverse Mercator projections employ a * false origin. See n0() and e0() . * * * @return The latitude of the true origin (radians) */ public double phi0() { return 0; } //Latitude of true origin ; protected String zonename = "XXX" ; } src/uk/co/mccombe/mapping/CoordinateSystem.java0000777000000000000000000001065314573074222016774 0ustar /* * CoordinateSystem.java * * Created on 05 July 2005, 17:17 * */ package uk.co.mccombe.mapping; /** * Abstract base class for coordinate systems in general. * * Each instance of CoordinateSystem contains a Position and references to the * Ellipsoid and Datum from which the coordinates are derived. The position itself is * independent of Ellipsoid and Datum. * @author Mike McCombe */ public abstract class CoordinateSystem { /** * Creates a new instance of CoordinateSystem using the default Ellipsoid and Datum */ protected CoordinateSystem() { } /** *

Create a new instance of CoordinateSystem for a specific Position, Ellipsoid and Datum

* @param pos Position of the point in this CoordinateSystem * @param e Ellipsoid to be used in this CoordinateSystem * @param d Datum for this instance of CopordinateSystem */ public CoordinateSystem(Position pos, Ellipsoid e, Datum d) { locus = pos; sph = e; ref = d; } /** * Get the Latitude and Longitude for this point. Note the result is referred to the current datum and ellipsoid * @return LatLong object containing latitude and longitude */ public LatLong toLatLong() { return locus.toLatLong(sph, ref); } /** * Return a String containing values of Latitude and Longitude.referred to the current Ellipsoid and Datum * @return String containing Latitude and Longitude in degrees. North or East are represented by positive values, South or West are negative. */ public String toLatLongString() { LatLong geog = locus.toLatLong(sph, ref); String res = String.format(java.util.Locale.UK, "%10.6f %11.6f", geog.lat(), geog.lon()); return res; } /** * Get the Datum used by this point * @return The Datum associated with this point */ public Datum getDatum() { return ref; } /** * Get the Ellipsoid used by this point * @return Ellipsoid */ public Ellipsoid getEllipsoid() { return sph; } /** *

Get the name of this CoordinateSystem. By default, this method returns the short name of the * class. For example, an instance of mccombe.mapping.IrishGrid returns the name "IrishGrid".

*

Implementers of sub-classes of CoordinateSystem are encouraged to override this method if a * more descriptive name is needed

* @return The name of the CoordinateSystem */ public String getName() { String fullName = getClass().getName(); int i = fullName.lastIndexOf("."); String name = fullName.substring(i + 1); return name; } /** * Get the Position of this point * @return The Position of this point */ public Position getPosition() { return locus; } /** * Get the "absolute" cartesian coordinates for this location. These are based on the WGS-84 datum and coordinate system. * @return XYZ object containing the cartesian coordinates for this point. */ public XYZ getWGS84() { return locus.coords(Datum.WGS_1984); } /** * The default Ellipsoid used by instances of this CoordinateSystem * * For example, a CoordinateSystem of type OSGB has the AirySphere as its default Ellipsoid * @return The Ellipsoid used as the deafult for this CoordinateSystem */ public abstract Ellipsoid defaultEllipsoid(); /** * Get the defaul Datum for an instance of CoordinateSystem. * * For example, instances of OSGB usually use the OSGB 1936 datum. * @return The default Datum used by this instance of CoordinateSystem */ public abstract Datum defaultDatum(); /** * Provide a String representing the position in a correct format for the * CoordinateSystem * @return String */ public abstract String toString(); /* * Parse a String for a double value using the current locale */ protected static double parseDouble(String s) throws java.text.ParseException { java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); return nf.parse(s.trim()).doubleValue(); } /** * The Position of this Coordinate */ protected Position locus; /** * The Ellipsoid for this point */ protected Ellipsoid sph; /** * The Datum for this point */ protected Datum ref; } src/uk/co/mccombe/mapping/Datum.java0000777000000000000000000002016614573074222014552 0ustar /* * Datum.java * * Created on 13 July 2005, 13:13 * */ package uk.co.mccombe.mapping; /** *

Datum is the base class for a coordinate system datum. Sub-classes * need to define values for the seven Helmert parameters needed to translate to the * Datum FROM WGS-84

* *

An extensive list of Helmert parameters can be found at http://earth-info.nga.mil/GandG/coordsys/datums/helmert.html * Units are *

*
  s - ppm
 *  rx, ry, rz - seconds of arc. 
 *  tx, ty, tz - metres
 * 
*

* This class also defines a set of static final member instances for common Datums *

* @author Mike McCombe */ public class Datum { /** * Create an instance of Datum using Helmert coefficients in abstract methods */ protected Datum(String name, double tx, double ty, double tz, double rotx, double roty, double rotz, double scale){ datumName = name ; t = new XYZ(-tx, -ty, -tz); rx = -rotx*R ; ry = -roty*R ; rz = -rotz*R ; s = -scale*1.0e-6 ; rot.set(0,0, 1+s); rot.set(0,1, -rz); rot.set(0,2, ry); rot.set(1,0, rz); rot.set(1,1, 1+s); rot.set(1,2, -rx); rot.set(2,0, -ry); rot.set(2,1, rx); rot.set(2,2, 1+s); rin = rot.inverse(); } /** * Converts XYZ coordinates from WGS-84 to this datum using Helmert Transformation * @param from XYZ Coordinates referred to WGS-84 * * @return XYZ Coordinates referred to this datum */ public XYZ fromWGS84(XYZ from){ Vector f = new Vector(from); Vector r = rot.times(f); Vector q = r.plus(t); return new XYZ(q.get(0), q.get(1), q.get(2)) ; } /** * Convert XYZ Coordinates referred to this datum to WGS-84 * @param to XYZ Coordinates to convert * @return XYZ Coordinates referred to WGS-84 */ public XYZ toWGS84(XYZ to){ Vector temp = new Vector(to); Vector q = temp.minus(t); Vector r = rin.times(q); return new XYZ(r.get(0), r.get(1), r.get(2)) ; } /** * Get X translation * @return X translation */ public double tx() { return t.x() ;} /** * Get Y translation * @return Y translation */ public double ty() { return t.y();} /** * Get Z translation * @return Z Translation */ public double tz() {return t.z();} /** * Get rotation about X * @return X rotation */ public double rx() { return rx ;} /** * Get Y rotation * @return Y rotation */ public double ry() { return ry ;} /** * Get Z rotation * @return Z rotation */ public double rz() { return rz ;} /** * Get scale factor adjustment * @return Scale factor adjustment (ppm) */ public double s() { return s ;} /** * Get name of Datum * @return Datum name */ @Override public String toString() { return datumName;} /** * The European (1950) Datum */ public static final Datum ED_1950 = new Datum("European Datum 1950 (Western Europe)", -87.0, -96.0, -120.0, 0.0, 0.0, 0.0, 0.0); /** * The Ireland (1965) Datum */ public static final Datum IRELAND_1965 = new Datum("Ireland 1965", 482.53, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15) ; /** * The French NTF Datum (used in IGN/Lambert projections) */ public static final Datum NTF = new Datum("NTF Datum France (IGN)", -168.0, -60.0, 320.0, 0.0, 0.0, 0.0, 0.0); /** * The OSGB (1936) Datum - used as the Datum for UK Ordnance Survey mapping */ public static final Datum OSGB_1936 = new Datum("Ordnance Survey of Great Britain 1936", 446.448, -125.157, 542.06, 0.150, 0.2470, 0.8421, -20.49); /** * The WGS (1984) Datum */ public static final Datum WGS_1984 = new Datum("WGS-84", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); /** * The Australian (1984) Geodetic Datum */ public static final Datum AUG_7 = new Datum("Australian Geodetic 1984", -116.0, -50.47, 141.69, 0.23, 0.39, 0.344, 0.0983); public static final Datum MGI = new Datum("MGI Datum (Austria)", 577.3,90.1, 463.9, 5.137, 1.474, 5.297, 2.42); //"Official NATO values // public static final Datum MGI = new Datum("MGI Datum (Austria)", 575 ,93, 466, 5.1, 1.6, 5.2, 2.5); //GEO values - see http://homepage.ntlworld.com/anton.helm/bmn_mgi.html public static final Datum MGI_SLOV = new Datum("MGI Datum (Slovenia)", 426.9,142.6,460.1,4.91,4.49,-12.42,17.1); //From OPC - see https://epsg.io/3912-3915 /** * The New Zealand 1949 Datum */ public static final Datum NZGD_1949 = new Datum("New Zealand 1949",59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993); /** * The New Zealand 2000 Datum */ public static final Datum NZGD_2000 = new Datum("New Zealand 2000",0.0,0.0,0.0,0.0,0.0,0.0,0.0); private Matrix rot = new Matrix(); private Matrix rin = new Matrix(); private XYZ t ; private double rx = 0.0 ; private double ry = 0.0 ; private double rz = 0.0 ; private double s = 0.0 ; private String datumName = "" ; private static final double R = 4.848136811095360E-06 ; //Seconds of arc to Radians private class Matrix { Matrix(){ } void set(int ix, int iy, double val){ store[ix][iy] = val ; } double get(int ix, int iy){ return store[ix][iy] ; } Vector times(Vector v){ Vector res = new Vector() ; for(int i=0 ; i<3 ; i++){ double tot = 0.0 ; for(int j=0 ; j<3 ; j++){ tot += store[i][j]*v.v[j] ; } res.set(i, tot); } return res ; } Matrix times(Matrix m){ Matrix res = new Matrix() ; for(int i=0 ; i<3 ; i++){ for(int j=0 ; j<3 ; j++){ double tot = 0.0 ; for(int k=0 ; k<3 ; k++){ tot += store[i][k]*m.store[k][j] ; } res.store[i][j] = tot; } } return res ; } Matrix inverse(){ double d = det(); Matrix res = new Matrix(); for(int i=0 ; i<3 ; i++){ for (int j=0 ; j<3 ; j++){ res.store[j][i] = cofactor(i, j)/d; } } return res ; } double det() { double res = 0.0 ; for (int j=0 ; j<3 ; j++){ res += store[0][j]*cofactor(0,j); } return res ; } double cofactor(int row, int col){ int r1 = ptr[row][0]; int r2 = ptr[row][1]; int c1 = ptr[col][0]; int c2 = ptr[col][1]; double det = store[r1][c1]*store[r2][c2] - store[r1][c2]*store[r2][c1]; double sign = 1.0 ; if(row!=col){ sign = Math.pow(-1.0,(row-col)); } return sign * det ; } private final int[][] ptr = {{1,2},{0,2},{0,1}}; private double[][] store = {{ 0,0,0},{0,0,0},{0,0,0}}; } private class Vector { Vector(){ } Vector(XYZ p){ v[0] = p.x(); v[1] = p.y(); v[2] = p.z(); } void set(int ix, double val){ v[ix] = val ; } double get(int ix) { return v[ix]; } Vector minus(Vector a){ Vector y = new Vector(); for(int ix = 0 ; ix<3 ; ix++){ y.v[ix] = v[ix] - a.v[ix]; } return y ; } Vector minus(XYZ xyz){ Vector p = new Vector(xyz); return minus(p); } Vector plus(Vector a){ Vector y = new Vector(); for(int ix = 0 ; ix<3 ; ix++){ y.v[ix] = v[ix] + a.v[ix]; } return y ; } Vector plus(XYZ xyz){ Vector p = new Vector(xyz); return plus(p); } private double[] v = { 0,0,0}; } } src/uk/co/mccombe/mapping/Ellipsoid.java0000777000000000000000000000712614573074222015425 0ustar /* * Ellipsoid.java * * Created on 05 July 2005, 18:07 * */ package uk.co.mccombe.mapping; /** * Ellipsoid is the base class used to describe the shape * of the earth's surface. It is a biaxial * ellipsoid, slightly flattened at the poles. * * @author Mike McCombe */ public class Ellipsoid { /** * Create an Ellipsoid with specified major and minor exes. * @param name Defines the name of this Ellipsoid * @param major Major axis (m) * * @param minor Minor axis (m) */ protected Ellipsoid(String name, double major, double minor) { majoraxis = major; minoraxis = minor; majsq = majoraxis * majoraxis; minsq = minoraxis * minoraxis; eccsq = (majsq - minsq) / majsq; //eccentricity ^ 2 ecc4 = eccsq * eccsq; ecc6 = eccsq * ecc4; ecc2sq = (majsq - minsq) / minsq; //second eccentricity ^2 ellipsoidName = name; } /** * Major Axis (m) of the spheroid * @return The major axis of the Ellipsoid */ public double majoraxis() { return majoraxis; } /** * Minor axis (m) of the spheroid * @return The minor axis (m) of the Ellipsoid */ public double minoraxis() { return minoraxis; } /** * The eccentricity squared for this Ellipsoid. * @return the eccentricity squared ( i.e. (a^2 - b^2)/(a^2) */ protected double eccsq() { return eccsq; } /** * Provide a String identifying this Ellipsoid * @return The name of this Ellipsoid */ @Override public String toString() { return ellipsoidName; } /** * The Airy Sphere 1830 - "best fit" Ellipsoid for Great Britain */ public static final Ellipsoid AIRY = new Ellipsoid("Airy Sphere 1830", 6377563.396, 6356256.910); /** * The Clarke (1880) Ellipsoid. Used in France with the NTF Datum and * Lambert Conformal Conical (LCC) projection */ public static final Ellipsoid CLARKE = new Ellipsoid("Clarke 1880", 6378249.200, 6356515.000); /** * The Hayford (1909) Ellipsoid. Typically used with he European 1950 (ED50) Datum */ public static final Ellipsoid HAYFORD = new Ellipsoid("Hayford 1909", 6378388.000, 6356911.946); /** * The GRS80 Ellipsoid - "Best fit" ellipsoid for the whole Earth. Defined for Global * Positioning System (GPS) and used with the WGS84 Datum. */ public static final Ellipsoid GRS80 = new Ellipsoid("GRS80", 6378137.0000, 6356752.3141); /** * The "Modified Airy" ellipsoid. The "best fit" Ellipsoid for Ireland and used with the Irish Grid. */ public static final Ellipsoid MODIFIED_AIRY = new Ellipsoid("Airy 1830 Modified", 6377340.189, 6356034.447); /** * The Bessel 1841 Ellipsoid. Used with the Austrian Grid */ public static final Ellipsoid BESSEL = new Ellipsoid("Bessel 1841", 6377397.155, 6356078.962818); /** * The Bessel 1841 Ellipsoid. Used with the Austrian Grid */ public static final Ellipsoid INTERNATIONAL = new Ellipsoid("International 1924", 6378388, 6356911.9461); private double majoraxis = 0.0; //The majoraxis of this Ellipsoid private double minoraxis = 0.0; //The minoraxis of this Ellipsoid private double majsq = majoraxis * majoraxis; private double minsq = minoraxis * minoraxis; private double eccsq = (majsq - minsq) / majsq; //eccentricity ^ 2 private double ecc4 = eccsq * eccsq; private double ecc6 = eccsq * ecc4; private double ecc2sq = (majsq - minsq) / minsq; //second eccentricity ^2 private String ellipsoidName = ""; } src/uk/co/mccombe/mapping/ENPair.java0000777000000000000000000000215314573074221014611 0ustar /* * ENPair.java * * Created on 05 July 2005, 16:56 * */ package uk.co.mccombe.mapping; /** * An ENPair represents a 2-dimensional coordinate pair used to define position on * a map in terms of "easting" and "northing" distances. * @author Mike McCombe */ public class ENPair { /** Creates a new instance of ENPair */ public ENPair() { } /** * Create an ENPair from two double values * @param east Easting distance (m) * @param north Northing distance (m) */ public ENPair(double east, double north){ x = east ; y = north ; } /** * Provide a String representation of the ENPair * @return A String showing Easting and Northing distances */ public String toString() { return String.format("%9.0f %9.0f",x,y);} /** * Access the Easting distance * @return the Easting distance(m) */ public double east() { return x ; } /** * Access the Northing distance * @return the Northing distance (m) */ public double north() { return y ; } private double x = 0.0 ; private double y = 0.0 ; } src/uk/co/mccombe/mapping/GridFormatException.java0000777000000000000000000000103214573074222017404 0ustar package uk.co.mccombe.mapping ; /** * An exception thrown when conversion from grid reference is impossible */ public class GridFormatException extends Exception { /** * Create an exception with the specified message * @param s text * */ public GridFormatException(String s) { super(); val = new String(s) ; } /** * Convert Exception to a String * * @return - the message */ public String toString() { return "GridFormatException -- " + val ; } private String val ; }src/uk/co/mccombe/mapping/IrishGrid.java0000777000000000000000000001463714573074222015372 0ustar /* * IrishGrid.java * * * Created on 24 August 2005, 18:12 * */ package uk.co.mccombe.mapping; /** * Implementation of the Irish Grid. Note that this is used throughout the island of Ireland, * North and South and that the OSGB grid is not applicable. * * * For a complete description of the Irish Grid, see http://www.osni.gov.uk/2.1_the_irish_grid.pdf * @author Mike McCombe */ public class IrishGrid extends TransverseMercator { /** * Create a new IrishGrid point for a specific Position, Ellipsoid and Datum. * @param p Position * @param e Ellipsoid to use with this instance * @param d Datum to use */ public IrishGrid(Position p, Ellipsoid e, Datum d) { super(p, e, d); } /** * Create a new IrishGrid point for a specific pair of Eastings and Northings, Ellipsoid and Datum. * @param point ENPair containing the easting and northing values * @param e Ellipsoid to use with this instance * @param d Datum to use */ public IrishGrid(ENPair point, Ellipsoid e, Datum d) { super(point, e, d); } /** * Create a new IrishGrid point for a specific pair of Eastings and Northings. Default values of Ellipsoid * and Datum are used. * @param point ENPair containing the easting and northing values */ public IrishGrid(ENPair point) { this(point, Ellipsoid.MODIFIED_AIRY, Datum.IRELAND_1965); } /** * Factory method to create a new IrishGrid point using a String containing * a grid reference. * @param gridref A valid Irish grid reference String (e.g. R 212 712) * @param e Ellipsoid to use * @param d Datum to use * @return A new IrishGrid point for the specified point, Ellipsoid and Datum * @throws uk.co.mccombe.mapping.GridFormatException In case of syntax error in he grid reference */ public static IrishGrid makePoint(String gridref, Ellipsoid e, Datum d) throws GridFormatException { ENPair point = getEN(gridref); return new IrishGrid(point, e, d); } private static ENPair getEN(String gridRef) throws GridFormatException { String target = gridRef.toUpperCase().trim(); String eastDigits = ""; String northDigits = ""; String gridLetter = ""; java.util.regex.Matcher match = pattern.matcher(target); if (match.matches()) { int n = match.groupCount(); gridLetter = match.group(1); if (match.group(3) == null) { String digits = match.group(2); int m = digits.length(); if (m % 2 != 0) { throw new GridFormatException("Invalid grid format - odd no. of digits"); } eastDigits = digits.substring(0, m / 2); northDigits = digits.substring(m / 2); } else { eastDigits = match.group(2); northDigits = match.group(3); if (eastDigits.length() != northDigits.length()) { throw new GridFormatException("Invalid grid format - easting and northing must have same no. of digits"); } } } else { throw new GridFormatException("Invalid grid reference format"); } try { double e = Double.parseDouble(justify(eastDigits)); double n = Double.parseDouble(justify(northDigits)); if (gridLetter.length() == 1) { int k = gridLetters.indexOf(gridLetter); int kn = k / 5; int ke = k % 5; e += ke * 100000; n += kn * 100000; } return new ENPair(e, n); } catch (NumberFormatException exc) { throw new GridFormatException("Invalid grid format - incorrect digits"); } } private static String justify(String s) { String padding = "00000"; int i = s.length(); if (i < 5) { int n = 5 - i; return s + padding.substring(0, n); } else if (i > 5) { return s.substring(0, 5) + "." + s.substring(5); } return s; } /** * Defines the default Datum for this system * @return Datum.Ireland_1965 */ public Datum defaultDatum() { return Datum.IRELAND_1965; } /** * Define the default Ellipsoid for this system * @return Ellipsoid.MODIFIED_AIRY */ public Ellipsoid defaultEllipsoid() { return Ellipsoid.MODIFIED_AIRY; } /** * Provide a String representation for this IrishGrid * @return A 10-figure Irish grid reference, with grid letter (e.g. R 21235 71262) */ public String toString() { ENPair en = this.toEN(); int e = (int) Math.round(en.east()); int n = (int) Math.round(en.north()); int j = e / 100000; int k = n / 100000; int m = 5 * k + j; String sq = gridLetters.substring(m, m + 1); j = e % 100000; k = n % 100000; return String.format("%s %05d %05d", sq, j, k); } /** * The central meridian * @return The central meridian (degrees) */ public double lamda0() { return Math.toRadians(-8.0); } /** * False Eastimg (metres) * @return False easting value (metres) */ public double e0() { return 200000.0; } /** * Define false northing * @return False northing value (metres) */ public double n0() { return 250000.0; } /** * Define latitude of true origin * @return Latitude of true origin (degrees) */ public double phi0() { return Math.toRadians(53.5); } /** * Define scale factor * @return Scale factor at central meridian */ public double f0() { return 1.000035; } private static final String gridLetters = "VWXYZQRSTULMNOPFGHJKABCDE"; private static final String regex = "([A-HJ-Z])\\p{javaWhitespace}*([0-9]+)\\p{javaWhitespace}*([0-9]+)??"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex); protected static boolean validateEN(ENPair p) { double x = p.east(); double y = p.north(); return (x >= MIN_E && x < MAX_E && y >= MIN_N && y < MAX_N); } private static final double MAX_E = 500000.0; private static final double MIN_E = 0.0; private static final double MAX_N = 500000.0; private static final double MIN_N = 0.0; } src/uk/co/mccombe/mapping/Lambert.java0000777000000000000000000002540214573074222015064 0ustar /* * Lambert.java * * Created on 05 July 2005, 22:15 */ package uk.co.mccombe.mapping; /** *

An abstract base class for implementations of the Lambert Conformal Conic Projection

*

A Lambert projection maps points on the ellipsoid onto a cone which touches the ellipsoid * at two parallels of latitude. For countries with a large east-west extent, it provides smaller * variations in grid convergence than the more common TransverseMercator projection - but over * a limited range of latitude. IGN, France's national mapping agency, adopted the Lambert * Conformal Conic projection but had to divide mainland France into three zones of latitude, * with a fourth zone for the island of Corsica. These are implemented in this package as * subclasses of Lambert (see {@link uk.co.mccombe.mapping.LambertI LambertI}, * {@link uk.co.mccombe.mapping.LambertII LambertII}, * {@link uk.co.mccombe.mapping.LambertIII LambertIII}, * {@link uk.co.mccombe.mapping.LambertIV LambertIV}). To overcome the obvious inconvenience of * having the country divided into four distinct coordinate zones, a fifth set of Lambert Conformal Conical * projection coefficients {@link uk.co.mccombe.mapping.LambertIIExtended LambertIIExtended} was produced to * provide a nationwide coordinate system but with greater degrees of distortion. Ironically, * the Lambert Conical projections have not been well-supported by handheld GPS equipment and * many French maps (such as the 1:25000 Blue Series) are now over-printed with a UTM grid.

*

In 1996, IGN introduced a new coordinate system known as {@link uk.co.mccombe.mapping.Lambert93 Lambert 93} *

* @author Mike McCombe * @see ETRS89 Lambert Conformal Conic Coordinate Reference System * @see Systemes de Projection (in French) */ public abstract class Lambert extends uk.co.mccombe.mapping.Projection { /** * Creates a new instance of Lambert based on Position, Ellipsoid and Datum * @param p Position of this point * @param e The Ellipsoid to use * @param d The Datum associated with this instance */ public Lambert(Position p, Ellipsoid e, Datum d){ super(p, e, d); LatLong geog = p.toLatLong(e, d); double qL = q(phiL()); double qU = q(phiU()); double qB = q(phiB()); double wL = w(phiL()); double wU = w(phiU()); double sp0 = Math.log(wU*Math.cos(phiL())/(wL*Math.cos(phiU())))/(qU-qL); double a = sph.majoraxis(); double k = (a*Math.cos(phiL())*Math.exp(qL*sp0))/(wL*sp0); //Mapping radius at equator double r0 = k / Math.exp(qB*sp0); double q = q(Math.toRadians(geog.lat())); double r = k / Math.exp(q*sp0); gamma = (lamda0() - Math.toRadians(geog.lon()))*sp0 ; east = e0() - r*Math.sin(gamma); north = r0 + n0() - r*Math.cos(gamma); double sp = Math.sin(Math.toRadians(geog.lat())); scaleFactor = Math.sqrt(1 - sph.eccsq()*sp*sp)*r*sp0/(a*Math.cos(Math.toRadians(geog.lat()))); } /** * Create a new Lambert object based on Easting and Northing distances * @param en Easting and Northing values * @param sphere The Ellipsoid to use * @param datum the Datum to use */ public Lambert(ENPair en, Ellipsoid sphere, Datum datum) { sph = sphere ; ref = datum ; double xCoord = en.east() ; double yCoord = en.north() ; double qL = q(phiL()); double qU = q(phiU()); double qB = q(phiB()); double wL = w(phiL()); double wU = w(phiU()); double sp0 = Math.log(wU*Math.cos(phiL())/(wL*Math.cos(phiU())))/(qU-qL); double a = sph.majoraxis(); double k = (a*Math.cos(phiL())*Math.exp(qL*sp0))/(wL*sp0); //Mapping radius at equator double r0 = k / Math.exp(qB*sp0); double rdash = r0 - yCoord + n0() ; double edash = e0() - xCoord ; double r = Math.sqrt(edash*edash + rdash*rdash); double q = Math.log(k/r)/sp0 ; gamma = Math.atan(edash/rdash); double lamda = lamda0() - gamma/sp0 ; double sp = (Math.exp(2*q) - 1.0)/(Math.exp(2*q)+1); double corr = 1.0 ; double e = Math.sqrt(sph.eccsq()); while (Math.abs(corr) > LIMIT){ double f1 = (Math.log((1+sp)/(1-sp)) - e*Math.log((1+e*sp)/(1-e*sp)))/2.0 - q ; double f2 = 1/(1-sp*sp) - sph.eccsq()/(1-sph.eccsq()*sp*sp); corr = -f1/f2 ; sp += corr ; } double lat = Math.toDegrees(Math.asin(sp)); double lon = Math.toDegrees(lamda); scaleFactor = Math.sqrt(1 - sph.eccsq()*sp*sp)*r*sp0/(a*Math.cos(Math.asin(sp))); locus = new Position(new LatLong(lat,lon), 0.0, sphere, datum); } /** * Parse Lambert coordinates into easting and northing distances * @param gridref A pair of Lambert coordinates (e.g. "X=435.212 Y=217.306"), specified in km. * @throws uk.co.mccombe.mapping.GridFormatException Invalid coordinate format results in a GridFormatException being thrown * @return Easting and Northing distances (m) */ protected static ENPair getEN(String gridref) throws GridFormatException { double xCoord = 0.0 ; double yCoord = 0.0 ; boolean gotX = false ; boolean gotY = false ; try { String regex = "[ \t=]+"; String[] parts = gridref.split(regex); int n = parts.length ; switch (n) { case 2 : { xCoord = parseDouble(parts[0]); yCoord = parseDouble(parts[1]); break ; } case 4 : { for(int i=0 ; i<4 ; i+=2){ if(parts[i].equalsIgnoreCase("X")){ xCoord = parseDouble(parts[i+1]); gotX = true ; } else if(parts[i].equalsIgnoreCase("Y")){ yCoord = parseDouble(parts[i+1]); gotY = true ; } } if(gotX && gotY) break ; } default : { throw new GridFormatException("Invalid Lambert Coordinate String"); } } } catch (java.util.regex.PatternSyntaxException e){ throw new GridFormatException("Invalid Lambert Coordinate String"); } catch (NumberFormatException e){ throw new GridFormatException("Invalid Lambert Coordinate String"); } catch (java.text.ParseException e){ throw new GridFormatException("Invalid Lambert Coordinate String"); } return new ENPair(xCoord*1000.0, yCoord*1000.0); } /** * Provide easting and northing distances * @return Easting and Northing distances (in metres) */ public ENPair toEN() { calcCoords(); return new ENPair(east,north); } /** * Provide a String representation in Lambert coordinates. * These are of the form "X=eeee.eee Y=nnnn.nnn" where eeee.eee and nnnn.nnn are the easting and northing * distances in km. * @return The coordinate String */ public String toString(){ calcCoords(); String res = String.format("X = %11.3f Y = %11.3f", east/1000.0, north/1000.0); return res ; } /** * Initialise coordinates for this Position */ protected void calcCoords() { LatLong geog = locus.toLatLong(sph, ref); double qL = q(phiL()); double qU = q(phiU()); double qB = q(phiB()); double wL = w(phiL()); double wU = w(phiU()); double sp0 = Math.log(wU*Math.cos(phiL())/(wL*Math.cos(phiU())))/(qU-qL); double a = sph.majoraxis(); double k = (a*Math.cos(phiL())*Math.exp(qL*sp0))/(wL*sp0); //Mapping radius at equator double r0 = k / Math.exp(qB*sp0); double q = q(Math.toRadians(geog.lat())); double r = k / Math.exp(q*sp0); gamma = (lamda0() - Math.toRadians(geog.lon()))*sp0 ; east = e0() - r*Math.sin(gamma); north = r0 + n0() - r*Math.cos(gamma); double sp = Math.sin(Math.toRadians(geog.lat())); scaleFactor = Math.sqrt(1 - sph.eccsq()*sp*sp)*r*sp0/(a*Math.cos(Math.toRadians(geog.lat()))); } /** * Calculate grid convergence * @return Grid convergence (degrees) */ public double gridConvergence(){ this.calcCoords(); return Math.toDegrees(gamma) ; } /** * Define the default datum for this coordinate system * @return default datum */ public uk.co.mccombe.mapping.Datum defaultDatum() { return Datum.NTF ; } /** * Define the default Ellipsoid for this coordinate system * @return default Ellipsoid */ public uk.co.mccombe.mapping.Ellipsoid defaultEllipsoid() { return Ellipsoid.CLARKE ; } private double q(double lat) { double e = Math.sqrt(sph.eccsq()); double sp = Math.sin(lat); double res = (Math.log((1+sp)/(1-sp)) - e*Math.log((1+e*sp)/(1-e*sp)))/2.0 ; return res ; } private double w(double lat){ double e = Math.sqrt(sph.eccsq()); double sp = Math.sin(lat); double res = Math.sqrt(1 - sph.eccsq()*sp*sp); return res ; } //Define abstract methods for projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected abstract double phiU() ; //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected abstract double phiL() ; //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected abstract double phiB() ; //Latitude of false origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected abstract double lamda0() ;//Longitude of grid origin /** * Define false easting value * @return False easting (m) */ protected abstract double e0() ; //False easting /** * Define false northing * @return False northing distance (m) */ protected abstract double n0() ; //False northing // Local variables private double scaleFactor = 1.0 ; //Scale factor private double gamma = 0.0 ; //Grid convergence private double east = 0.0 ; //Easting value private double north = 0.0 ; //Northing value private static final double LIMIT = 1.0E-7 ; //Convergence limit } src/uk/co/mccombe/mapping/Lambert93.java0000777000000000000000000000640414573074222015241 0ustar /* * Lambert93.java * * Created on 24 October 2006, 22:49 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical (LCC) projection * for the French Lambert-93 system. Unlike previous French LCC implementations, this one * uses GRS80/WGS84 ellipsoid and datum by default. * * @author Mike McCombe */ public class Lambert93 extends Lambert{ /** * Create an instance of Lambert93 from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public Lambert93(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of Lambert93 from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public Lambert93(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of Lambert93 from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static Lambert93 makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); return new Lambert93(en, e, d); } /** * Define default Datum for this system * @return the default Datum (Datum.WGS_1984) */ public uk.co.mccombe.mapping.Datum defaultDatum() { return Datum.WGS_1984 ; } /** * Define the default Ellipsoid for this system * @return the default Ellipsoid (Ellipsoid.GRS80) */ public uk.co.mccombe.mapping.Ellipsoid defaultEllipsoid() { return Ellipsoid.GRS80 ; } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(49.0);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(44.0);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(46.5);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(3.0);}//Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 700000.0;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 6600000.0 ;} //False northing } src/uk/co/mccombe/mapping/LambertI.java0000777000000000000000000000616414573074222015201 0ustar /* * LambertI.java * * Created on 19 July 2005, 09:30 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical projection * for Zone 1 (Northern France between latitudes 48.15 and 51.3 degrees N) * @author Mike McCombe */ public class LambertI extends Lambert { /** * Create an instance of LambertI from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public LambertI(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of LambertI from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public LambertI(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of LambertI from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. French convention sometimes includes the zone number as the first * digit of the Y (northing) coordinate (e.g. "Y=1210.98" denoting a northing distance of 210.98km * in zone 1). If present, this is ignored. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static LambertI makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); double y = en.north(); if(y>=1000000.0 && y<2000000.0) { y -=1000000.0; double x = en.east(); en = new ENPair(x,y); } return new LambertI(en, e, d); } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(50.39591167);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(48.59852278);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(49.5);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(2.337229167);} //Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 600000 ;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 200000 ;} //False northing } src/uk/co/mccombe/mapping/LambertII.java0000777000000000000000000000620614573074222015307 0ustar /* * LambertII.java * * Created on 19 July 2005, 09:23 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical projection * for Zone 2 (Central France between latitudes 45.45 and 48.15 degrees N) * @author Mike McCombe */ public class LambertII extends Lambert { /** * Create an instance of LambertII from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public LambertII(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of LambertII from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public LambertII(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of LambertII from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. French convention sometimes includes the zone number as the first * digit of the Y (northing) coordinate (e.g. "Y=3210.98" denoting a northing distance of 210.98km * in zone 3). If present, this is ignored. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static LambertII makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); double y = en.north(); if(y>=2000000.0 && y<3000000.0) { y -=2000000.0 ; double x = en.east(); en = new ENPair(x,y); } return new LambertII(en, e, d); } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(47.69601444);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(45.89891889);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(46.8);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(2.337229167);} //Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 600000 ;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 200000 ;} //False northing } src/uk/co/mccombe/mapping/LambertIIExtended.java0000777000000000000000000000543414573074222016772 0ustar /* * LambertIIExtended.java * * Created on 15 July 2005, 18:50 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical projection * for Zone 2-extended (all of mainland France and Corsica) * @author Mike McCombe */ public class LambertIIExtended extends Lambert { /** * Create an instance of LambertIIExtended from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public LambertIIExtended(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of LambertIIExtended from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public LambertIIExtended(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of LambertII from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static LambertIIExtended makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); return new LambertIIExtended(en, e, d); } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(47.69601444);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(45.89891889);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(46.8);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(2.337229167);} //Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 600000 ;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 2200000 ;} //False northing } src/uk/co/mccombe/mapping/LambertIII.java0000777000000000000000000000620614573074222015420 0ustar /* * LambertIII.java * * Created on 19 July 2005, 09:44 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical projection * for Zone 3 (Southern France between latitudes 42.3 and 45.45 degrees N) * * @author Mike McCombe */ public class LambertIII extends Lambert { /** * Create an instance of LambertIII from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public LambertIII(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of LambertIII from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public LambertIII(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of LambertIII from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. French convention sometimes includes the zone number as the first * digit of the Y (northing) coordinate (e.g. "Y=2210.98" denoting a northing distance of 210.98km * in zone 2). If present, this is ignored. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static LambertIII makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); double y = en.north(); if(y>=3000000.0 && y<4000000.0) { y -=3000000.0 ; double x = en.east(); en = new ENPair(x,y); } return new LambertIII(en, e, d); } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(44.99609389);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(43.19929139);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(44.1);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(2.337229167);} //Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 600000 ;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 200000 ;} //False northing } src/uk/co/mccombe/mapping/LambertIV.java0000777000000000000000000000611714573074222015325 0ustar /* * LambertIV.java * * Created on 19 July 2005, 09:50 * */ package uk.co.mccombe.mapping; /** * A non-abstract class implementing the Lambert Conformal Conical projection * for Zone 4 (Corsica) * @author Mike McCombe */ public class LambertIV extends Lambert { /** * Create an instance of LambertIV from Position, Ellipsoid and Datum * @param p the position of this point * @param e The Ellipsoid to use * @param d The datum to be used */ public LambertIV(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create an instance of LambertIV from easting and northing distances, Ellipsoid and Datum * @param en Easting and Northing distances * @param e Ellipsoid to use * @param d Datum to use */ public LambertIV(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } /** * Static factory method to create an instance of LambertIV from a grid reference String * @param gridref A grid reference of the form "X=... Y=..." where the values are easting and * northing distances in km. French convention sometimes includes the zone number as the first * digit of the Y (northing) coordinate (e.g. "Y=4210.98" denoting a northing distance of 210.98km * in zone 4). If present, this is ignored. * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static LambertIV makePoint(String gridref, Ellipsoid e, Datum d)throws GridFormatException { ENPair en = getEN(gridref); double y = en.north(); if(y>=4000000.0 && y<5000000.0) { y -=4000000.0 ; double x = en.east(); en = new ENPair(x,y); } return new LambertIV(en, e, d); } // Projection constants /** * Define Upper standard parallel for this conical projection * @return Upper Standard Parallel (radians) */ protected double phiU() { return Math.toRadians(42.76766333);} //Upper parallel /** * Define lower standard parallel for this projection * @return Lower Standard Parallel (radians) */ protected double phiL() { return Math.toRadians(41.56038778);} //Lower parallel /** * Define latitude of false origin * @return Latitude of false origin (radians) */ protected double phiB() { return Math.toRadians(42.165);} //Latitude of false grid origin /** * Define longitude of grid origin * @return Longitude of grid origin (radians) */ protected double lamda0() { return Math.toRadians(2.337229167);} //Longitude grid origin /** * Define false easting value * @return False easting (m) */ protected double e0() { return 234.358 ;} //False easting /** * Define false northing * @return False northing distance (m) */ protected double n0() { return 185861.369 ;} //False northing } src/uk/co/mccombe/mapping/LatLong.java0000777000000000000000000001115614573074222015037 0ustar /* * LatLong.java * * Created on 05 July 2005, 16:24 * */ package uk.co.mccombe.mapping; /** * A simple immutable wrapper class for Latitude and Longitude values * @author Mike McCombe */ public class LatLong { /** Creates a new instance of LatLong */ public LatLong() { } /** * Create LatLong instance from specified values of Lat and Lon. * @param lat Latitude (degrees) * @param lon Longitude (degrees) */ public LatLong(double lat, double lon) { y = lat ; x = lon ; } /** * A static method to turn values of degrees, minutes and seconds into a latitude value. * @param ns "N" or "S". Points south of the equator have negative values of latitude. * @param deg Degrees. Value must not exceed 90. * @param min Minutes - zero or positive, less than 60. * @param sec Seconds - zero or positive real value less than 60.0 * @throws uk.co.mccombe.mapping.LatLongFormatException if the degrees/minutes/seconds values do not correspond to legal latitudes between * 0 and 90.0 or if ns is neither "N" nor "S" * @return value in the range -90.0 to +90.0 */ public static double latDMS(String ns, int deg, int min, double sec) throws LatLongFormatException { if(ns.length()!=1 || legalNS.indexOf(ns.toUpperCase())<0) throw new LatLongFormatException(String.format("Invalid N/S specifier <%s>",ns)); double sign = 1.0 ; if(legalNS.indexOf(ns.toUpperCase())>0) sign = -1.0 ; if ( deg>90 || deg <0) throw new LatLongFormatException(String.format("Invalid degrees value <%d>",deg)); if ( min>59 || min <0) throw new LatLongFormatException(String.format("Invalid minutes value <%d>",min)); if (sec>=60 || sec <0) throw new LatLongFormatException(String.format("Invalid seconds value <%d>",sec)); double result = sign*((double)deg + (double)min/60.0 + sec/3600.0); return result ; } /** * A static method to turn values of degrees, minutes and seconds into a longitude value. * @param ew "E" or "W". Points west of the reference meridian have negative values of longitude. * @param deg Degrees. Value must not exceed 180. * @param min Minutes - zero or positive, less than 60. * @param sec Seconds - zero or positive real value less than 60.0 * @throws uk.co.mccombe.mapping.LatLongFormatException if the degrees/minutes/seconds values do not correspond to legal longitudes between * 0 and 180.0 or if ew is neither "E" nor "W" * @return value in the range -180.0 to +180.0 */ public static double lonDMS(String ew, int deg, int min, double sec) throws LatLongFormatException { if(ew.length()!=1 || legalEW.indexOf(ew.toUpperCase())<0) throw new LatLongFormatException(String.format("Invalid E/W specifier <%s>",ew)); double sign = 1.0 ; if(legalEW.indexOf(ew.toUpperCase())>0) sign = -1.0 ; if (deg>180 || deg <0) throw new LatLongFormatException(String.format("Invalid degrees value <%d>",deg)); if ( min>59 || min <0) throw new LatLongFormatException(String.format("Invalid minutes value <%d>",min)); if (sec>=60 || sec <0) throw new LatLongFormatException(String.format("Invalid seconds value <%f>",sec)); if (deg==180 && (min!=0 || sec!=0)) throw new LatLongFormatException(String.format("Invalid minutes and seconds values <%d %f>",min,sec)); double result = sign*((double)deg + (double)min/60.0 + sec/3600.0); return result ; } /** * Get the latitude component * @return Latitude (degrees) */ public double lat() { return y ; } /** * Get the longitude component * @return Longitude (degrees) */ public double lon() { return x ; } /** * Provide a String representing this latitude and longitude. * @return The String representation of the coordinates (in Lat/Lon format) */ public String toString() { return toDMS(lat(), "NS") + " " + toDMS(lon(), "EW"); } public static String toDMS(double v, String signs) { double v1 = Math.abs(v) ; double s = Math.round(v1*36000.0) ; double sec = s % 600 ; s = Math.round((s-sec)/600.0) ; sec = sec / 10.0 ; double min = s % 60 ; double deg = Math.round((s-min)/60) ; String sign = signs.substring(0, 1) ; if(v<0) sign = signs.substring(1, 2); return String.format(java.util.Locale.UK,"%3.0f %s %2.0f\' %4.1f\"", deg, sign, min, sec); } private double x = 0.0 ; private double y = 0.0 ; private static final String legalNS = "NS"; private static final String legalEW = "EW"; } src/uk/co/mccombe/mapping/LatLongFormatException.java0000777000000000000000000000143214573074222020063 0ustar /* * LatLongFormatException.java * * Created on 02 November 2007, 17:11 * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package uk.co.mccombe.mapping; /** * An exception class for errors in Latitude and Longitude formats * @author Mike */ public class LatLongFormatException extends java.lang.Exception { /** * Creates a new instance of LatLongFormatException without detail message. */ public LatLongFormatException() { } /** * Constructs an instance of LatLongFormatException with the specified detail message. * @param msg the detail message. */ public LatLongFormatException(String msg) { super(msg); } } src/uk/co/mccombe/mapping/MappingToolkit.java0000777000000000000000000002661014573101251016432 0ustar package uk.co.mccombe.mapping; import java.lang.reflect.*; /** * MappingToolkit provides access to standard features of the mapping package by name. * It is particularly useful in a GUI environment where the user needs to be able to * select from lists of CoordinateSystems, Datums and Ellipsoids * * @author Mike McCombe */ public class MappingToolkit { /** * Construct a new MappingToolkit */ public MappingToolkit() { LatLong latlon = new LatLong(52.0, -2.0); ENPair en = new ENPair(100000.0, 100000.0); Spherical sph = new Spherical(latlon, Ellipsoid.GRS80, Datum.WGS_1984); MapEntry sp = new MapEntry(sph, "52.375, -2.70916"); coordmap.put(sp.getName(), sp); MapEntry p1 = new MapEntry(new OSGB(en, Ellipsoid.AIRY, Datum.OSGB_1936), "ST 430969"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new LambertIIExtended(en, Ellipsoid.CLARKE, Datum.NTF), "X=455.23 Y = 2302.1"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new LambertI(en, Ellipsoid.CLARKE, Datum.NTF), "X=455.23 Y = 1102.1"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new LambertII(en, Ellipsoid.CLARKE, Datum.NTF), "X=455.23 Y = 2302.1"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new LambertIII(en, Ellipsoid.CLARKE, Datum.NTF), "X=936.7 Y = 3102.5"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new LambertIV(en, Ellipsoid.CLARKE, Datum.NTF), "X=455.23 Y = 2302.1"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new Lambert93(en, Ellipsoid.GRS80, Datum.WGS_1984), "X=455.23 Y = 2302.1"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new IrishGrid(en, Ellipsoid.MODIFIED_AIRY, Datum.IRELAND_1965), "M730196"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new NZMG(en, Ellipsoid.INTERNATIONAL, Datum.NZGD_1949), "2487100 mE 6751049 mN"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new NZTM2000(en, Ellipsoid.GRS80, Datum.NZGD_2000), "2487100 mE 6751049 mN"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new UTM(en, 1, Ellipsoid.GRS80, Datum.WGS_1984), "32T 406946 5383757"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new AustrianM28(en, Ellipsoid.BESSEL, Datum.MGI), "M28 486697 83757"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new AustrianM31(en, Ellipsoid.BESSEL, Datum.MGI), "M31 486697 83757"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new AustrianM34(en, Ellipsoid.BESSEL, Datum.MGI), "M34 486697 83757"); coordmap.put(p1.getName(), p1); p1 = new MapEntry(new SloveneGrid(en, Ellipsoid.BESSEL, Datum.MGI_SLOV), "486697 83757"); coordmap.put(p1.getName(), p1); } /** * Provide the default Ellipsoid for the named CoordinateSystem class * @param classname The name of the CoordinateSystem * @return the defaultEllipsoid() for the class * @throws java.lang.IllegalArgumentException if the CoordinateSystem cannot be found */ public Ellipsoid defaultEllipsoid(String classname) throws IllegalArgumentException { MapEntry m = coordmap.get(classname); if (m == null) { throw new IllegalArgumentException(String.format("No such CoordinateSystem: %s", classname)); } CoordinateSystem c = m.getCoordinateSystem(); return c.defaultEllipsoid(); } /** * Provide the default Datum for the specified CoordinateSystem * @param classname A String containing the name of a CoordinateSystem * @return the defaultDatum() * @throws java.lang.IllegalArgumentException if the CoordinateSystem cannot be found. */ public Datum defaultDatum(String classname) throws IllegalArgumentException { MapEntry m = coordmap.get(classname); if (m == null) { throw new IllegalArgumentException(String.format("No such CoordinateSystem: %s", classname)); } CoordinateSystem c = m.getCoordinateSystem(); return c.defaultDatum(); } /** * Make a CoordinateSystem instance from a grid reference * @param name - the name of the CoordinateSystem to make * @param args - the argument list for the class's makePoint() method * @return A new CoordinateSystem instanceof the specified type * @throws java.lang.NoSuchMethodException * @throws uk.co.mccombe.mapping.GridFormatException * @throws java.lang.IllegalAccessException * @throws java.lang.IllegalArgumentException * @throws java.lang.reflect.InvocationTargetException * @throws java.lang.InstantiationException */ public CoordinateSystem makeCoordinateSystem(String name, Object... args) throws NoSuchMethodException, GridFormatException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { CoordinateSystem p = coordmap.get(name).getCoordinateSystem(); // Class c = p.getClass(); Class c = p.getClass(); Class[] classes = new Class[args.length]; for (int i = 0; i < args.length; i++) { classes[i] = unwrap(args[i].getClass()); } if (args.length == 0) { throw new NoSuchMethodException("Default constructor"); } if (args[0] instanceof String) { Method make = c.getMethod("makePoint", classes); CoordinateSystem point = (CoordinateSystem) make.invoke(null, args); return point; } else { Constructor con = c.getConstructor(classes); CoordinateSystem point = (Projection) con.newInstance(args); return point; } } /** * Create an alphabetically ordered list of CoordinateSystem names * @return The list of names */ public java.util.Vector getCoordinateSystemNames() { java.util.Vector coordList = new java.util.Vector<>(); java.util.Collection types = coordmap.values(); for (MapEntry g : types) { coordList.add(g.getName()); //Populate the List with an alphabetically-sorted list of types } return coordList; } /** * Create an alphabetically-ordered list of Projections. Since Projection is a sub-class of CoordinateSystem * this list will be a subset of that provided by getCoordinateSystemNames() * @return the list of names */ public java.util.Vector getProjectionNames() { java.util.Vector coordList = new java.util.Vector(); java.util.Collection types = coordmap.values(); for (MapEntry g : types) { if (g.getCoordinateSystem() instanceof Projection) { coordList.add(g.getName()); } } return coordList; } /** * get a list of available Datum instances * @return the list */ public java.util.Vector getDatumList() { Class datumclass = Datum.WGS_1984.getClass(); Field[] fields = datumclass.getFields(); java.util.Vector datumList = new java.util.Vector<>(); for (Field f : fields) { Class type = f.getType(); String typeName = type.getName(); if (typeName.equals("uk.co.mccombe.mapping.Datum")) { try { Datum datum = (Datum) f.get(null); datumList.add(datum); } catch (IllegalArgumentException | IllegalAccessException ex) { } } } return datumList; } /** * Get an instance of a specific Datum * @param name - the name of the Datum * @return the requested Datum instance */ public Datum getDatum(String name) { java.util.Vector list = getDatumList(); for(Datum datum : list){ if(datum.toString().equals(name)) return datum ; } return null ; } /** * Get an instance of a specific Ellipsoid * @param name - the name of the Ellipsoid * @return the requested Datum instance */ public Ellipsoid getEllipsoid(String name) { java.util.Vector list = getEllipsoidList(); for(Ellipsoid e : list){ if(e.toString().equals(name)) return e ; } return null; } /** * Get an example of a grid reference for a specified CoordinateSystem * @param classname - the name of the CoordinateSystem * @return a valid grid reference String * @throws java.lang.IllegalArgumentException if the CoordinateSystem name cannot be found */ public String getExample(String classname) throws IllegalArgumentException { MapEntry m = coordmap.get(classname); if (m == null) { throw new IllegalArgumentException("No such CoordinateSystem: " + classname); } return m.getExample(); } /** * Get a list of available Ellipsoid instances * @return the list */ public java.util.Vector getEllipsoidList() { Class ellipsoidclass = Ellipsoid.GRS80.getClass(); Field[] fields = ellipsoidclass.getFields(); java.util.Vector ellipsoidList = new java.util.Vector<>(); for (Field f : fields) { Class type = f.getType(); String typeName = type.getName(); if (typeName.equals("uk.co.mccombe.mapping.Ellipsoid")) { try { Ellipsoid e = (Ellipsoid) f.get(null); ellipsoidList.add(e); } catch (IllegalArgumentException ex) { } catch (IllegalAccessException ex) { } } } return ellipsoidList; } /** * Add a CoordinateSystem type to the toolkit * @param c a CoordinateSystem instance * @param example - a String containing a valid grid reference for this type */ public void add(CoordinateSystem c, String example) { MapEntry m = new MapEntry(c, example); coordmap.put(m.getName(), m); } private Class unwrap(Class c) { if (c == Integer.class) { return int.class; } if (c == Double.class) { return double.class; } if (c == Float.class) { return float.class; } if (c == Boolean.class) { return boolean.class; } if (c == Long.class) { return long.class; } if (c == Byte.class) { return byte.class; } if (c == Character.class) { return char.class; } if (c == Short.class) { return short.class; } return c; } private class MapEntry { public MapEntry(CoordinateSystem type, String exampleText) { coordinate = type; example = exampleText; } public String getExample() { return example; } public CoordinateSystem getCoordinateSystem() { return coordinate; } public String getName() { return coordinate.getName(); } private String example; private CoordinateSystem coordinate; } private java.util.TreeMap coordmap = new java.util.TreeMap(); } src/uk/co/mccombe/mapping/NZMG.java0000777000000000000000000001546014573074222014254 0ustar /* * New Zealand Mapping Grid - an implementation of a conformal orthomorphic projection * See http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf for a technical description * of NZMG * */ package uk.co.mccombe.mapping; /** * * @author Mike McCombe */ public class NZMG extends uk.co.mccombe.mapping.Orthomorphic { /** * Create a new NZMG point for a specific Position, Ellipsoid and Datum. * @param p Position * @param e Ellipsoid to use with this instance * @param d Datum to use */ public NZMG(Position p, Ellipsoid e, Datum d) { super(p, e, d); } /** * Create a new NZMG point for a specific pair of Eastings and Northings, Ellipsoid and Datum. * @param point ENPair containing the easting and northing values * @param e Ellipsoid to use with this instance * @param d Datum to use */ public NZMG(ENPair point, Ellipsoid e, Datum d) { super(point, e, d); } /** * Static factory method to create an instance of NZMG from a grid reference String * @param gridref A grid reference of the form "157203 mE 6752091 mN " where the values are easting and * northing distances in m * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new NZMG instance */ public static NZMG makePoint(String gridref, Ellipsoid e, Datum d) throws GridFormatException { ENPair en = getEN(gridref); return new NZMG(en, e, d); } /** * Parse NZMG coordinates into easting and northing distances * @param gridref A pair of NZMG coordinates (e.g. "2487100.638 mE 6751049.719 mN), specified in m. * @throws uk.co.mccombe.mapping.GridFormatException Invalid coordinate format results in a GridFormatException being thrown * @return Easting and Northing distances (m) */ protected static ENPair getEN(String gridref) throws GridFormatException { double xCoord = 0.0; double yCoord = 0.0; boolean gotX = false; boolean gotY = false; try { String regex = "[ \tm]+"; String[] parts = gridref.split(regex); int n = parts.length; switch (n) { case 2: { xCoord = parseDouble(parts[0]); yCoord = parseDouble(parts[1]); break; } case 4: { for (int i = 1; i < 4; i += 2) { if (parts[i].equalsIgnoreCase("E")) { xCoord = parseDouble(parts[i - 1]); gotX = true; } else if (parts[i].equalsIgnoreCase("N")) { yCoord = parseDouble(parts[i - 1]); gotY = true; } } if (gotX && gotY) { break; } } default: { throw new GridFormatException("Invalid NZMG Coordinate String"); } } } catch (java.util.regex.PatternSyntaxException e) { throw new GridFormatException("Invalid NZMG Coordinate String"); } catch (NumberFormatException e) { throw new GridFormatException("Invalid NZMG Coordinate String"); } catch (java.text.ParseException e) { throw new GridFormatException("Invalid NZMG Coordinate String"); } return new ENPair(xCoord, yCoord); } @Override public Ellipsoid defaultEllipsoid() { return Ellipsoid.INTERNATIONAL; } @Override public Datum defaultDatum() { return Datum.NZGD_1949; } /** * Calculate the Grid Convergence for the current location. Grid Convergence * is defined as the angle between Grid North and True North. The value is * zero on the Central Meridian. * * In the absence of an algebraic form for grid convergence for NZMG, this implementation reverts to * basics and calculates it by making a small increment (delta) north and south of the point and * calculating the change in easting and northing values. * * @return The value of Grid Convergence (radians) */ @Override public double gridConvergence() { LatLong here = locus.toLatLong(sph, ref); double lon = here.lon(); double lat = here.lat(); double delta = 0.01; LatLong l1 = new LatLong(lat + delta, lon); LatLong l2 = new LatLong(lat - delta, lon); Position p1 = new Position(l1, 0.0, sph, ref); Position p2 = new Position(l2, 0.0, sph, ref); NZMG n1 = new NZMG(p1, sph, ref); NZMG n2 = new NZMG(p2, sph, ref); ENPair e1 = n1.toEN(); ENPair e2 = n2.toEN(); double dn = e1.north() - e2.north(); double de = e1.east() - e2.east(); double convergence = Math.atan2(de, dn); return convergence; } @Override public String toString() { ENPair en = this.toEN(); return String.format("%7.0f mE %7.0f mN", en.east(), en.north()); } public double cfi(int i) { return cfi[i]; } public double cfl(int i) { return cfl[i]; } public double n0() { return 6023150.0; } public double e0() { return 2510000.0; } public double phi0() { return Math.toRadians(-41.0); } public double lamda0() { return Math.toRadians(173.0); } public Complex cfb1(int i) { return cfb1[i]; } public Complex cfb2(int i) { return cfb2[i]; } private static final double cfi[] = {0.6399175073, -0.1358797613, 0.063294409, -0.02526853, 0.0117879, -0.0055161, 0.0026906, -0.001333, 0.00067, -0.00034}; private static final double cfl[] = {1.5627014243, 0.5185406398, -0.03333098, -0.1052906, 0.0368594, 0.007317, 0.01220, 0.00394, -0.0013}; private static final Complex[] cfb1 = { new Complex(0.7557853228, 0.0), new Complex(0.249204646, 0.003371507), new Complex(-0.001541739, 0.041058560), new Complex(-0.10162907, 0.01727609), new Complex(-0.26623489, -0.36249218), new Complex(-0.6870983, -1.1651967)}; private static final Complex[] cfb2 = { new Complex(1.3231270439, 0.0), new Complex(-0.577245789, -0.007809598), new Complex(0.508307513, -0.112208952), new Complex(-0.15094762, 0.18200602), new Complex(1.01418179, 1.64497696), new Complex(1.9660549, 2.5127645)}; public int cfilen() { return cfi.length; } public int cfllen() { return cfl.length; } public int cfblen() { return cfb1.length; } } src/uk/co/mccombe/mapping/NZTM2000.java0000777000000000000000000001036014573074222014565 0ustar /* * New Zealand Transverse Mercator 2000 Coordinate System */ package uk.co.mccombe.mapping; /** * * @author Mike McCombe */ public class NZTM2000 extends uk.co.mccombe.mapping.TransverseMercator { public NZTM2000(Position p, Ellipsoid e, Datum d) { super(p, e, d); } public NZTM2000(ENPair en, Ellipsoid e, Datum d) { super(en, e, d); } /** * Static factory method to create an instance of NZMG from a grid reference String * @param gridref A grid reference of the form "157203 mE 6752091 mN " where the values are easting and * northing distances in m * @param e Ellipsoid to use in conversions * @param d Datum to use in conversions * @throws uk.co.mccombe.mapping.GridFormatException thrown in case of format error in the grid reference * @return A new LambertI instance */ public static NZTM2000 makePoint(String gridref, Ellipsoid e, Datum d) throws GridFormatException { ENPair en = getEN(gridref); return new NZTM2000(en, e, d); } /** * Parse NZMG coordinates into easting and northing distances * @param gridref A pair of NZMG coordinates (e.g. "2487100.638 mE 6751049.719 mN), specified in m. * @throws uk.co.mccombe.mapping.GridFormatException Invalid coordinate format results in a GridFormatException being thrown * @return Easting and Northing distances (m) */ protected static ENPair getEN(String gridref) throws GridFormatException { double xCoord = 0.0; double yCoord = 0.0; boolean gotX = false; boolean gotY = false; try { String regex = "[ \tm]+"; String[] parts = gridref.split(regex); int n = parts.length; switch (n) { case 2: { xCoord = parseDouble(parts[0]); yCoord = parseDouble(parts[1]); break; } case 4: { for (int i = 1; i < 4; i += 2) { if (parts[i].equalsIgnoreCase("E")) { xCoord = parseDouble(parts[i - 1]); gotX = true; } else if (parts[i].equalsIgnoreCase("N")) { yCoord = parseDouble(parts[i - 1]); gotY = true; } } if (gotX && gotY) { break; } } default: { throw new GridFormatException("Invalid Lambert Coordinate String"); } } } catch (java.util.regex.PatternSyntaxException e) { throw new GridFormatException("Invalid NZTM2000 Coordinate String"); } catch (NumberFormatException e) { throw new GridFormatException("Invalid NZTM2000 Coordinate String"); } catch (java.text.ParseException e) { throw new GridFormatException("Invalid NZTM2000 Coordinate String"); } return new ENPair(xCoord, yCoord); } /** * Define scale factor * @return Scale factor at central meridian */ @Override public double f0() { return 0.9996; } /** * Define latitude of true origin * @return Latitude of true origin (degrees) */ public double phi0() { return 0.0; } /** * Define false northing * @return False northing value (metres) */ public double n0() { return 10000000.0; } /** * False Eastimg (metres) * @return False easting value (metres) */ public double e0() { return 1600000.0; } /** * The central meridian * @return The central meridian (radians) */ public double lamda0() { return Math.toRadians(173.0); } /** * Define the default Ellipsoid for this system * @return Ellipsoid.GRS80 */ public Ellipsoid defaultEllipsoid() { return Ellipsoid.GRS80; } @Override public Datum defaultDatum() { return Datum.NZGD_2000; } @Override public String toString() { ENPair en = this.toEN(); return String.format("%7.0f mE %7.0f mN", en.east(), en.north()); } } src/uk/co/mccombe/mapping/Orthomorphic.java0000777000000000000000000001354214573074222016155 0ustar /* * Abstract base class for Conformal Orthomorphic projections such as NZMG * * Code here has been adapted from C code downloaded from New Zealand's LINZ mapping agency * See http://www.linz.govt.nz/geodetic/software-downloads/nzmg.zip This also contains test data. * */ package uk.co.mccombe.mapping; /** * * @author Mike McCombe */ public abstract class Orthomorphic extends uk.co.mccombe.mapping.Projection { /** * Create a new Orthomorphic instance for a location specified by * Position, Ellipsoid and Datum * * @param p A Position object defining the location * @param e The Ellipsoid used as a reference * @param d The Datum used */ public Orthomorphic(Position p, Ellipsoid e, Datum d) { super(p, e, d); LatLong geog = locus.toLatLong(sph, ref); } /** * Create an instance of Orthomorphic based on a specified ENPair * @param point An ENPair containing the Easting and Northing distances of the point * @param sphere The Ellipsoid to use * @param datum The Datum to use with this instance */ public Orthomorphic(ENPair point, Ellipsoid sphere, Datum datum) { Complex zn, zd, tmp1, tmp2; sph = sphere; ref = datum; double x = (point.north() - n0()) / a(); double y = (point.east() - e0()) / a(); Complex z0 = new Complex(x, y); int k = cfblen() - 1; Complex z1 = cfb2(k); for (int i = k - 1; i >= 0; i--) { z1 = z1.mul(z0); z1 = z1.add(cfb2(i)); } z1 = z1.mul(z0); for (int it = 1; it >= 0; it--) { zn = cfb1(k).mul(5.0); zd = cfb1(k).mul(6.0); for (int i = 4; i > 0; i--) { tmp2 = cfb1(i).mul((double) i); tmp1 = zn.mul(z1); zn = tmp1.add(tmp2); tmp2 = cfb1(i).mul((double) (i + 1)); tmp1 = zd.mul(z1); zd = tmp1.add(tmp2); } // cadd( &zn, &z0, cmult( &zn, cmult( &zn, &zn, &z1), &z1)); zn = zn.mul(z1); zn = zn.mul(z1); zn = zn.add(z0); // cadd( &zd, cfb1, cmult( &zd, &zd, &z1 )); zd = zd.mul(z1); zd = zd.add(cfb1(0)); z1 = zn.div(zd); } double lon = lamda0() + z1.i(); int imax = cfllen() - 1; double sum = cfl(imax); double tmp = z1.r(); for (int i = imax - 1; i >= 0; i--) { sum = sum * tmp + cfl(i); } sum *= tmp / 3600.0e-5; double lat = phi0() + Math.toRadians(sum); locus = new Position(new LatLong(Math.toDegrees(lat), Math.toDegrees(lon)), 0.0, sph, ref); } @Override public ENPair toEN() { LatLong geog = locus.toLatLong(sph, ref); // void geod_nzmg( double lt, double ln, double *n, double *e ) { Complex z0, z1; double lt = (geog.lat() - Math.toDegrees(this.phi0())) * 3600.0e-5; double ln = Math.toRadians(geog.lon()); int k = cfilen() - 1; double sum = cfi(k); for (int i = k - 1; i >= 0; i--) { sum = sum * lt + cfi(i); } sum *= lt; int j = cfblen() - 1; z1 = new Complex(sum, ln - lamda0()); z0 = cfb1(j); for (int i = j - 1; i >= 0; i--) { z0 = z0.mul(z1); z0 = z0.add(cfb1(i)); } z0 = z0.mul(z1); /* for (i = 9; i--;) sum = sum*lt+cfi[i]; sum *= lt; z1.real = sum; z1.imag = ln-ln0/rad2deg; z0.real = cfb1[5].real; z0.imag = cfb1[5].imag; for ( i=5; i--;) cadd(&z0,cmult(&z0,&z0,&z1),cfb1+i); cmult(&z0,&z0,&z1); *n = n0+z0.real*a; *e = e0+z0.imag*a; } */ return new ENPair(e0() + a() * z0.i(), n0() + a() * z0.r()); } @Override public abstract double gridConvergence(); protected double a() { return sph.majoraxis(); } public abstract double n0(); public abstract double e0(); public abstract double phi0(); public abstract double lamda0(); public abstract double cfi(int i); public abstract int cfilen(); public abstract double cfl(int i); public abstract int cfllen(); public abstract int cfblen(); public abstract Complex cfb1(int i); public abstract Complex cfb2(int i); public static final class Complex { public Complex(double real, double imag) { x = real; y = imag; } public Complex add(Complex c) { return new Complex(c.r() + x, c.i() + y); } public Complex sub(Complex c) { return new Complex(x - c.r(), y - c.i()); } public Complex mul(Complex c) { double rx = x * c.r() - y * c.i(); double ry = x * c.i() + y * c.r(); return new Complex(rx, ry); } public Complex mul(double r) { return new Complex(r * x, r * y); } public Complex div(double r) { return new Complex(x / r, y / r); } public Complex div(Complex c) { double bottom = c.modsq(); Complex top = this.mul(c.conjg()); return top.div(bottom); } public double modsq() { return x * x + y * y; } public double r() { return x; } public double i() { return y; } public Complex conjg() { return new Complex(x, -y); } public String toString() { return String.format("(%13.6f,%13.6f)", x, y); } private final double x; private final double y; } } src/uk/co/mccombe/mapping/OSGB.java0000777000000000000000000002315614573074222014234 0ustar package uk.co.mccombe.mapping ; import java.math.* ; /** *

* Class OSGB provides handling for the Ordnance Survey of Great Britain * coordinate system. *

*

* OSGB is a conventional Transverse Mercator coordinate system in which a standard * sphere (the "Airy 1830 sphere") is projected onto a plane. Several other well-known * coordinate systems operate in the same way (e.g. the Irish Grid and UTM) but with * origins and spheroids chosen to be most suitable to the area of use. The OSGB grid is * applicable only to Great Britain (i.e. England, Scotland and Wales but NOT Northern Ireland * or the Republic of Ireland, or the Channel Islands). *

*

* For more information about OSGB and the manipulation of Transverse Mercator coordinate systems * you could try:- *

 *      "A guide to coordinate systems in Great Britain" - Ordnance Survey of Great Britain
 *      "GDA Technical Manual" at www.anzlic.org.au
 * 
*

* @author Mike McCombe * * Simplified and tidied 21-Oct-2007 */ public class OSGB extends uk.co.mccombe.mapping.TransverseMercator { /** * Create a new OSGB point based on a grid reference. This is the preferred method of obtaining a * new OSGB object from a grid reference and for translating a grid reference into a Position. For example *
     *    
     *    try {
     *        OSGB point = OSGB.makePoint("ST755619", Ellipsoid.AIRY, Datum.OSGB_1936);
     *        Position here = point.getPosition();
     *        ...
     *    }
     *    catch(GridFormatException e){
     *        //Handle exception
     *        ...
     *    }  
     *    
     * 
* @param osReference A String containg a valid grid reference. This consists of a two-letter * grid square (e.g. "ST") followed by 1-5 digits of easting and the same number of digits * of northing. Whitespace may appear between the grid-letters and easting and between * easting and northing values. * @param e The Ellipsoid used in conjunction with this point. This is almost always the * Airy (1830) sphere. * @param d The Datum to use in conjunction with this point. This is almost always the * OSGB (1936) Datum. * @return A new OSGB object * @throws uk.co.mccombe.mapping.GridFormatException A GridFormatException is thrown whenever the grid reference provided has invalid * syntax. */ public static OSGB makePoint(String osReference, Ellipsoid e, Datum d) throws GridFormatException { ENPair point = getEN(osReference) ; return new OSGB(point, e, d); } /** * Create a new OSGB object from easting and northing distances * @param p Easting and Northing distances (m) * @param e Ellipsoid used by this point * @param d Datum used for this point. */ public OSGB(ENPair p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Create a new OSGB object using Easting and Northing distances. The Ellispoid and Datum * are the Airy Sphere and OSGB (1936) Datum. * @param p The easting and northing distances (m) */ public OSGB(ENPair p){ this(p, Ellipsoid.AIRY, Datum.OSGB_1936); } /** * Create a new OSGB object for a specific Position, Ellipsoid and Datum. * @param p the Position of this point. * @param e The Ellipsoid to use (normally Ellipsoid.AIRY) * @param d The Datum to use (normally Datum.OSGB_1936) */ public OSGB(Position p, Ellipsoid e, Datum d) { super(p, e, d); } /** * Define the defaultDatum for this CoordinateSystem * @return Datum.OSGB_1936 */ public uk.co.mccombe.mapping.Datum defaultDatum() { return Datum.OSGB_1936; } /** * Define the default Ellipsoid for this CoordinateSystem * @return Ellipsoid.AIRY */ public uk.co.mccombe.mapping.Ellipsoid defaultEllipsoid() { return Ellipsoid.AIRY; } /** * Provide a String representation for this point. * @return 10-figure (1m) grid reference, with grid square */ public String toString() { ENPair p = toEN() ; return osRef(p); } /** Convert Easting and Northing distances in metres into OS grid ref * * @return OS Grid Reference (String) * @param easting (m) * @param northing (m) */ private static String osRef(ENPair point) { double easting = point.east(); double northing = point.north(); if(!validateEN(point)) { return "" ; } // returns 10 digit reference for given easting and northing in km. // eg SO 12345 67890 long e = 1000000 + Math.round(easting) ; long n = Math.round(northing) + 500000 ; long i = e / 500000 ; long j = n / 500000 ; String l = "" ; l += t1.charAt((int)(i + 1 + j * 5)-1) ; e = e % 500000 ; n = n % 500000 ; i = e / 100000 ; j = n / 100000 ; l += t1.charAt( (int)(j * 5 + i + 1)-1) ; e = e % 100000 ; n = n % 100000 ; String ec = "" ; ec += (100000 + e); String nc = "" ; nc += (100000 + n); l += " " + ec.substring(1) + " " + nc.substring(1) ; return l ; } /** Convert OS grid reference to easting and northing values * * @param os Grid Reference (String) * @exception GridFormatException on bad OS Grid Reference * @return Pair containing the easting (x) and northing(y) distances in metres */ private static ENPair getEN(String os) throws GridFormatException { double units = 1.0 ; String northPart, eastPart, gridSquare ; String arg = os.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); java.util.regex.Matcher matcher2 = altPattern.matcher(arg); if(matcher.find()) { gridSquare = matcher.group(1); String gridOffset = matcher.group(2); int offsetLength = gridOffset.length(); if((offsetLength % 2) != 0) throw new GridFormatException("Invalid OS Grid Reference - odd number of digits"); eastPart = gridOffset.substring(0,offsetLength/2); northPart = gridOffset.substring(offsetLength/2); units = Math.pow(10.0,5-offsetLength/2); } else if(matcher2.find()) { gridSquare = matcher2.group(1); eastPart = matcher2.group(2); northPart = matcher2.group(3); if(eastPart.length()!= northPart.length()) throw new GridFormatException("Invalid OS Grid Reference - easting & northing have different lengths"); units = Math.pow(10.0,5-eastPart.length()); } else throw new GridFormatException("Invalid OS grid reference"); String firstLetter = gridSquare.substring(0,1); String secondLetter = gridSquare.substring(1,2); int i1 = (gridLetters.indexOf(firstLetter)) % 5 ; int j1 = 4 - (gridLetters.indexOf(firstLetter)) / 5 ; int i2 = (gridLetters.indexOf(secondLetter)) % 5 ; int j2 = 4 - (gridLetters.indexOf(secondLetter)) / 5 ; double eastSquare = (i1-2) * 500000.0 + i2 * 100000.0 ; double northSquare = (j1 - 1)* 500000.0 + j2 * 100000.0 ; try { double ev = Double.parseDouble(eastPart) * units + eastSquare; double nv = Double.parseDouble(northPart) * units + northSquare; return new ENPair(ev,nv); } catch (NumberFormatException bad) { throw new GridFormatException("Invalid OS Grid Reference - bad digits"); } } private static final String t1 = "VWXYZQRSTULMNOPFGHJKABCDE" ; private static final String validationRegex1 = "([HJNOST][A-HJ-Z])\\s*((\\d\\d)+)$"; private static final String validationRegex2 = "([HJNOST][A-HJ-Z])\\s*(\\d+)\\s+(\\d+)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); private static final java.util.regex.Pattern altPattern = java.util.regex.Pattern.compile(validationRegex2); private static final String gridLetters = "ABCDEFGHJKLMNOPQRSTUVWXYZ" ; /** * Define the scale factor on the central meridian * @return scale factor (0.9996012717) */ public double f0() { return 0.9996012717 ; } /** * Define latitude of true origin * @return Latitude (degrees) of the true origin (49.0) */ public double phi0() { return Math.toRadians(49.0); } /** * Define the false northing value * @return False Northing distance (-100km) */ public double n0() { return -100000.0 ; } /** * Define false easting value * @return false easting value (400km) */ public double e0() { return 400000.0 ; } /** * Define value of central meridian (degrees) * @return central meridian (2.0 W) */ public double lamda0() { return Math.toRadians(-2.0); } /** * Check that ENPair lies within the permitted range * @param p * @return true if OK ; */ protected static boolean validateEN(ENPair p){ double x = p.east(); double y = p.north(); return (x>=MIN_E && x=MIN_N && ytoString() method because a Position needs to be * part of a CoordinateSystem to have a meaningful String representation. * *

Instances of this class are immutable.

* @author Mike McCombe */ public class Position { /** * Creates a new instance of Position based on cartesian coordinates relative to the * specified Datum. * @param point XYZ coordinates of this position relative to the specified Datum * @param datum The Datum used as the basis of the cartesian coordinates */ public Position(XYZ point, Datum datum) { cartesian = datum.toWGS84(point) ; } /** * Create a new Position using Lat/Long relative to a specified Ellipsoid and Datum * @param geo LatLong of the point * @param ellipsoidHeight Height above the Ellipsoid of this point (double) * @param sphere The Ellipsoid against which this Lat and Lon are defined * @param datum The Datum used to measure the Lat and Lon */ public Position(LatLong geo, double ellipsoidHeight, Ellipsoid sphere, Datum datum){ double h = ellipsoidHeight ; double a = sphere.majoraxis(); double sp = Math.sin(Math.toRadians(geo.lat())); double cp = Math.cos(Math.toRadians(geo.lat())); double sl = Math.sin(Math.toRadians(geo.lon())); double cl = Math.cos(Math.toRadians(geo.lon())); double nu = a / Math.sqrt(1 - sphere.eccsq()*sp*sp); double x = (nu + h)*cp*cl ; double y = (nu + h)*cp*sl ; double z = ((1 - sphere.eccsq())*nu + h)*sp ; XYZ loc = new XYZ(x,y,z); cartesian = datum.toWGS84(loc); } /** * Get the cartesian coordinates of this Position relative to a specified Datum * @param datum The Datum against which to measure the Position * @return Cartesian (XYZ) coordinates of this Position */ public XYZ coords(Datum datum){ return datum.fromWGS84(cartesian); } /** * Get the LatLong of this Position relative to specified Ellipsoid and Datum * @param sphere The Ellipsoid to use as reference * @param datum The Datum to measure relative to * @return A LatLong object for this Position */ public LatLong toLatLong(Ellipsoid sphere, Datum datum){ XYZ now = datum.fromWGS84(cartesian); //This x, y, z needs to be wrt current datum double x = now.x(); double y = now.y(); double z = now.z(); double lamda = Math.atan2(y, x); double p = Math.sqrt(x*x + y*y); double phi = Math.atan(z/(p*(1-sphere.eccsq()))); double delta = 10.0; while (Math.abs(delta)>1.e-8){ double sp = Math.sin(phi); double nu = sphere.majoraxis()/Math.sqrt(1-sphere.eccsq()*sp*sp); double p2 = Math.atan((z+nu*sphere.eccsq()*sp)/p); delta = p2 - phi ; phi = p2 ; } return new LatLong(Math.toDegrees(phi), Math.toDegrees(lamda)) ; } /** * Calculate the height of this Position above the specified Ellipsoid. * @param sphere The Ellipsoid from which to calculate the height * @param datum The Datum defining the location of the Ellipsoid * @return The height in metres of this position above the Ellipsoid */ public double ellipsoidHeight(Ellipsoid sphere, Datum datum){ XYZ now = datum.fromWGS84(cartesian); //This x, y, z needs to be wrt current datum double x = now.x(); double y = now.y(); double z = now.z(); double lamda = Math.atan2(y, x); double p = Math.sqrt(x*x + y*y); double phi = Math.atan(z/(p*(1-sphere.eccsq()))); double delta = 10.0; double nu =0.0 ; while (Math.abs(delta)>1.e-8){ double sp = Math.sin(phi); nu = sphere.majoraxis()/Math.sqrt(1-sphere.eccsq()*sp*sp); double p2 = Math.atan((z+nu*sphere.eccsq()*sp)/p); delta = p2 - phi ; phi = p2 ; } return p/Math.cos(phi) - nu ; } /** * Compare this Position with another Object * @param o The Object to compare with * @return TRUE if o is an instance of Position co-located with this Position. */ public boolean equals(Object o){ if(o instanceof Position) { Position p = (Position) o ; return (this.cartesian.x()== p.cartesian.x() && this.cartesian.y()== p.cartesian.y() && this.cartesian.z()== p.cartesian.z()); } return false ; } private XYZ cartesian ; //WGS84 coordinates of this point } src/uk/co/mccombe/mapping/Projection.java0000777000000000000000000000270714573074222015615 0ustar /* * Projection.java * * Created on 05 July 2005, 17:30 * */ package uk.co.mccombe.mapping; /** * A Projection is an abstract base class for CoordinateSystems in which a a 3-dimensional * position is "projected" onto a surface to allow it to be represented in 2-dimensions. * @author Mike McCombe */ public abstract class Projection extends CoordinateSystem { /** Creates a new instance of Projection */ protected Projection() { // super(); } /* public Projection(LatLong place, Ellipsoid e, Datum d){ super(place, e, d); } @Deprecated public Projection(XYZ coords, Ellipsoid e, Datum d){ super(coords, e, d); } **/ /** * Create a new Projection for a Position. The projection uses a specified Ellipsoid and Datum. * @param p The Position * @param e Ellipsoid to use * @param d Datum to be used. */ public Projection(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Get a 2-dimensional representation of the projected position as easting * and northing distances * @return An ENPair representing the projected Position */ public abstract ENPair toEN() ; /** * Calculate Grid Convergence - the angle between the North axis * and True North at this particular point. * @return Grid convergence (radians) */ public abstract double gridConvergence(); //Grid convergence - in radians } src/uk/co/mccombe/mapping/SloveneGrid.java0000777000000000000000000000674414573074222015727 0ustar package uk.co.mccombe.mapping; /** * * @author Mike */ public class SloveneGrid extends uk.co.mccombe.mapping.TransverseMercator{ public SloveneGrid(Position p, Ellipsoid e, Datum d){ super(p, e, d); } public SloveneGrid(ENPair en, Ellipsoid e, Datum d){ super(en, e, d); } public static SloveneGrid makePoint(String gridRef, Ellipsoid e, Datum d)throws GridFormatException { String arg = gridRef.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); if (matcher.find()) { String eastingNum = matcher.group(1); String northingNum = matcher.group(2); try { double xCoord = Double.parseDouble(eastingNum); double yCoord = Double.parseDouble(northingNum); ENPair pa = new ENPair(xCoord, yCoord); return new SloveneGrid(pa, e, d); } catch (NumberFormatException ee) { throw new GridFormatException("Illegal Slovene grid format"); } } throw new GridFormatException("Invalid Slovene grid reference"); } @Override public Datum defaultDatum() { return Datum.MGI_SLOV; } @Override public Ellipsoid defaultEllipsoid() { return Ellipsoid.BESSEL;} /** * Provide a String representation of this point in Slovene coordinates * @return A Slovene coordinate String */ public String toString() { ENPair pa = toEN(); return String.format("%8.0f %8.0f", pa.east(), pa.north()); } /** * The scale factor on the Central Meridian. Generally, Transverse Mercator * projections increasingly exaggerate distances further from the central * meridian. It is usual to reduce the scale factor at the central meridian * to compensate for this effect and optimise the scale over the area of interest. * @return the value of the ScaleFactor at the central meridian. */ @Override public double f0() { return 0.9999 ; } /** * n0() defines the "false northing" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. n0() is used as an * offset to the grid so that the "true origin" appears to have a northing value * equal to n0(). * * @return The false northing distance (double) */ @Override public double n0() { return -5000000.0; } /** * phi0() defines the latitude of the true origin of the projection. * * Note, however, that many Transverse Mercator projections employ a * false origin. See n0() and e0() . * * * @return The latitude of the true origin (radians) */ @Override public double e0() { return 500000.0 ; } public double phi0() { return 0; } //Latitude of true origin ; /** * lamda0() defines the longitude (in radians) of the true origin also * known as the "Central Meridian". * @return The central meridian (radians) */ @Override public double lamda0() { return Math.toRadians(15.0); } private static final String validationRegex1 = "\\s*(\\d+\\.?\\d*)\\s*(\\d+\\.?\\d*)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); } src/uk/co/mccombe/mapping/Spherical.java0000777000000000000000000000373614573074222015416 0ustar /* * Spherical.java * * Created on 16 July 2005, 13:07 * * This is a simple spherical coordinate system using latitude and longitude to define position * */ package uk.co.mccombe.mapping; /** * Spherical Coordinates - an implementation of positional coordinates based on * Latitude and Longitude. Whilst often not explicitly stated in practice, these * are relative to a specified datum and ellipsoid. * @author Mike McCombe */ public class Spherical extends CoordinateSystem { /** Creates a new instance of Spherical */ protected Spherical() { } /** * Create a new Spherical Coordinate set based on Lat/Lon, the Ellipsoid and Datum * @param latLon A LatLong object containing the Latitude and Longitude of the point * @param e The Ellipsoid used to define Lat and Lon * @param d The Datum used to determine the Lat / Lon */ public Spherical(LatLong latLon, Ellipsoid e, Datum d){ super(new Position(latLon, 0.0, e, d), e, d); } /** * Create a new Spherical Coordinate set based on a specific Position, Ellipsoid and Datum * @param p The Position * @param e The Ellipsoid to use when translating this Position * @param d The Datum to use when representing this point. */ public Spherical(Position p, Ellipsoid e, Datum d){ super(p, e, d); } /** * Define the DefaultDatum for this coordinate set * @return The Default Datum */ public Datum defaultDatum() { return Datum.WGS_1984 ; } /** * Define the Default Ellipsoid for this point * @return The Default Ellipsoid */ public Ellipsoid defaultEllipsoid() { return Ellipsoid.GRS80 ; } /** * Provide a String representing this coordinate set * @return The String representation of the coordinates (in Lat/Lon format) */ public String toString() { LatLong geog = locus.toLatLong(sph, ref); return geog.toString(); } } src/uk/co/mccombe/mapping/TransverseMercator.java0000777000000000000000000002444714573074222017337 0ustar /* * TransverseMercator.java * * Created on 05 July 2005, 16:14 * */ package uk.co.mccombe.mapping; /** *

TransverseMercator is an abstract base class for coordinate systems * derived from the Transverse Mercator Projection. Methods are provided * for standard conversions between Lat/Lon and Easting/Northing *

* *

Concrete sub-classes derived from TransverseMercator need to provide values for * the projection constants f0(), phi0(), n0(), e0() and lamda0() (see below). *

* @author Mike McCombe */ public abstract class TransverseMercator extends Projection { /** * Default constructor for TransverseMercator */ protected TransverseMercator(){ n = (sph.majoraxis()-sph.minoraxis())/(sph.majoraxis() + sph.minoraxis()); } /** * Create a new TransverseMercator instance for a location specified by * Position, Ellipsoid and Datum * * @param p A Position object defining the location * @param e The Ellipsoid used as a reference * @param d The Datum used */ public TransverseMercator(Position p, Ellipsoid e, Datum d){ super(p, e, d); n = (sph.majoraxis()-sph.minoraxis())/(sph.majoraxis() + sph.minoraxis()); LatLong geog = locus.toLatLong(sph, ref); northernHemisphere = geog.lat() > 0.0 ; } /** * Create an instance of TransverseMercator based on a specified ENPair * @param point An ENPair containing the Easting and Northing distances of the point * @param sphere The Ellipsoid to use * @param datum The Datum to use with this instance */ public TransverseMercator(ENPair point, Ellipsoid sphere, Datum datum){ this(point, 30, sphere, datum, true); } /** * Create an instance of TransverseMercator based on a specified ENPair * @param sphere The Ellipsoid to use * @param z UTM Zone number * @param datum The Datum to use with this instance * @param point An ENPair containing the Easting and Northing distances of the point * @param hemisphere true if the point is in the Northern hemisphere */ protected TransverseMercator(ENPair point, int z, Ellipsoid sphere, Datum datum, boolean hemisphere){ zone = z ; sph = sphere ; ref = datum ; northernHemisphere = hemisphere ; n = (sph.majoraxis()-sph.minoraxis())/(sph.majoraxis() + sph.minoraxis()); double E = point.east() ; double N = point.north(); double a = sph.majoraxis(); double b = sph.minoraxis(); double phiDash = (N - n0())/(a*f0()) + phi0(); double dp = phiDash ; double sp = Math.sin(dp); double cp = Math.cos(dp); double tp = Math.tan(dp); double tpsq = tp*tp ; double err = N - n0() - M(dp) ; while (Math.abs(err) > 0.001) { err = (N - n0() - M(dp)) ; dp +=err / (a*f0()); } sp = Math.sin(dp); cp = Math.cos(dp); tp = Math.tan(dp); tpsq = tp*tp ; double nu = sph.majoraxis()*f0()/Math.sqrt(1.0 - sph.eccsq()*sp*sp); double rho = sph.majoraxis()*f0()*(1.0-sph.eccsq())/Math.pow((1.0 - sph.eccsq()*sp*sp),1.5); double etasq = nu/rho -1.0 ; double q7 = tp / (2.0*rho*nu); double q8 = tp * (5.0 + 3.0*tpsq + etasq - 9.0*tpsq*etasq)/ (24.0*rho*nu*nu*nu); double q9 = tp * (61.0 + 90.0*tpsq + 45.0 * tpsq*tpsq) / (720.0*rho*Math.pow(nu, 5)); double q10 = 1.0 / (nu*cp); double q11 = (nu/rho + 2.0*tpsq)/(cp * 6.0 * nu*nu*nu); double q12 = ( 5.0 + 28.0 * tpsq + 24.0 * tpsq * tpsq)/(cp*120.0*Math.pow(nu,5)); double q12a = (61.0 + 662.0*tpsq + 1320.0*tpsq*tpsq + 720.0 * tpsq * tpsq * tpsq)/(5040.0 * Math.pow(nu,7) * cp); double de = E - e0(); double phi = dp - q7 * de*de + q8 * Math.pow(de, 4) - q9 * Math.pow(de, 6); double lamda = lamda0() + q10 * de - q11 * Math.pow(de, 3) + q12 * Math.pow(de, 5) - q12a * Math.pow(de, 7); locus = new Position(new LatLong(Math.toDegrees(phi), Math.toDegrees(lamda)), 0.0, sph, ref); } /** * Provide an ENPair containing Easting and Northing distances for this point * @return an ENPair */ public ENPair toEN() { LatLong geog = locus.toLatLong(sph, ref); double sp = Math.sin(Math.toRadians(geog.lat())); double cp = Math.cos(Math.toRadians(geog.lat())); double tp = Math.tan(Math.toRadians(geog.lat())); double tpsq = tp*tp ; double nu = sph.majoraxis()*f0()/Math.sqrt(1.0 - sph.eccsq()*sp*sp); double rho = sph.majoraxis()*f0()*(1.0-sph.eccsq())/Math.pow((1.0 - sph.eccsq()*sp*sp),1.5); double etasq = nu/rho -1.0 ; double dp = Math.toRadians(geog.lat()); double q1 = M(dp) + n0(); double q2 = nu*sp*cp/2.0 ; double q3 = (nu/24.0)*sp*cp*cp*cp*(5.0-tpsq+9.0*etasq); double q3a = (nu/720.0)*sp*Math.pow(cp,5)*(61.0-58.0*tpsq+tpsq*tpsq); double q4 = nu*cp ; double q5 = (nu/6.0)*cp*cp*cp*((nu/rho)-tpsq); double q6 = (nu/120.0)*Math.pow(cp, 5)*(5.0 - 18.0*tpsq + tpsq*tpsq + 14.0*etasq-58.0*tpsq*etasq); double dl = Math.toRadians(geog.lon())-lamda0() ; if(dl>Math.PI) dl -= 2.0*Math.PI ; if(dl< -Math.PI) dl += 2.0*Math.PI ; double N = q1 + q2*Math.pow(dl,2)+q3*Math.pow(dl, 4) + q3a*Math.pow(dl, 6); double E = e0() + q4*dl + q5*Math.pow(dl, 3)+ q6*Math.pow(dl, 5); return new ENPair(E, N); } /** * Calculate the Grid Convergence for the current location. Grid Convergence * is defined as the angle between Grid North and True North. The value is * zero on the Central Meridian. * * @return The value of Grid Convergence (radians) */ public double gridConvergence(){ LatLong geog = locus.toLatLong(sph, ref); double lat = geog.lat(); double lon = geog.lon(); double sl = Math.sin(Math.toRadians(lat)); double cl = Math.cos(Math.toRadians(lat)); double rho = sph.majoraxis()*(1.0-sph.eccsq())*Math.pow(1.0-sph.eccsq()*sl*sl, -3/2); double nu = sph.majoraxis()*Math.pow(1.0-sph.eccsq()*sl*sl,-1/2); double psi = nu/rho ; double t = Math.tan(Math.toRadians(lat)); double omega = Math.toRadians(lon) - lamda0() ; double term1 = -omega*sl; double term2 = -(Math.pow(omega,3)/3.0)*sl*cl*cl*(2*psi*psi-psi); double term3 = -(Math.pow(omega,5)/15.0)*sl*Math.pow(cl,4)*(Math.pow(psi,4)*(11-24*t*t)-Math.pow(psi,3)*(11-36*t*t)+2*psi*psi*(1-7*t*t)+psi*t*t); double term4 = -(Math.pow(omega,7)/315.0)*sl*Math.pow(cl,6)*(17-26*t*t+2*Math.pow(t,4)); return term1 + term2 + term3 + term4 ; } /** * Calculate the Point Scale Factor for the current Position. This is f0() on the central meridian. * * @return The Point Scale Factor */ public double pointScaleFactor(){ LatLong geog = locus.toLatLong(sph, ref); double lat = geog.lat(); double lon = geog.lon(); double sl = Math.sin(Math.toRadians(lat)); double cl = Math.cos(Math.toRadians(lat)); double rho = sph.majoraxis()*(1.0-sph.eccsq())*Math.pow(1.0-sph.eccsq()*sl*sl, -3/2); double nu = sph.majoraxis()*Math.pow(1.0-sph.eccsq()*sl*sl,-1/2); double psi = nu/rho ; double t = Math.tan(Math.toRadians(lat)); double omega = Math.toRadians(lon) - lamda0() ; double term1 = (omega*omega/2.0)*psi*cl*cl; double term2 = (Math.pow(cl*omega,4)/24.0)*(4.0*Math.pow(psi,3)*(1.0-6.0*t*t)+psi*psi*(1.0+24.0*t*t)-4.0*psi*t*t); double term3 = (Math.pow(cl*omega,6.0)/720.0)*(61.0-148.0*t*t+16.0*Math.pow(t,4.0)); return f0()*(1.0 + term1 + term2 + term3) ; } private double n = 0.0 ; private double M(double dp){ double v = sph.minoraxis()*f0()*( (1.0 + n + 5.0*n*n/4.0 +5.0*n*n*n/4.0)*(dp - phi0()) -(3.0*n + 3.0*n*n + 21.0*n*n*n/8.0)*Math.sin(dp-phi0())*Math.cos(dp+phi0()) +(15.0*n*n/8.0 + 15.0*n*n*n/8.0)*Math.sin(2.0*(dp-phi0()))*Math.cos(2.0*(dp+phi0())) -35.0*n*n*n*Math.sin(3.0*(dp-phi0()))*Math.cos(3.0*(dp+phi0()))/24.0); return v ; } protected boolean northernHemisphere = true ; protected int zone = 30; /** * The scale factor on the Central Meridian. Generally, Transverse Mercator * projections increasingly exagerate distances further from the central * meridian. It is usual to reduce the scale factor at the central meridian * to compensate for this effect and optimise the scale over the area of interest. * @return the value of the ScaleFactor at the central meridian. */ public abstract double f0() ; //Scalefactor on central meridian /** * phi0() defines the latitude of the true origin of the projection. * * Note, however, that many Transverse Mercator projections employ a * false origin. See n0() and e0() below. * * * @return The latitude of the true origin (radians) */ public abstract double phi0() ; //Latitude of true origin (radians) /** * n0() defines the "false northing" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. n0() is used as an * offset to the grid so that the "true origin" appears to have a northing value * equal to n0(). * * @return The false northing distance (double) */ public abstract double n0(); //Northing of true origin /** * e0() defines the "false easting" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. e0() is used as an * offset to the grid so that the "true origin" appears to have an easting value * equal to e0(). * * @return The false easting distance (double) */ public abstract double e0(); //Easting of true origin /** * lamda0() defines the longitude (in radians) of the true origin also * known as the "Central Meridian". * @return The central meridian (radians) */ public abstract double lamda0(); //Longitude of true origin and central meridian } src/uk/co/mccombe/mapping/UTM.java0000777000000000000000000002042114573074222014137 0ustar /* * UTM.java * * Created on 13 July 2005, 11:50 * */ package uk.co.mccombe.mapping; /** * The Universal Transverse Mercator coordinate system * @author Mike McCombe */ public class UTM extends TransverseMercator { /** * Create a new instance from Position, Ellipsoid and Datum. The UTM zone is calculated * automatically from the Position's longitude (relative to the specified Ellipsoid and Datum). * @param p Position * @param e Ellipsoid to use * @param d Datum to use */ public UTM(Position p, Ellipsoid e, Datum d) { super(p, e, d); LatLong geo = p.toLatLong(e, d); zone = getZone(geo.lon()); } /** * Create a new instance of UTM from Position, UTM zone, Ellipsoid and Datum. * @param p Position * @param z The UTM zone number * @param e Ellipsoid to use * @param d The Datum for this point */ public UTM(Position p, int z, Ellipsoid e, Datum d) { super(p, e, d); zone = z; } /** * Create a new UTM object from easting and northing values, zone number, Ellipsoid and Datum * @param en Easting and northing values (m) * @param z Zone number (0-59) * @param sphere Ellipsoid for this point * @param datum Datu for this point * @param hemisphere - true if point is in the NOrthern Hemisphere */ public UTM(ENPair en, int z, Ellipsoid sphere, Datum datum, boolean hemisphere) { super(en, z, sphere, datum, hemisphere); zone = z; } /** * Create a new UTM object from easting and northing values, zone number, Ellipsoid and Datum * @param en Easting and northing values (m) * @param z Zone number (0-59) * @param sphere Ellipsoid for this point * @param datum Datu for this point */ public UTM(ENPair en, int z, Ellipsoid sphere, Datum datum) { super(en, z, sphere, datum, true); } /** * This constructor is provided for test purposes only (because it has a common interface with * other sub-classes of Projection). * Its use is @deprecated Use the UTM(ENPair, int, Ellipsoid, Datum) instead. * @param en Easting and northing values (m). Zone number is assumed to be 0. * @deprecated */ @Deprecated public UTM(ENPair en) { super(en, Ellipsoid.GRS80, Datum.WGS_1984); zone = 0; } /** * Static factory method to create a UTM instance from a grid reference String * @param gridRef Grid reference String e.g. "32T 31577 202576" * @param e Ellipsoid for this instance * @param d Datum for this instance * @throws uk.co.mccombe.mapping.GridFormatException if the grid rference String is invalid * @return A new instance of UTM for the specified point. */ public static UTM makePoint(String gridRef, Ellipsoid e, Datum d) throws GridFormatException { String arg = gridRef.toUpperCase().trim(); java.util.regex.Matcher matcher = pattern.matcher(arg); if (matcher.find()) { String zoneNumber = matcher.group(1); String zoneLetter = matcher.group(2); String eastingNum = matcher.group(3); String northingNum = matcher.group(4); boolean hemisphere = latZones.indexOf(zoneLetter) >= 10 ; try { int z = Integer.parseInt(zoneNumber); double xCoord = Double.parseDouble(eastingNum); double yCoord = Double.parseDouble(northingNum); ENPair pa = new ENPair(xCoord, yCoord); return new UTM(pa, z, e, d, hemisphere); } catch (NumberFormatException ee) { throw new GridFormatException("Illegal UTM format"); } } throw new GridFormatException("Invalid UTM grid reference"); } /** * Get the default datum for this type of CoordinateSystem * @return The default Datum (WGS84) */ public uk.co.mccombe.mapping.Datum defaultDatum() { return Datum.WGS_1984; } /** * Get the default Ellipsoid for this CoordinateSystem * @return Default Ellipsoid (GRS80) */ public uk.co.mccombe.mapping.Ellipsoid defaultEllipsoid() { return Ellipsoid.GRS80; } /** * Provide a String representation of this UTM point in UTM coordinates * @return A UTM coordinate String */ public String toString() { ENPair pa = toEN(); /* java.text.DecimalFormat fm1 = new java.text.DecimalFormat("00"); java.text.DecimalFormat fm2 = new java.text.DecimalFormat("000000"); String s1 = fm1.format(zone); String s2 = fm2.format(pa.east()); String s3 = fm2.format(pa.north()); return s1 + " " + s2 + " " +s3 ; */ return String.format("%02d%1s %8.0f %8.0f", zone, latZone(), pa.east(), pa.north()); } /** * A static method to calculate the correct zone number for a specified longitude. * @param lon Longitude (degrees) * @return Zone number (0-59) */ public static int getZone(double lon) { int z = 31 + (int) Math.round((lon - 3) / 6); return z; } /** * Get the zone number of this point * @return Zone number (0-59) */ public int getZone() { return zone; } /** * Check if this point is in the northern hemisphere * @return true if this point is north of the equator */ public boolean getNorthernHemisphere(){ return northernHemisphere ; } /** * The scale factor on the Central Meridian. Generally, Transverse Mercator * projections increasingly exagerate distances further from the central * meridian. It is usual to reduce the scale factor at the central meridian * to compensate for this effect and optimise the scale over the area of interest. * @return the value of the ScaleFactor at the central meridian. */ public double f0() { return 0.9996; } //Scale factor on central meridian /** * e0() defines the "false easting" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. e0() is used as an * offset to the grid so that the "true origin" appears to have an easting value * equal to e0(). * * @return The false easting distance (double) */ public double e0() { return 500000; } //Easting of true origin /** * n0() defines the "false northing" distance of the projection. False origins * are usually used with TM projections to ensure that easting and northing * distances are always positive over the area of interest. n0() is used as an * offset to the grid so that the "true origin" appears to have a northing value * equal to n0(). * * @return The false northing distance (double) */ public double n0() { if (!northernHemisphere) { return 10000000.0; } return 0; } /** * phi0() defines the latitude of the true origin of the projection. * * Note, however, that many Transverse Mercator projections employ a * false origin. See n0() and e0() . * * * @return The latitude of the true origin (radians) */ public double phi0() { return 0.0; } //Latitude of true origin ; /** * lamda0() defines the longitude (in radians) of the true origin also * known as the "Central Meridian". * @return The central meridian (radians) */ public double lamda0() { //Longitude of true origin double deg = (zone - 31.0) * 6 + 3.0; return Math.toRadians(deg); } private String latZone() { String res = " "; LatLong geog = locus.toLatLong(sph, ref); double lat = geog.lat(); int lz = (int) lat ; if (lz > -80 && lz < 84) { int j = (lz + 80) / 8; res = latZones.substring(j, j + 1); } return res; } private static final String latZones = "CDEFGHJKLMNPQRSTUVWXX"; private static final String validationRegex1 = "(\\d\\d)\\s*([C-HJ-NP-X])\\s*(\\d+\\.?\\d*)\\s*(\\d+\\.?\\d*)$"; private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(validationRegex1); } src/uk/co/mccombe/mapping/XYZ.java0000777000000000000000000000376214573074222014175 0ustar /* * XYZ.java * * Created on 13 July 2005, 13:05 * */ package uk.co.mccombe.mapping; /** * A simple immutable class for 3D cartesian coordinates * @author Mike McCombe */ public class XYZ { /** Creates a new instance of XYZ */ public XYZ() { } /** * Create a new XYZ coordinate * @param x The X component * @param y The Y component * @param z Th Z component */ public XYZ(double x, double y, double z) { vec3[0] = x; vec3[1] = y; vec3[2] = z; } /** * Get the X component * @return The X component */ public double x() { return vec3[0]; } /** * Get the Y component * @return The Y component */ public double y() { return vec3[1]; } /** * Get the Z component * @return The Z component */ public double z() { return vec3[2]; } /** * Provide a hashcode for this XYZ based on the values contained. This is * necessary to meet the contract for the Object.hashcode() method which * requires that if object1.equals(object2) their hashcodes must also be equal. * * @return hashcode */ @Override public int hashCode() { int hash = 7 * 47; if (vec3 != null) { for (double v : vec3) { long z = Double.doubleToLongBits(v); hash = (int) ((hash+z) * 43); } } return hash; } /** * Compare this XYZ with another Object * * @param obj - the Objecty to compare to. * @return true if obj is another XYZ containing the same values. They are not * necessarily the same object. */ @Override public boolean equals(Object obj) { if (!(obj instanceof XYZ)) { return false; } XYZ xyz = (XYZ) obj; return (this.vec3[0] == xyz.vec3[0] && this.vec3[1] == xyz.vec3[1] && this.vec3[2] == xyz.vec3[2]); } double[] vec3 = {0.0, 0.0, 0.0}; } src/uk/co/mccombe/terrain/0000777000000000000000000000000014573074316012642 5ustar src/uk/co/mccombe/terrain/AboutDialog.form0000777000000000000000000003433214573324357015734 0ustar
src/uk/co/mccombe/terrain/AboutDialog.java0000777000000000000000000003116314573324357015711 0ustar /* * AboutDialog.java * * Created on 20 January 2008, 21:51 */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class AboutDialog extends javax.swing.JDialog { /** Creates new form AboutDialog * @param parent the parent frame * @param modal boolean value specifying whether or not this dialog box is modal */ public AboutDialog(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { jLabel1 = new javax.swing.JLabel(); jButton1 = new javax.swing.JButton(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); versionLabel = new javax.swing.JLabel(); jLabel5 = new javax.swing.JLabel(); buildDate = new javax.swing.JLabel(); jLabel7 = new javax.swing.JLabel(); jLabel8 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); jLabel9 = new javax.swing.JLabel(); jLabel10 = new javax.swing.JLabel(); jLabel11 = new javax.swing.JLabel(); jLabel12 = new javax.swing.JLabel(); jLabel6 = new javax.swing.JLabel(); javaVersionLabel = new javax.swing.JLabel(); jLabel13 = new javax.swing.JLabel(); userLabel = new javax.swing.JLabel(); jLabel14 = new javax.swing.JLabel(); javaVendorLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/uk/co/mccombe/terrain/images/About.png"))); // NOI18N jButton1.setText("Close"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); jLabel2.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel2.setText("Terrain Tool"); jLabel3.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel3.setText("Version:"); versionLabel.setFont(versionLabel.getFont()); versionLabel.setText("1.0"); jLabel5.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel5.setText("Date:"); buildDate.setText("13 Jul 2009"); jLabel7.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel7.setText("Author:"); jLabel8.setText("Mike McCombe"); jLabel4.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel4.setText("E-mail:"); jLabel9.setText("mike@mikemccombe.co.uk"); jLabel10.setText("This software is distributed under the terms of the \n"); jLabel11.setText("GNU General Public Licence v3"); jLabel12.setText("Copyright 2008 - 2024 Mike McCombe"); jLabel6.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel6.setText("Java Version:"); javaVersionLabel.setText("8_40"); jLabel13.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel13.setText("User Home:"); userLabel.setText("C:\\Fred"); jLabel14.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N jLabel14.setText("Runtime"); javaVendorLabel.setText("Anyone"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 267, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(36, 36, 36) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(layout.createSequentialGroup() .addComponent(jLabel13, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(userLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 171, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel3) .addComponent(jLabel4) .addComponent(jLabel6) .addComponent(jLabel14) .addComponent(jLabel5) .addComponent(jLabel7)) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(30, 30, 30) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(buildDate) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(versionLabel) .addComponent(jButton1, javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(jLabel9, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(javaVersionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addComponent(jLabel8))) .addGroup(layout.createSequentialGroup() .addGap(29, 29, 29) .addComponent(javaVendorLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 170, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addComponent(jLabel2) .addComponent(jLabel10) .addComponent(jLabel11) .addComponent(jLabel12)) .addContainerGap(38, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, 334, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) .addComponent(versionLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel5) .addComponent(buildDate)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel7) .addComponent(jLabel8)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel9) .addComponent(jLabel4)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(javaVersionLabel) .addComponent(jLabel6)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel14) .addComponent(javaVendorLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel13) .addComponent(userLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jLabel10) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel11) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel12) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jButton1))) .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed setVisible(false); }//GEN-LAST:event_jButton1ActionPerformed /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { AboutDialog dialog = new AboutDialog(new javax.swing.JFrame(), true); dialog.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } /** * setVersion - Set the Version ID String in the Dialog box * @param ver - version ID */ public void setVersion(String ver){ versionLabel.setText(ver); } /** * setDate - Set the build date in the Dialog box * @param date - build date */ public void setDate(String date){ buildDate.setText(date); } /** * setJavaVersion * @param javaVersion -- java version string */ public void setJavaVersion(String javaVersion){ javaVersionLabel.setText(javaVersion); } /** * setJavaRuntime * @param javaVendor -- java vendor string */ public void setJavaRuntime(String javaVendor){ javaVendorLabel.setText(javaVendor); } /** * setJavaVersion * @param userHome - the user's home directory */ public void setUserHome(String userHome){ userLabel.setText(userHome); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel buildDate; private javax.swing.JButton jButton1; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel10; private javax.swing.JLabel jLabel11; private javax.swing.JLabel jLabel12; private javax.swing.JLabel jLabel13; private javax.swing.JLabel jLabel14; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; private javax.swing.JLabel jLabel7; private javax.swing.JLabel jLabel8; private javax.swing.JLabel jLabel9; private javax.swing.JLabel javaVendorLabel; private javax.swing.JLabel javaVersionLabel; private javax.swing.JLabel userLabel; private javax.swing.JLabel versionLabel; // End of variables declaration//GEN-END:variables } src/uk/co/mccombe/terrain/CoordinateDialog.form0000777000000000000000000001454614573074754016761 0ustar
src/uk/co/mccombe/terrain/CoordinateDialog.java0000777000000000000000000002064314573074754016732 0ustar /* * CoordinateDialog.java * * Created on 19 January 2008, 16:46 */ package uk.co.mccombe.terrain; import uk.co.mccombe.mapping.*; import java.awt.event.ActionEvent; import javax.swing.DefaultComboBoxModel; /** * * @author Mike */ public class CoordinateDialog extends javax.swing.JDialog { /** Creates new form CoordinateDialog */ public CoordinateDialog(java.awt.Frame parent, boolean modal, MappingToolkit toolbox) { super(parent, modal); initComponents(); coordSet.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent evt) { coordSetActionPerformed(evt); } }); toolkit = toolbox ; java.util.List coordList = toolkit.getProjectionNames(); coordSet.setModel(new DefaultComboBoxModel(coordList.toArray(new String[0]))); java.util.List datumList = toolkit.getDatumList(); datumSet.setModel(new DefaultComboBoxModel(datumList.toArray(new Datum[0]))); java.util.List ellipsoidList = toolkit.getEllipsoidList(); ellipsoidSet.setModel(new DefaultComboBoxModel(ellipsoidList.toArray(new Ellipsoid[0]))); } int getReturnStatus() { return returnStatus; } void setSelectedCoordType(String currentCoordType) { if (currentCoordType != null) { coordSet.setSelectedItem(currentCoordType); } } private void coordSetActionPerformed(ActionEvent evt) { String selected = (String) coordSet.getSelectedItem(); Ellipsoid e = toolkit.defaultEllipsoid(selected); ellipsoidSet.setSelectedItem(e); Datum d = toolkit.defaultDatum(selected); datumSet.setSelectedItem(d); } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); coordSet = new javax.swing.JComboBox(); datumSet = new javax.swing.JComboBox(); ellipsoidSet = new javax.swing.JComboBox(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); okButton.setText("OK"); okButton.setPreferredSize(new java.awt.Dimension(65, 23)); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); jLabel1.setText("Coordinate System"); jLabel2.setText("Datum"); jLabel3.setText("Ellipsoid"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1) .addComponent(jLabel2) .addComponent(datumSet, 0, 380, Short.MAX_VALUE) .addComponent(jLabel3) .addComponent(ellipsoidSet, 0, 380, Short.MAX_VALUE) .addComponent(coordSet, 0, 380, Short.MAX_VALUE) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(cancelButton) .addGap(18, 18, 18) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1) .addGap(11, 11, 11) .addComponent(coordSet, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(datumSet, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jLabel3) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(ellipsoidSet, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(cancelButton)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); pack(); }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed String coordSystemName = (String) coordSet.getSelectedItem(); doClose(RET_OK); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { CoordinateDialog dialog = new CoordinateDialog(new javax.swing.JFrame(), true, new MappingToolkit()); dialog.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } public String getExample() { String typeName = (String) coordSet.getSelectedItem(); return toolkit.getExample(typeName); } public String getProjection() { return (String) coordSet.getSelectedItem(); } public Datum getDatum() { return (Datum) datumSet.getSelectedItem(); } public void setDatum(Datum d){ datumSet.setSelectedItem(d); } public Ellipsoid getEllipsoid() { return (Ellipsoid) ellipsoidSet.getSelectedItem(); } public void setEllipsoid(Ellipsoid e){ ellipsoidSet.setSelectedItem(e); } private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } public static final int RET_OK = 1; public static final int RET_CANCEL = 0; private int returnStatus = RET_CANCEL; // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JComboBox coordSet; private javax.swing.JComboBox datumSet; private javax.swing.JComboBox ellipsoidSet; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JButton okButton; // End of variables declaration//GEN-END:variables private MappingToolkit toolkit ; } src/uk/co/mccombe/terrain/DataFileException.java0000777000000000000000000000041314573074222017032 0ustar /** * DataFileException - Thrown after errors in handling height data files */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class DataFileException extends Exception{ DataFileException(String msg){ super(msg); } } src/uk/co/mccombe/terrain/DefaultPathnames.java0000777000000000000000000000327014573130163016730 0ustar /* * Define default path names for the terraintool.properties file and the data cache. This * class is platform-agnostic. Use an os-specific implementation of Pathnames interface to * force files into os-dependent locations. See for example DebianPathnames. */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class DefaultPathnames implements Pathnames { @Override public String propertiesPath() { if (isUnix()) { if (CONFIGHOME != null) { return CONFIGHOME + SEP + "terraintool" + SEP; } else { return HOME + SEP + ".config" + SEP + "terraintool" + SEP; } } return HOME + SEP + SUBDIR + SEP; //If not Linux variant, assume to be Windows } @Override public String dataPath() { if (isUnix()) { if (DATAHOME != null) { return DATAHOME + SEP + "terraintool" + SEP; } else { return HOME + SEP + ".local" + SEP + "share" + SEP + "terraintool" + SEP; } } return HOME + SEP + SUBDIR + SEP ;//If not Linux variant, assume to be Windows } protected boolean isUnix() { String os = System.getProperty("os.name"); return os.toLowerCase().startsWith("linux"); } private static final String HOME = System.getProperty("user.home"); private static final String SEP = System.getProperty("file.separator"); private static final String SUBDIR = ".terraintool"; private static final String CONFIGHOME = System.getenv("$XDG_CONFIG_HOME"); private static final String DATAHOME = System.getenv("$XDG_DATA_HOME"); } src/uk/co/mccombe/terrain/DefaultProperties.java0000777000000000000000000000262414573074222017151 0ustar /* * DefaultsProperties.java * * Created on 12 May 2006, 10:59 * */ package uk.co.mccombe.terrain; /** * * @author Mike McCombe */ public class DefaultProperties extends java.util.Properties { /** Creates a new instance of DefaultProperties */ public DefaultProperties() { super(); // this.setProperty("region", "Eurasia"); this.setProperty("demURL","https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_HGT.001/2000.02.11/"); this.setProperty("autodownload", "false"); this.setProperty("coordinatesystem", "OSGB"); this.setProperty("currentgridref", "ST 100600"); this.setProperty("example", "ST 700600"); this.setProperty("ellipsoid", "Airy Sphere 1830"); this.setProperty("datum", "Ordnance Survey of Great Britain 1936"); this.setProperty("eastoffset", "0.0"); this.setProperty("northoffset", "0.0"); this.setProperty("heightoffset", "0.0"); this.setProperty("e-w_range", "1000.00"); this.setProperty("n-s_range", "1000.0"); this.setProperty("spacing", "50.0"); this.setProperty("alignment","0"); this.setProperty("latitude","52.0"); this.setProperty("longitude","-2.0"); this.setProperty("locale", "GB"); this.setProperty("useASTER", "nasa"); this.setProperty("therionCoordinateSet","OSGB"); } } src/uk/co/mccombe/terrain/DEMReader.java0000777000000000000000000004436014573075016015245 0ustar package uk.co.mccombe.terrain; import uk.co.mccombe.mapping.*; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.*; import java.net.URISyntaxException; import java.util.zip.*; import javax.swing.JComponent; import javax.net.ssl.HttpsURLConnection; /** * DEMReader - an abstract base class for readers of various forms of Digital Elevation Model data sets * * @author Mike McCombe */ public abstract class DEMReader extends PropertyChangeSupport { protected DEMReader(JComponent item) throws DataFileException { super(item); PropertyChangeListener[] listeners = item.getPropertyChangeListeners(); for (PropertyChangeListener ear : listeners) { addPropertyChangeListener(ear); } addPropertyChangeListener(listener); item.addPropertyChangeListener(listener); DIRECTORY = TerrainFrame.paths.dataPath(); File dir = new File(DIRECTORY); if (!dir.isDirectory()) { boolean madeDirectory = dir.mkdir(); if (!madeDirectory) { throw new DataFileException(String.format("Failed to create data directory %s%n", DIRECTORY)); } } try { File readMeTxt = new File(DIRECTORY + "ReadMe.txt"); if (!readMeTxt.isFile()) { PrintWriter readMe = new PrintWriter(new FileWriter(readMeTxt)); readMe.printf( "This directory contains copies of raw compressed data files downloaded from NASA. TerrainTool will re-use the files it needs%n" + "instead of downloading them again, saving a lot of time. However, you can save disk space by manually deleting any files you%n" + "no longer need.%n%n" + "Data is read in zipped form. DO NOT DECOMPRESS THESE FILES."); readMe.close(); } } catch (IOException ex) { //Do nothing if there's an error writing the ReadMe file } } protected String makename(String ns, double lat, String ew, double lon) { return String.format(formatstring(), ns, (int) lat, ew, (int) lon); } /** * Calculate terrain height at a specified location using biaxial cubic Lagrangian * interpolation on surrounding points in the tile. This method provides an exact fit * at the points defined in the tile and a slightly smoothed surface in between. * To speed the calculation up, rows of values from the tile are cached. * * @param place -- the LatLong of the place to calculate the height for. * @return -- the calculated height at place. * @throws MissingDataFileException * @throws DataFileException */ public double getHeight(LatLong place) throws MissingDataFileException, DataFileException { double lat = Math.floor(place.lat()); double lon = Math.floor(place.lon()); if(lat>=maxLat()){ throw new DataFileException(String.format("Latitude beyond the northern limit (%4.1fN) of the dataset",maxLat())); } if(lat= 0 && j > xtile - 3) { int v = page.getValue(j); if (v != missingValue()) { x[i] = (double) j; h[i] = (double) v; i++; } else { missing++; } j--; } if (i == 3) { java.awt.geom.Point2D.Double p0 = new java.awt.geom.Point2D.Double(x[0], h[0]); java.awt.geom.Point2D.Double p1 = new java.awt.geom.Point2D.Double(x[1], h[1]); java.awt.geom.Point2D.Double p2 = new java.awt.geom.Point2D.Double(x[2], h[2]); y[m] = lagrangian(x0, p0, p1, p2); w[m] = (double) k; m++; } k++; } if (m == 3) { java.awt.geom.Point2D.Double q0 = new java.awt.geom.Point2D.Double(w[0], y[0]); java.awt.geom.Point2D.Double q1 = new java.awt.geom.Point2D.Double(w[1], y[1]); java.awt.geom.Point2D.Double q2 = new java.awt.geom.Point2D.Double(w[2], y[2]); double height = lagrangian(y0, q0, q1, q2); resultcount++; return height; } return MISSING; } protected CacheEntry getRow(String name, int ytile) throws MissingDataFileException, DataFileException { tries++; cycle++; String shortname = String.format("%s#%04d", name, ytile); if (cacheEnable) { CacheEntry page = cache.get(shortname); if (page != null) { hits++; page.setLastUsed(); return page; } } // Record not in cache or cache not enabled try { String filename = DIRECTORY + name + extn(); File infile = new File(filename); if (!infile.isFile()) { String tempname = name + extn(); try { downloadFile(tempname); } catch (IOException | java.security.KeyManagementException | java.security.NoSuchAlgorithmException | java.net.URISyntaxException e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n", tempname)); } } in = new java.util.zip.ZipInputStream(new FileInputStream(infile)); String entryname = ""; do { ZipEntry entry = in.getNextEntry(); if(entry==null) throw new DataFileException(String.format("ZIP file %s does not contain expected entry %s",filename,zipEntryName(name))); entryname = entry.getName(); } while (!entryname.equalsIgnoreCase(zipEntryName(name))); int recordno = 0; try { int[] heights; while (true) { heights = readRecord(); if (recordno == ytile) { break; } recordno++; } if (cacheEnable) { if (cache.size() >= MAX_CACHE_SIZE) { //Find the oldest entry and remove it CacheEntry oldest = null; long age = cycle; for (CacheEntry test : cache.values()) { if (test.lastUsed() < age) { age = test.lastUsed(); oldest = test; } } String key = oldest.getName(); cache.remove(key); } CacheEntry page = new CacheEntry(shortname, heights); cache.put(shortname, page); return page; } CacheEntry page = new CacheEntry(shortname, heights); return page; } catch (EOFException e) { throw new MissingDataFileException("Hit end of file"); } } catch (IOException e) { throw new MissingDataFileException("Unable to read file - " + e.toString()); } } /** * Read the next record from the current ZipInputStream * * @return int[] buffer of length recordlength() containing the decoded next record; * @throws IOException */ public int[] readRecord() throws IOException { byte[] buffer = new byte[recordlength() * 2]; int[] outbuffer = new int[recordlength()]; int sofar = 0; //Keep reading until we have the whole record while (sofar < recordlength() * 2) { int res = in.read(buffer, sofar, recordlength() * 2 - sofar); if (res == -1) { break; } sofar += res; } for (int i = 0; i < recordlength(); i++) { short temp; if(2 * i < sofar) { if (littleendian()) { temp = (short) (buffer[2 * i + 1] << 8 | (0xff & buffer[2 * i])); } else { temp = (short) (buffer[2 * i] << 8 | (0xff & buffer[2 * i + 1])); } outbuffer[i] = temp; } else { outbuffer[i] = missingValue(); } } return outbuffer; } /** * Read the next byte record from the current ZipInputStream * * @return byte[] buffer of length recordlength() containing the decoded next record; * @throws IOException */ public byte[] readByteRecord() throws IOException { byte[] buffer = new byte[recordlength()]; int sofar = 0; //Keep reading until we have the whole record while (sofar < recordlength()) { int res = in.read(buffer, sofar, recordlength() - sofar); if (res == -1) { break; } sofar += res; } return buffer; } /** * Calculate the number of the element in the buffer that refers to a particular lat or lon value * * @param x the double lat/lon value * @return A double value representing the buffer element index */ public double tile(double x) { double q = Math.floor(x); double r = (recordlength() - 1) * (x - q); return r; } /** * Calculate the proportion of a degree represented by a particular element in the buffer * * @param tile the integer number of the element * @return double number representing a fraction of a degree */ public double frac(int tile) { double x = (double) tile / (double) (recordlength() - 1); return x; } /** * Download a specific file and sand save it in scratch space on the local machine * * @param filename the name of the file to download * @throws java.security.KeyManagementException * @throws java.security.NoSuchAlgorithmException * @throws IOException * @throws MissingDataFileException * @throws java.net.URISyntaxException */ protected void downloadFile(String filename) throws java.security.KeyManagementException, java.security.NoSuchAlgorithmException, IOException, MissingDataFileException, URISyntaxException { String sitename = getDownloadSiteName(); if ( !downloadable() || sitename==null) { String msg = String.format("TerrainTool needs a data file %s", filename ); throw new MissingDataFileException(msg); } String urlString = getDownloadSiteName() + "/" + filename; java.net.URI uri = new java.net.URI(urlString); java.net.URL url = uri.toURL(); setMessage(String.format("Downloading data: %s", filename)); HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); InputStream ins = con.getInputStream(); String outfilename = DIRECTORY + filename; File outfile = new java.io.File(outfilename); DataOutputStream outstream = new DataOutputStream(new FileOutputStream(outfile)); int bytecount = 0; long sofar = 0; long guess = 900000; while (bytecount != -1) { byte[] buffer = new byte[BUFFERLENGTH]; bytecount = ins.read(buffer); if (bytecount > 0) { outstream.write(buffer, 0, bytecount); sofar += bytecount; int progress = (int) (100 * sofar / guess); setProgress(progress); setMessage(String.format("Downloading data: %s", filename)); } } outstream.close(); ins.close(); setMessage(""); } /** * CacheEntry -- A class used to store a row of values */ protected class CacheEntry { /** * Create a CacheEntry object which contains a named row of the current tile. * * @param name The name of the row in the form tileName#rownumber> e.g. "NASADEM_HGT_N51W003#1034" * @param buffer an integer buffer of length recordlength() containing the heights for the specified row. */ public CacheEntry(String name, int[] buffer) { page_name = name; last_used = cycle; data = buffer; } /** * */ public void setLastUsed() { last_used = cycle; } /** * getName() Get the name of the CacheEntry * * @return String the name of this CacheEntry */ public String getName() { return page_name; } /** * * @param i int the index value of the item in the row to fetch * @return int the value of the specified element */ public int getValue(int i) { return data[i]; } /** * Find when this CacheEntry was last referenced * @return long the cycle number when the entry was last referenced */ public long lastUsed() { return last_used; } private long last_used; private final String page_name; private final int[] data; } /** * Fit an exact polynomial to a set of points and return its value at a specified point * @param x double the value at which to evaluate the calculated polynomial * @param points a set of java.awt.geom.Point2D.Double values * @return double value of the polynomial */ public static double lagrangian(double x, java.awt.geom.Point2D.Double... points) { int n = points.length; double tot = 0.0; for (int i = 0; i < n; i++) { double prod = 1.0; for (int j = 0; j < n; j++) { if (j != i) { prod *= (x - points[j].getX()) / (points[i].getX() - points[j].getX()); } } tot += prod * points[i].getY(); } return tot; } public long hits() { return hits; } public long resultcount() { return resultcount; } public long tries() { return tries; } public long missing() { return missing; } public void resetCounts() { hits = 0; tries = 0; missing = 0; resultcount = 0; } protected void setMessage(String msg) { if (!msg.equals(lastMessage)) { firePropertyChange("message", lastMessage, msg); } lastMessage = msg; } protected void setProgress(int val) { int v = Math.min(100, val); v = Math.max(0, v); if (lastValue != v) { firePropertyChange("progress", lastValue, v); } lastValue = v; } protected String getProperty(TerrainProperties propertyName) { return TerrainFrame.properties.get(propertyName); } @Override public PropertyChangeListener[] getPropertyChangeListeners() { PropertyChangeListener[] res = new PropertyChangeListener[1]; res[0] = listener; return res; } private PropertyChangeListener listener = new java.beans.PropertyChangeListener() { @Override public void propertyChange(java.beans.PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); String propertyValue = evt.getNewValue().toString(); /* if (propertyName.equalsIgnoreCase("download")) { download = propertyValue.equalsIgnoreCase("true"); } */ if (propertyName.equalsIgnoreCase("message")) { lastMessage = propertyValue; } } }; public abstract String datasetName(); public abstract boolean downloadable(); public abstract int recordlength(); public abstract String formatstring(); public abstract String extn(); public abstract boolean littleendian(); public abstract int missingValue(); public abstract String[] copyright() ; protected abstract void setDownloadSiteName(String s) ; protected abstract String getDownloadSiteName(); protected abstract String eastWest(double lon); protected abstract String northSouth(double lat); public abstract double maxLat(); //Maximum latitude for dataset public abstract double minLat(); //Minimum latitude for dataset public String zipEntryName(String name){ return name + ".hgt"; } public String numEntryName(String name){ return ""; } protected static java.util.zip.ZipInputStream in; protected String DIRECTORY = ""; protected long cycle = 0; protected java.util.HashMap cache = new java.util.HashMap<>(); protected boolean cacheEnable = true; protected static final int MAX_CACHE_SIZE = 16; protected long hits = 0; protected long tries = 0; protected long missing = 0; protected long resultcount = 0; protected static final double MISSING = -32768.0; protected static final int BUFFERLENGTH = 1024; protected String lastMessage = ""; protected int lastValue = 0; protected static final java.util.Locale LOCALE = java.util.Locale.UK; //Force use of UK locale for number formatting protected boolean useLegacy = false ; protected String[] copyright = {"Copyright statement is missing"}; } src/uk/co/mccombe/terrain/images/0000777000000000000000000000000014573074273014111 5ustar src/uk/co/mccombe/terrain/images/About.png0000777000000000000000000007224014163163576015701 0ustar ‰PNG  IHDRú§"±&gAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<PLTEw¨ãwcF‰´å:sÅx¤Û8Ec¦šŽLTm…f7ÙÛä‰tV•xG—™¦yc8TŒÖu[6¨‘niU5Z’Ú‹rH)4Mˆvf¥ˆP54%ÆÄËwgV”¼å†‡–˜”™·´ºI‚ΜQ§¤©&)$“zUǵ¤+\²gXDL‡Ó‰„ˆ¼ÄÕ›£¶èéðA{ÍÕÕÞ!M¤±£•˜‡wH9%¬³ÇSˆÍ464š†ftX(YF(†zvyvwvˆ¬UJ3vy‡{ƒ–3i½®ÅÝ#Qª)(¦Åãuje•ЇVd‹0aµdJ%IE4kR)cšÞhfh´ª¦jÞ–³VUXªºÕ5KfhvByÆ1e¹‚jA†Œ£DFGŒ”§ÌËÕ)Y®'U¬Yasu|—ÍÎÛ(LZTGÄÇÔIC(³µÄƒªÜ[R7´ºÍ¢£q\B&:j§­ÄdZS„lT˜©Éc–ÖkcXzrj»½Ìju•‚kJ‘{cks‰jQ»ºÃ«­¼5mà ,#Q¦‚y".’‹+14ÅÌÛƒ|†¢¤³7m½jbHFW‚’“ck…EKX$+3·¾ÓšºÞqžØkšÕŠ˜³SKD¤ª¼³®µ-/(,a¶aL2‰lA¬«³ÎÓÞžÂæ„c,kqy_–ÝĽĽÃÎKRZ®²½ÏÕä“r=\.µ»Åsms3id@Tr¥›‰¤ÊÆËÖ;q¾ÓÅ´ÑÏ×|oXK¢o¢áYBp<®â¢¦½a’Ñàâë<>4i|¥¤ª´`]b±¶ÌAuÄ»±«‹mK/,!вÞv–ß}J\`Woc-]µÑÊÌŽg\O)PMR©¦±…öžwF}ËxdKoK&gžáàßæ}q“lEl5»¶Á$q^QŸ±Î`m‘ÌÇÑ vM2’¤:=F}oL$Y®?ro.+5MOH}€~àÛÞŒd>‚[>‹dI€°æ“­ÒaO@w_!&F}ÇIž\Ñh–ÑÀÇÛ4i¶eš×NN5°®Â(QªÊÏßqoU„°Ü×Órp–‹ ,òq&IDATxÚbøë?®‡"(Ðý¨ $ À=ÁGLæGùEê#”3pÀ`Œˆá#Ì¿`ÿ­×]O‚o«Àʬ‚ @YÉ ¸°€‡ ­‡@1€¢âÝõ8=\‡0CùPÁ*΀1u‘t"™‚l"á!ˆ#˜ÉN8(¢P¸þ#@1¬'¿`/€ŒÆÆC?±*Ò…º(Båb(E æÔݦkoŸ è åÈ«fÒEH¯ ºf™ÉÕÆ»Õ$…ãkƒ*:œÐæçtÅaØÌðߌZ¬›ŠÕ?ü`“ ®AÚ9%›çVHbñP!èûpyü·îw;.I6þ©™'-ãºññ3.MhQ7¢rSp“'¢±"ú‚S˜ZŒO&tíÇÿN¼ˆî_ˆ?‘ýK_ð“x˜B¥À€U`˜²*ù, a @1 | öøC¸9¡8*£ÀÌ„ÃHFâF¡C¦ÆD!3à¬Hêg$Œª`Y  {D?yD€Üúì92A²æ( )À`ìHP°  –à„„^$Du #%øŠÂqý §ÁäaƒA"¦+œBø&ƒ€A öc{ì øÿ3¸·Ë¨Ñ4)âñÄ&^ùÍ?íTt¬ót_h·ÚÜq`†×uWÍwØO ‰(Lý)Á=ÈvzM¬@z6ÿ"¹˜>à+2„¾"T4}… ‰¯¶p ñ"DÌŠ@©¯0ž+”‡Cp0"ðy¸B6Ù6™¶M FS˜mj‚1°@ @á6Ù¢òà|[lÚAFC€é¯06Ôæ¯ÈX@Єæ¤ð \þúè/ †M¿U@ X ‚ˆ€0¨­ Â!ìÀ–€(Ìÿ¶p_7¡ V Xˆ@RËW€\QA êÙýŸLEnuH\s«[kõ`w´Èãj†v?ýÝ"Ø‚Ú,9£ƒH ÿ²Õ’¨Š5!Y®ß†ÃaÄ@ ˆµ…x³ F3È… ‚¹WW0£ÂÕÌ f¸ÂÕè„AÂC$æj0&4¡rš°ÊCÅa¡€Ä`[÷uØ0OBÁj,üÕ0&*=r ÊÑ«÷ Ÿ=Ø‚  Ä_’½º‚÷ x0±=p‰=H… AHJQAô( fAE÷ °èê=`¼Ì€ñ |¨0ö@ † ˆ+ðú/ˆˆp@bFP̨Ü=`Ä ÃA(ÁÝV‚é1„¡ ÆÁ {öÀ‚ˆ@@®^ @ X}‹ð€6Ä Ú{´Lí   Ò0„B‚v” ! ìb$!©”HlŒ‡@0RÐЀ¦P`ޤ óÈiË dÈA„†gá|l¦IßYìzÀb(:ázåäà6"éø#>!ˆsž”#ˆųÚÐx{R[„`V`'Àà†P˜`Ä€H¯„,QcäÔ \˜9Y ‚B#2ŒÈ79(G@,aÊa²>‰ Jyà4 ¹ÄŽ]9pCYhþEòU©Ái#ÇÁs`"v ‡Î–CcC<#99€b@ŽT¤x%ø !±±‰A0DÀÈ!äðý†¸—Š¡(—ψrN0 ˆoÕÔpz—Õ %øPY¸E(ŸŒ@$ˆøツ@Øè8`>ƒæØGŸˆ€bÀéa¯R „ÈOOÁˆOŠ@l(ñ BÀDÀ!c„ @”:Ä ˆ3† ûB…@‚Py|H¨BŸ"Ö'¸„ý AB}¶~"!°p!€b€Æ-n Á|JUð ÆØ ÁHs¡nâån`¥0<ÀÄ£­OKˆv¯ áòì,(9 B AB`ŒD€@.• $sA „™; ’¶‚(T€  ¡ ‡$,ˆÙ»8<‹æÜ ‰ò'’w¾Íò„ø&ü „ss¿åzÂ!}ƒP ô-÷ÑeƒpÔן!Äg0ã3˜ A¶‚"€bàÃŒ`HŒ¢ûÕBzžPDø 1€è„ù ´ˆÂo߀L8Ñãéó {€|„0p‚Ï@!?÷ 1?c@<@ (¾…î[¨OÑ}äƒ/‚)BÁb¡˜4Ð EÝ>A¨ÎéC(ô(Œ3q† $(\™£iè@ B˜±‹{=}.ýÆ!칋 o]„{Hn€úzŒ a!»ÁT7ˆµÄéq€¾a0¹Á$è ê ÐÛ§oú§jht~é ¸Q||Áž{ô"Ø—."HL’ ÅÇP. „€Ân(‰tCBˆn °½~ÐòϞŠ_Rëuúû’wAq ñ)˜ÚÁäùŽäxˆ0 „r€„f€°@j`xœ†!Ë6<óÒxÒxÏŸ_¯ÑéŸêŸ™@  Ô ÷-ØpqNß2€2À)(c" œ@â@ø,Ép±d • ‚,pL¡• ‚°’å å޶˜{;ç$õ&¥jüš? è÷Nÿœ€b'j o}pÅÌwdÍ %0J&ƒ †Í@j3ˆÝœ ›“ ( '€ùüôi Á!òòéƒ@Þi–ÓmÂCC} ¦ÍO˜Ï Œò/¿~}éôï4Ì \¾Åí_ ŸlfØ !6C ‰Ä`hH†àd<dûyeò›ä7oVökÞ¼És3³\A³qgæMCÿÔNÃ4žFž%½šáó~ýo4ÔèœÆ9þ´_õ_¾|éƺáM€b@ò.¿¾¡×@¿ñæ×`G½†ã×p6Œ„ ï5Tþ5x½ùÀ뛡F@& ³˜€Þ=ô#qà@2H$yåÊÞpͤ‰9³¯•Í–¸-q{­ÎóKÏutÖê\­m—¶]Ò‰˜í-­®Î3ï×Tß„„rÃ/ _æOÓøò¥þ‹ÆÎÝ7 3»7C< $€Îe€øîúׯ! 01„@p€ € €qAäA„úøXÍÍ?·bî©7X„ö®|_yÐ2|gÁ섵:::œÏ999/q>_Ëyi›––Ö¶m  tÊ’NIódÌI¨OmÔøµ*A#á‹Fýü_ Ÿ×¼áÍ›9„%!oyoh¾dzð%æïR $Àtˆ”sºt½9lii™&“&ã\«nžo®®.n¹¢qBABDìÚµœ òÿ¥K ?#èqß9Ëúw.¾ÝØX~ª׿ê2·h¬ú[?sZý´i@Ÿktjh|é¼™© @ ¤ BÐøÜŒ¡¸¼*…M+<Æ º˜¤º€Ô«Kª Ht¼ BtuÝÌñÖÁ½Ñì~ê§&(Ô¶Ö:7x-Q¿ç;;66""6VèkPŒƒRö%°ß·A"~šD£´´tãœÛII†Ióv×O«ßm¨±Û°˜Ëè×ÔÝÀŒ¾ûfjg*@1ü9½µ`3á‰Z)˜$Ÿ^ ä9H…ƒt!2‚Ò Æô®›<¾á¾ ]\\¬¥ÔÕe®ÝŽÐ‘Mk.—ˆ˜¦ Œèi ¸ÁmOƒé:‹Cïñþ˜7§  5uNjRjý´/_V­ZÕ ÌÞ©_8åû—ÎTÿ݆™vØÖƒôcë?ɯÅÏëØý õ-ØÙ+WxŒ½Óá÷d¤¤CO¿!ìQ ˜òÔÓBY ÓÐ_34æ^Ì?›Â[-m ê 2nßúUGâšlAlDB,0ÎQ®3m0™ƒ²9ºf}®sJ:†GóGyÿ⤤Ԃ9©sæ$ ¶Î9À¼­‘ª±;3µ~þü_¿æ×wîöu),ô]@ 2 3—Ì<–”ÁÍ7 ËwN•w*ÉðÔ© ׿)È3à’’œé@ô!Aè‚]Ó§#‹uA¥»!*ñÝÏóï±MŒML‰uÃãuçÖ4ÙNε³'ÈÊN(‹X›‘° èup”“û6 䀙:?îÅ„û6þØTžš´¸@£>!X¤Ù ÊÙ »û;¿€:°b›6-5uwNoŒM¯@1ð¬h ½À~…ù=ávù§^»¶¸¼¼L"¡ éZÁœ2 C…´4™ÖQóÐ?½o¦Ž O‚Èéþ@ù` Dþ@ÖM_…´Ûlll,€@ÝûÚmçœ:·¯•OýqjžlÂÚUà”Êåö3Ðë:—8Wsxè=ÍÆÆÝ‹9¼Üé «€©»Xu&ìÎìÔøÕ ô;0öë¿îþӻŷ°Í €edVKOßÞ•X³jWWêbÙ²¤¤²9 ©åó’nGHHDHL8ô½‚Lµ¹—×MGˆ¿ ÐÄúÒ @즑?ˆ„v`Á®p™4놘 PoM“x¾“3v7Ͻ{<[“ êW­ŠÕ‰­OX5_'a0ÁÇÆÎ&{ +b-g–P›˜^iÍr Ê¤Ì¤y©@Œ÷ú `©¶;5ØMëü¥a˜¹í†[nÝúÓöØ € Ëwžjä‘iU*½o@IW ‘h¥üý§–;–À⢼\vÎíµÓÖ=?Û[!íÔ5Ù†Ú4g‹üðtoÚÁit´³ƒŠø;Ú6áKAA}¯X `z.˜ð#AbÎî-Bc[ÛÜ ¥þ9ýó€%RyÒ<ß-±_ €5xR¿Fj§ï–Ô-šÀöm*0ïì6´ mqÄ4£Àd<µ1÷æ<7Á%¿a’ìì¿ÓnGÄ›Nõ«bW%¤–E€*ÔÙ½Þ{=_û÷60þêÌN«vVÏ·prºiG ð‡àÞkué{÷x¢]b ]ÚÔ€}möp˘˜Â °±¹ç›̺sRSc§k& ìLøb˜ÙŸÖXþcNAøŸpi ÏcBy|ïù6jôoI2l,O*O–ô«| 5¾ÌIýaXÞi£*ç¾dnf†·’¢-Í †yIeIó€hêÔòF`É%ÓZ]mÞÚ¸xN0f#¦%¬V¡L`_€¨i@Ÿ¯•˜}líí½ÇŽ=¿keõôjÙdÌÌ—, µ¹IØ¿  Òîf!ûãi`?ºíqht›ËãÖ‚kee§Bc,c, o=ê l­óúª_I»b; 4R33AÕ–F_ gCÿü‘•n°Yqf£ocj„oR90î“5§jò6îÕÌä ÍôÍŒ±Ìܽ¥‹¡æŽðÿ–fÌú< €æ”-.+˜P6¯¬|BãÔòSi izÇ€=‚U ŠèkN`»’Ô×&=þüÒ¥ˆÛמkqJxŸÒ“aÓK»Ì¦䈨“×®“ÒèžïC úµÓâkÝËÞØ }Oæž4kt¨‹Kû㩲Cc oí¸åRcÊôÏÔ$`îNÈôMJÈÔlÌ4ÌÜ¢ÉcSÎã ÊÀDî˳¥œGÚÆÆ0I³?©?ͼ=ŒµÍÆ×…ݺ-ƒ•Ý%:àVèÃ~ÍÞp__-6·â*+oµdõ۫ækÿ9À ˜³ø°U85;Ø ,ö"°RYj6ƒªµ/zEÇþ[˲iµÞiN@_×^½,zèw'‹™åPÿг¨à&!vù²²F?¶––æ)É`çqikÉ”ñM³ik+ù½-&œÑOñh–'$tvfþù“¹ÅR³×·×÷OL 0‹÷†ÚØë´Ð{ÀzàÞ–S 2Ñ.Ò6<67ž,aŸYî{‹5ð¾ÈÒ¥"KY[Z ¿üð-쵉IÝò/úÑ#‘B— €byûÚœ À&ƒ„„DAÙ5 `ïÚLÏu¦KÝm—ž_Z»v-¸ƒjF(°Ózþê#’@ï‹‹9â•·Ä+ÕîQWa 'ç&BÀÊÓÿÜŠfm&wéVÖ{…%-ÑÒ÷¤ÛZÚn³¹ÍŸÐPÐC¹ïÔ‚Ý«Rû}{ÿøþ±üóg·oaôŽ?1À c#}‡˜Ç}}w–sj5îXÜß/Ýàçdκôþ}vM`û¨ f©H†H@@k‹ïbß[š¡.16ì‹*oùj¶, †¤²ÛI§®IÄ®1è `wÓ Fâ6NPIÒ„Š=¦“p{-çZ‰c:Û€}c ÇE/_>"™ÄéFö‘#ÅÀˆurrZŠê_X òòéÓZîXîÂÎÊøØæ°b ˆ³vi+Ì`‘¾ÅîRôz!(Þ ÿ=ŠÐþ‚‚„9™š¾š¾–á,-}鸥Ð:£å_ILLè½piéþ<á¾+¾¬ Õ¶>R'¤µfˆdeÛX]ÒZ2D–fd°ºdÞséot‰ÆyË­Þ€¥ÄÐ(;§Q¶@BgÐïÀ}m8iC:@Ö4 Ç9çÛѳegËÊFHÜN˜*[&;»LBë6Û^™Ë¢¢’ÅÎê^’N¢ ˜=’ŸïäúæÙ•+O¯ ô²åòå;v,_¾¼%€U„=ú±º4°cÍúˆ=€Ýe){a {4ÐçàäîrëÖŽP`Üßóõ=•´*ØñÜâë ìÍ…þñ½'"àS"ÂSÒzXØinIÚ’dè+ÃSb>U6-â^Kkt[ ««u;ûÒ€Œ `X°Úhf´5Dßô(Ä0užtÚì²2‰µ·­P.×Ñv €1¾ví*`‡!6b°SP®0aªÂNõ2²IlêÞiæNÕæ@KU_âd^ ò<È÷¢ù~3-‚€åVË­;Œ,-wìû\ZXYnb–ímì ÒÑqKÎŒæÆhö6`õô÷­˜`ÿXÌݦäÔ¤úˆ„Ã-[4?­yO$# º½…ÕEú^ÚŠBߤ¤Fž-e¾ý§N©³††¶í²i”ÕI˜ýOX[²Ç‰”ìbú’˜â3¢C¥ÿùÅ=ªŒ[šòy@1¨·>kðÞ)3aŽÂì‚Ù:À®sû/§8›ëöó€]|NÎÎ å¡^3ÕeVX§¥ñxÁ’%æ­—[ÍÕ%$³œ¼œœ€ñ ò;;eùÁüé²ü–K °êâýô÷}‘Œû¬¬»K?Îð³~̾”•5£%.ƒ= èë–[@ßûÞÊp¹åbShczØ›3-¶s÷–ÌLÍÞ^Ͷ¥"-ÑÿZ\ ¥cÂË®5žòMZ¬ÃËïõ§­h´Žn‰Ž±imL{…@ž¤¤äøG¼NöåC˜ä%–8ÍE¯K ÐV éòSáWö V‘8`´³?¶ ̸ÿؘQ°t)kk´KtLhL[a‹µe@ °„) µÉ´a—OÐ)¶Æ23;·Ø°>Š‹c-ŒñegïOâáÙ}*¼üTyR£oÒ¼ÆÆ†Ð_³ï±—x±¯x¼ÄfExÄ¿?m…ì""K[¢ÛZD€¥¼ˆ{Û þ ‘ÊJ`Rˆ †uÕê2fÏK›ª #sŠGa‚°8{öìc6ìa~"ÛÞE‹„…Ý¿?wnûý°§ÖíOçzYD‡?m¿_×QW·=ìd__ßÛ>Ió¹a'³üž.qrÊøùÑ- ’õ~†È} [D€é}îãè¹ìaÐÆžLó쬬· à cÚ€ Ä×·%šuW u´´FDa‰Mj}Bï–[4“l–>y=#½½¤¼,”çG/ODARcãT`C´N쥴è]-ì3[y¤Û¶üÖlȈc/a h‰«©¬¬ÉxlÉ€âüQee\\@1áâ:ç-S­ïì\Ûª àœl³OГ}Ýn\g\w?ì¾È}ãûasž>Ùž÷¿$ èacã°ÃOç†ß÷»ÿÔÏ/Ì=l{Øýía»Âžß7 ËÆþ‘ƒ¨è:Éu¿ã¿W5z¿ø¸Rq1׺¼Ã~à¸ú]œà¾é Ôu¹Àðhße½kæ3ÙˆØS2<3çÝ[WçwøþöíÀt cæ{`ù *Ø€Žg`Ô8 â׬B`xtÜûêÀ!JuÀP¢)àÊüý»•û÷ïñªŠŽ³ýºÙë²³¹¸$‡=,b|”†ÁqÞÌ"À¢èœí3-žY·¦ñÌ<f\rñDã:Eźí}aAÉœ<€Ìú@_/Ç9¨t•6••• `ˆf@%Ðï@#@E0„‚@¤4ÆÑ…+lÂ=TöPöh ,o”î?Ú_½%6-ØžY|[AAvh4ú˜ìTÎç«$Êl™7uÎ`w/5VKëq4;+°oTh雓™¹»3äé ÿ3ˆáøáíÛÝãÝÝS¸Ög¯“ä*e”\W$YÄu\4˜ã€ÞÅ)(Æë 16ó~X °„36V¬¥`uL쨛Ø1QˆëÜÁ|cØ~”µAñ\'ôù£¸°Ç+\ÉTÖ} ò)Œ ÈÅšš‰ÆÆ"íÑ¡<<§¦6¦­h±¶Ñl o¸×š–:§Q³üZÁ‚©À®GlAY„DÚÔÛÓ€=*Ù²s´¶I$%ê_µ (õkÚ/`G«Øç»µØöÏ4ìÔHØ}ºØ¢zi@1èË<Ëÿ]mñ4%½™±HRÕ]µ¹¹9ý¼äº§‰Œ\¢ÖÅߟËÁŸlØi ,ú 2uàÈJ‹× º‰ÀXŸäLœò<(\Œ·ƒñ#‘û Ÿ×|ŽkV`¡W @>³À¾ïPìP¶$¶·Ï´6 ½¬Í-fZ7XÔ¦ÉÊžR—á)¿}OzçµÞµ³¯]›#1‡óv=(™Ï^¥Å¹ Øž³M+bNYÁœ‚µÓ¦Å{Ý๸۠NOïÍ/©©_bc}[öžÞá²|@1\Ö“vœíÙ:U+ÆøíÛ·oO|û 9EõéDw÷¾ÆuñâÛAEvÝ“º:`ŸÌ6w»qÇDpYŽWPæž)Ù'‚<]ñ¾q$ñƒ ?ãûÛ>ìÆù±VÂÐÃ0VÍPþ6Þeãõôä®õÖÿ»5ç{ì­i²G$³H'•]“UðmlœWž6')bšNĜةs.E¬ÕÙvI Ø«¾½v°×PÒY5 45ôúmߘ??z5ûSwïÖèLMõµÜ²Øÿ÷õ ï²9SŸ™«ÿÍÈô¸¸0ñ£?k»»¨ŒdVÊvÅýÛ'NTÜ?q¢±"‹OÜ/¤Œ%Úvc(†ÍDcHúäo ‚‰`¶1ˆIøÀ¤_ö8¨t¿/Rùáu˜Ïþ¾½ ØxÚ5·Æ˜]úñ§ìlgu›ð©×Žd9åeå©Ë–IL“=uNš5°Z•IJ˜-‘ ¡3mÛßUÀ¿S4¿¼Ø×¼û%v>h^†3Øçó6„·ì6LíL*ؽeËn ÃÝ彚Ä nïN¿Ã‡­Ò§¨¦<Øž˜(¾]<Ñ‘kݺãl\ë¸ÖI6÷ßzWA”1‰=ô;0@¾ÆÆÖ}`9ò:ØÓ`oƒy@‰íÛE 騄ޫDôE 2®îþ\›™À–L˜È}v›põVó7$e¥WL*sª5Mï¼¹^ñ‘sÞ ²³Ÿî |fý,mÅb …´Åœœ·§]ÚÆ©#![ö|íZ‰9Ó$¦éó:°Ë})âGŽáî_±ÀŒ>§ˆ ûwné/ßÒo@ ñV结yâ:ódz€ñ.¾}âd®ÙÙŒéŒÙEÙ ân;ÐÇ`Ï‹c6Æ zè[qÿïÃÒÚjøL¥}PòÈD "PÄÇsv"¾A%E¨D†RIL4ëý0cöö@›˜èÀ%ÒiæÎi­~ÀDÏ9ÛYÝüFzºêÝôZÛ²eI³Óv¦iΛÇÃ3OÁY½µ`Ûm `í6§lެ,0K”Íž}['¶ aZDR’¡FA'gláœ9«4æ$e¦v–‡÷g&uWþwÕøíîîOEk‹¸ôØâÅÓU³A…ü:UFIÆóÍéE@Ï‹ƒ}LïŠ Ä­ƒÅÿDp 7VTehÃ`âDpŠ'E -Ê&À>O´Hëx*"AÕHØÃðk ˆ»Èã;·½ÍØe ½×xOºµQ¶1ÜZsNÁ©ÖêVó%NÎÎêµÞkuö^[œ$ûÿ™õŠˆÙi2ei³×ÎŽåÔÑÙ­Y0'BbÕ±Ù³Òd%@•;ëc§­š¦S0gΪØ„„9ý‹wkú¦j¤î– †âß@¥üvR•LI±J/¨Mq2„Ï=È5—”dŒŸè0ùA³;0)ìƳ¸"0þ~úœ €$ˆVkŠ5ŠŠàDÆ Z” > `É¢ÜêP•å âX™Mu/¢ÿˆÔ°[H³î VäóË5K¤­ÛDLH([ám®žïädîLÚâµs&ÈXxíòõžªð…ìªc ‘†LÃÝsÖrJ”MÓÙ¶m-°xב˜£Ìe   ±«b  “ S 5[w…Czº{b¢jqÑa÷ïîîî‰)‚yßÅÅ STϯ;n¾.Ï}¢ƒ¸ÕvP2z”ÁAžMf'nÂv°Ï:~‡ÄõDp`À¢ ÚNQuÐ&[M ÈÓ@ÿËõ¹1ì¬ÑÀþh˜1°õÆêËsïï¼ÐÖÀöPïrßêiæNi ­i < Î+æìM“QŸæºØuaa6Û¶]z®³{÷nÃi³ýæþO;vûï1‰cÇn¯ÝkanáÅS{í«æüèÍìü!ÓðäÎŽûÄ0yJˆU¢ ª jŠ••ÕvñíV)@?íEÝþÉÙ¢À®MtÍ~HR‡Ä00@e<ˆ Œ[E°èöí Xy8 Z e=8êí= ç¡ž…(ºAþWTd—uqiam cµVÛ[x¬Ç$%ñ†6j.ž sj‚×Üh/u `™&“ö_Aá¿ó³q»±ßLéeÞ ÷+þ.é l. `åö˜Ù·'~Oÿ’(n°,}rÊdA+Áï _™) °ç2vyl¿¢ÃþýÀ– ¨ß¿’ …œøDH"W„Ôû Pe}`(ËD+P­‰q08ƒŠ5Ðêè˜+í7ÓFÚØg_ÁªÙàåמÚϳ³ 4´_ÝfEÃLž¤y2Ö^~3Õ£­Ÿ5Ìô ;yrׯ°§@Yœôó󲸑%zD4[ÒIÔ\4ñd~u­¤•×`°ûõbÀo—MçüØ‚ Æ‹jŒïGŸö-vÃ}ˆ!1ñ»ûv ï·›ñ‡ˆÆ‹Ÿ`üŠÒÉüEª)ßMLØ,Ñö<<äƒ|¾TµƒkxP¬÷ï(“õXp@bü (WCØû +Šçå·—3zœˆµ…EãîP`ë'4­¼L:P½¼uIØý°]sK¬­gJ—”Zkö—Ÿ²Ù)-­¹˜g…WØÌpN¶ÚÖ lÓ.±™ß””Ì˺qCÕ8¬¯/Þ<>иnûÉ]OEæJÏ›;[Æb‰_‡È}cÖÞœP??ëÐP_C€bŸA"0âÝÅ¿§$N.Jú(ýû“b®ÀÿÕ½ÿÛö×ý¿è `Ô‹C¤~7€$|cqH¬î¶q@ßÿÌ…DðD¨Ÿ'‚ 7ÅŽ‰Û;€y縗6ñ~ÉîÞ–‘Žvöêí"Ѿ< §xÚÛYÃî³·tß÷z&mcÝÁ©ã¼äÙŠò©2+¦¶þŸº³`¶w±“,›¹…ä«íw&ž|fán|2,ìþÜßü¹Æa»ÂÚØYW¤)¤ñÜ 4~RcÜ~¿=ÆW:ðäý¹Öý†ýáÄ`åžø¶ÏýûöÉÀ2Îà»àùôta.FÁe\Íé͉î@ß™q) ä׊|P:Wgshûèup©·}"4áC#äíý ¿ÖA hñþ\Í|v›†Æ]~ÛÃvi¦I7Ø4Þé¸ßf¸ª|…o柘["O€\ÇjÓû”¤}}·vfƒ°B?‘žò² dŸ˜¸ŒKr;ÈÏâ@ŸŠ;‹v{?¨twùt?$£ƒÞDEh¹)Ø¡å:°VŸ8јÒï,R¼lÊÖÝ™›î[ÈWÓ!nù³kª«¯`ã™àÕqòYc;+ëÄûm6%…^'â½ÂæJGHÌž9G9-Aëyš³ºß}¿¹íÑ»BûÓž•gÝYÔÑîÛê~ÿd{´MïîÔÔ~ß?á… Ò³¦fÑ¢û-½9 '½ÇIIs:çhìö ^~~~ÁtAÁ””x`ì'&&ž¸ÁøÝXÖ‹'NtzäuP€ªtP­Ž¶CZôàJX(‚ þ‰°2õ@QEHÕôö"Ï;»ï÷ÙÃfîb^ƒ€Åñ.`ÛµÕuݽ埯LCÚ.ʼn÷Þ†Íõšé{[²…D-Û.VÖ6߆öûü¼wùMœ¸¨£%¦I`Ý}ö?©_4voÑì¹u+Æ%`iÍ¢;JyÌweÍ Õ\²dÅâ„„/††ÄÀ~ÙËeŒo­¹ÕfÕæüãÀ>‹ªj3°Øö_ŒiP¬½~˜&‚=l¾‰ÃBÁ˜á%Ý~p ¯ªÏ@EžñDh\ƒ”ïÅ'SùPŒßFøÒGu"Àž 0¦;2–Æ- ha½]XÈ~ßø¾ŸõŠpé»æÛ»ÀbAFfæÉ§'E2êî‡Õ…»Ã%¿ö¦KŠÖŠŠ<ê(4ôµÙu2zžM4k»âÖð9<Ÿˆ´ñÌ[<§á·Z22\Z–ÞYt?ZØl·î/ç¹×Ð?çת/õ»wwÿ2Æ"ÉÂ\Ë.³ÉTëŸÓK-®­fÌæ*æRååúÍøà¸èƒææxwÕïÛÅ!¹ä[ñý(ñœ(íÛA«q`Êyu"0Ä >Å80/ÁÆU‚†¦kØw´ÜÚÑkÉj&3/ÿý¹sÛVÜ{Ö˜4ÓÏTl;º÷Ñ}2¬°4Þf“Y6áTZ6×v‘€˜Î‚KjJl¤Ÿ©é`ßÝè’!PX®iX`ØŸšiã"’ÑÂ.¢x?ÚæóÍÞÐ~i§%Ò+ c5¾|IMíÔ †WW„…,¸²o_1Û9ýââãúÇMŽŸã Jçâ:^lbrL@â¿ãÅ÷;ÀŠ·íûa¥üvh€q€u¾±"¨ÕŠr §$ØÛïA>3¾³¨”±;-Íèˆce· ýÓŸyoî}ãö§âŠa÷ýò½æõÏ+Ó”.‰¾êîë§ö;w€õRGGX!çZo…2…kÞ&o;¢¥y¶Ü³Œ±ôµá¹/Ò¾Ãޝ_áiÃÌ$Ã¤Û ©Ió d]\Úü€MŸ@i‹ø°¾giêÖgZ—'..H­¯OMÝ @ ËBöíÛWÄX$,¼OIxYÈùe!EEÀØÎæZw>´NÄä2ÐóJJJòÅ\\J&\òž‚"RƉ#%{qȆ¸¹JçûAI\ñŽÐÿ ü dË×ÅJ`{ï éË[-¡²êêÆwÂÚ¢kî€úüÆçª'lSÏû,Åî(:„Y„+†Žû÷Ùcr$&¤¥]ÓKs~û~âý'Š»|“~lÙÒÝrëf‚„ì’è©ê<¯•%ÉÈV««7ì²–ϼ·ääI?ë†%6 xúç¤ú–÷û6¦ñ46òƒcÑ:Æ<`îVedlfø‚ü‚ŒééŒ@hš~žÍ|™¾hvQ×¾¢á}EEÂò\šã¿‹ow€%ø‰Ð2”Ý!m\`5Z@‰|‘"0•ÛÞn¯©‰ D/mqam¹õÇwËËÖ]À6Î}¿“ÀN…b]P›±ña'÷Ð`Ðv‘ºšö¹÷´GÏŒÉ,+“Pë\+Ù7Ñx®»ˆHÇö¾¬xéξ½i[ÃÛ€ÓÁ®Y~ª±ÜÐ0iê’¾]»JîYxùöKŸl÷zܸEZz…úŠy _ÊyVø>¶iØå¤@ ß­ÎWsýN–î@$ØÜ,Ù¬ºnWQ‘ àdþó’‚üüËÒ—…„LáæV’—7‘Wz, A:,æ  iò€ª;ÅJ`ÆvçñEw‡W@> hió³° ßâÝôxŒ_ °H¸sŸØé¨ë¨T< °À6ßPö]6ý‹SSÃ{ “Òd[Þ†½W|šµý±Kt;°70wÂì©'Ãdxþ}.½Ë+ÏzjRÚãèû¬ì·BüNJKîŠmn°X²‚'uZl¹ï–,¿Ç¥-,¦ƒ$##1þnn>ü=%¥YŸwY‘$cQQzzJúBŽ^~ÞôôôóŒçÏóãù¢ì}Ùª‚éü!òJÀ@KéãB"Ý`"¤—ê÷‰ov×:AAMGX(›WVf´ÜbŸfnÃãe1ó °–¶¾¯¬èý€y"l{˜ßŽèvÖ@›†NgN‡wiÌÞ[ë=_G¢Q†-­:/Þø$°!(¾+­ÁÅ:5n®ääø¾]}Û³@µÇvÅ“êlÙîÀ ýãjùÔÉÙµËKZS´ÊœgÕüÎ_©6 Ò^KdÊ[ƒUJŠÕ÷Ä`½ž"Èø€±y]^Š 0æÓU›”ôææt^^^AÆó²'ße,–<¿ŽëïºßÍ))ßO„È ˆ ˜È›˜ˆæªÂ‰ ¨V‡@ï‹ òO¶Ú§ìñÊ:`A]YY—ñ(®¥d×vŧ+N)x=¿_ìã+žb‘ûsë&ÆoWTÜlðn¦¹ÂËbW˜Œž×vã™{TKæ_zž&ceììá»wî„Y§éU›°a÷÷ßq·Ý¿¨î=0£Ý™^æmPY)óÇpwf£SXßÉ,?¯À††¡<í+|ûûË“4€¹|§´ºï.v€bPÍÖæ’Å&JÎ1¦Ÿç*âL¦^`ÎOQMo~kŒ_`»ÇtáÂ…ÀÖÏ ^Ž« ¸åMŠ02ª~?1EI)DДW^é0°-)éÅA>Ÿ<ã…ÇäÉfÀÞaÜÞîwòþ£‘¥m13·O¼Ó¯šxÔÈ6™AslÆ^Û5¡°ccÜ>·NÜØÂ+Ìš˜*ïÛ,®¾a.S]{$,ðŽˆbûö‰ÀúX èñ‰À¤Uw¿oÿý'wîß©‹öÕ¸ÍV·T¤7'5Í+ ´ÞÃo×®] K–ðh†Æ„jfúöòfú®àixìí @ \¢ÙÙE ¼h °˜³¦l^F`Î^V$Z¤,Թγ0êÓyU>ççåXÈÏq•cÓ¦MSøyyA1ló¹Á Á+Ñœé­.˜ìw33s›l`  ,îƒ&“—²ú…Ë>pçw‘âÄ0`"]ä°ýðÜís':Ùé‰~ïë2ã‰Oû¶[Ë̵Ù-[ËÖo®µ(ln]´¸øuãíaâïA¶âõEwŒÝa÷[TôzÍÜÞÌyÞ¢}qqK3Xsî=îöãý€ÑþØb׳{ýI²s¦(¤*Ï\¼ÛðO[t› 0dˆáAúF`¶Âer˜ìy¡ ¬Ä˜2Y”ú‹Š›CB„…ðó¾ddÌÖŒ‚é¼Ë¦,ã²l¯)?ºjÊä””Äî@ß‚|jûïùõà¢oÑq÷,?аý¬â€mœýÀ¾^ÈÜí ñ ãíûë@c5íOÛ'‹•x aýhÍ/1ásdÍÕ%jŸî óó›XŒY ö;×AYèN°Þ¬3³Îš8qûIku™súGî×€–ĉ´MË¿ïè·+úä®]Cy4 eœ«'Ì9%£n®>Ï÷±´æ–ððP_ßP€bHgl.’lfTåO´:aÊ;…[X˜ Ø¥6äO¤€:7ßOk<`¨Hq&€ôe˦„,œ"Ì!,ÂÁ[$)™ý€QõÄ÷ ö?°çTÄ'B›¾ûßßÙî·=þ†ûû;ÀÁDEH»n‘Ãİv`­¸ÝoâvpWθ£=þ¾ƒâvccwꉯaíÑ_$foÓ™vi‚‚¬¹1P˜ÁkÚg.{TS;(ï7~Ú ÅüËÎ\é’~5•qqïi zÅcëÀÀÇ»¬m¶d¦þJ˜ê|þ­¤sµ…L£/Ojh›á/Ð&?Ðv€b`L¿ÁÈø€ëÁƒßÇÿ?ûÿû·þÿ|AÆß)VV)ñV@ÿVùÀ"­ùð‰ÁÀ´¾LXr!ï  ¼ªT\Ä2%ØRö|€a'Ì€ ß”@M?+3`S8°ßt­È"ÅGwêî·…>Û.ÞLúwÀ»ýÆ~‡AãwaO@ÃÝ÷Ã4 dÿrêhm;¦×Øj|ßX<(ε––~ûXbÖ,r“7®ÃØy¶(ûTÓ%oì¶ŠkêX],mf–ôgf–gf¦É$©/é3ÈzëÞçÅ3§ Á§qñœ„_õ¿¦ÍO FÆóŒ\„A-µ3l&&&&\ÂJòÀLž­ôØ’çÊÞ§t¼XøÐñxÕw«ô"FUFIQ.Æ…!S0r˜g§OG^V³û÷ïà®?0••4»§s€0W‚z ~C‡¢øvã§aaa^áiYû‡‰Ûéè1¯øíaíÛK¢Ak¶ÚHÌž}íØ1åj'ãö“÷ýܯ+vÌzªáþ"ðŠ«;ÆK²TAÕçö퇟Y䋊ŠÞ0PŒ{$27ðOæâ µ ÖIó¦q®ÕøUî»3uÂ’%»œ&ìâ±¶ðójU·0*Ó¶áwι@@¯‡qZ*Àd,í¸„Cx„ãèP˜«ø°)Ÿ½K8›KX²H‰‹ ˜ÏUS¿'  ~`é'¼ŽŸ÷ü²… öMy ¹.ÿ·¤d:0O0îãV²20g~³DpÏØù6ú)½¯V÷E:ÂîW‚šxÆŠÛÃæ†mW¶ƒK´ÕÛÀ2ç“½æ­ ÝÆ¶ËAñý"Ű@/¯Œ¥ì·þŠtܹŸµ"h¥ŽLZêl}QÉÉî÷âKþøÔJÞèë˲ åiØõÌ|W«ºŒ…uàL ™t³·2 À 4ÜPÁâÿ‘Õ­ÕGˆX¥…=x DÀzžKø/#/Ð E\¸¸Öq±éíU8~ü»° T’WbTµvg¿7ËóŒ\E˸B–™  þ gpð«šò‰V“i>ñðað°ILuúÞÆ°¬sp·-+ê˜ì¦Ö‰;âÀt¿_<Þj;°þŒ–™½×{‚lZšyüöºíÀä½=ìñ‘û.¾™šá À6[žqXŒ5Oç/‰Û²µÙî'w5Ì›]|ä0 ¶Ÿ<h½%¦¥¥=ãQåÅû3ŸmáqÊÍÚ¬˜×o,òê9ä©:e¥§;Iƒ  °»@Ÿ?xP²˜Æ…Ïé‰+v`£èõ¢¢u&gôM~ ~ïܧT,Ϭþi9%%^7}ŠðºŒ7ÖIJ.[æÁ(Èèžïô­»±Áw÷ïæçþÎ>wîY^üwpË{P*0z ½‰âϬ'no·aMÈl7›u#mÂ1½sÎzÞ¢Íî I ñ“aÛãóÒ­¬T÷î•q¶ø“vcâýh…½ÎS½kL6;i£n.**yc20‘õ‹õ]¡<†«R·ìŽvê¢mÂË ÓvxëŸcÓcc3YV¬N6!¢,AæÆÛNÄÐ<Ù̓7ñéÀŠ ”ë 7áæP«(RĥĬöååõó›ÓCT¹•B€ €ï‚Àð­`HöÑsµëU­âãSÒùùOL>¬ªšžÒ|C@ùŒ7·¼˜°¿».Xö‹;`zI4U 1/ɧaŠª-Ûûâßæå5X8éáJLÕφÍöf÷ýYoÅÅÝ™ÆÝ ˆ¬âDO>‰¾Ù©îÔÊvYÕ=,Ðp/ã]ÁG€à†¤Å¼k<<<ó¦Å&ÜnlYÝ;oqçüKÅüwùÓïòŸÏfÌ ε՗¨JV›/£ ‡?°s8¾Ø1ã¥z`’çzÀûà°ÉÎ[ +þt~a ç‹äK{A`…gìîˆþ>a•Â+²ï\uŸ$¨ßWjêl¿î=Ï/˜-z¾ˆC^^‰›ˆ€¿ãÍ)î‰'N« ÷í ùLpCÈá:¸åÿ½y»qü óÚ4eI‡xw÷EÀ@J-ºãð~»Õ~qPP‰E@ý÷¾øôuÅÀÄ­Ú×÷4Ðw‚×ùôÀVØÝôô†â#úlz³%öÞ¾¦ ;!P¤½wΪ‚zÎÙÅ7ÌÌ&«{!—ÓҪϧ¯“8L-7TÓo°–âtôà/ÊKIaûXÞ['<¥äm ^çJ±üŒÀ’Ø\ovwO4û‚ŒªÍñVV“§„pLörø=îòg‡,œñ.丒’(°³Ï%¯$ ò»0°é+üw<(å‡Ô€âª˜öÓ0jÏŸÏ2®és¿³èNÇv?¯]^ÏòÞ}l°}?PS_°è.þÝÀ= ؘ;7,,ÐWcB­9—ÓÝ¢Ù’wU«k×]˜ü Ø/óv6Ϻ6Sý¿…ï6Ný¾,‹ê|ÑùÎÙG.W¿5›œ~Ä"ßIõ­ê[€böÍLΞÂ-øvr:c‘°5-ð »eËù§ü| Š(^`9(Xxm•^âÀ®ž   *ã2^`&ð0åM4]8cÆÂ…܇l†Ò¹ËÇ•Š…¯lâuu¹¹A}cp¾åz þ”Ãß¿»KLÆ”f`ùr>ëÉ¢ÊÊ`×Ý/:P¦:Ouß>~+q`!÷Xº?uwß>qû÷íí3K€=o_uéÅó×x×ê‹Iw:’ï|Yõ­ ° wÖKòvf ‹¶y¼KzE[诈ËfoÓÏKI+›fá”·ëm_CVžSZ¾Yµó €bXJÜSЏå…´»‚éÀ¦,Ð%àºè1~FÁ,+þáßaâ‚Û<€=9a%`Oõ{ £° lʽ‚ÀfnódUU~S~^ ß7)]¹º@þЕC&ÅW”BB^û=!S—¥|ßžø]ÕjâöDqP#7±Yþ¸Òq.FAayn`B8›Ñ}Ñý¬]»Þå2nk°wóšùØËÚ"Úúõý¹^~~%ÏÊg+LõÞ«wî\Z±ÂÞËÅÕ—¹Žim¸øÙ ýêllµiÞ7¶Ï¼yloš‚zÿ6ÎØ2™f'«ZUH›Zð¥5ËBsvŸÁÛ·Vo­ˆã*÷‚ ”„AÃRÂÜ!“ùùí+çûFAA`“Ø#<¥xnbJüöD`›%]XÀ«ò®[·.¾ù÷o¯ïÀ{¢™W˜KUPðhxÃc!ÇU¥«W¸•s +My1eÿÂ…ËUÝS€Åüït`¶ß.L7‰ª!ç§LI18Á/n&¬´L˜×ì†y^ŸÁýíOž°[7Dg<ª9¬^}NÒ¹Ñ9ýFž×Îø½I˜{¼‰Òÿ’¾ø`7X‡5§x,æx`çêÕ«W®^}Á±i /°vf†eÀVóÛôV ,ïl¦SJzºÁöïfªé!!¦¦ûß¿7pZJ¼4NäþÌÀûûýñÃÿË&È´ÊìUH› æôôiGX˜ñ\`KÐÝo;PzQX‰ßa¿vËióç°¥o4{ûÖ)¦dÅÌ™áÒ+úexêÉ4œjuZâõlgZƒºôÃFé>/ €bàZw~ 0¹ƒ{ìÀ,,‘ŠÒÓσF-³‹Ž[³ÂÍüJ—o+' +y €ÉÖ½¹ù7°r÷6¼¼ÊÊgÎ}vø0¸£s8Å Xü-[ÆÁqEøEÈŒe 9–ñ›¦ƒzûüËøÓoð§¿uúØÈ7PMy›˜ÂÈŸÞüÝ Ô$ä¶ 7¾ß¿ÿý¢ÊG"a¬"^ùÏfzÍœ·hâáÿúN¢ñÖ­ÿyÔû€}^Ð"·š;à¹ÀýÛÅçî.ütæNN‰çɉYÒŸÍÛ›¶kæ é3ŸñðüZÛêgãågáw?ìäýº@/é´©¡Ñ~Ä 'Àf",¼X‡6܇ä¹CB¦Ÿ)ÚW2eÊany.®fÞ"^~ÆøDSSÆ·“oħÎR¦y ø}Hˆ1Tž9öîÁoA`ÑLÔªñÀ˜dÖ‡¯B^ò¿ôp»pߘbà—‚骿ý`ÁtÁÉÀ°QÅžØæ÷˜,˜îŒ÷ýïý$­¾;Ugs™ëéW‡öæ+€ýÆù^ñÏÂkŒ¯_ïÐqg¢¢âv‡;ûŸziN]l³³þWì´Sìì6 ~2µ‚“o¨§¥ýO»§¹MgBIàýû3½¤ŸÍ½o|¿ÆÏ†gq;ûN€bÐ;cr\žkŠpÑaá)@Os sŸ¦ñP_N˜wYÑPÕ´ï—𦫛6…„ì:¼.÷<ï”㢿ooNám~'LøSô¸„AU¸üqFÐ@%?°¹çl¿º«ª2¦óŸ_¶lá² .¤}íazH=kjÊ1…ã%00B–[€)ªÇ/KfÝHOv _š¥gëécÌ^¦Ä•}7ý|Q¶¹wÚL›hÖ›ÿ ^ñíí"j*ut´ƒfßïŸxgûöøüyÓÖêÅÆrtÊ*(ô+¨k*(”*›0[–­XÒYOfw`ô3›†Àè¹sÏÜY ½bç=€bÐ;WT$Â;X›M z|Ó>îâ󌌂¼ü!û„¹Î/ÛWÄ{•›;[ÉÄdïÕ)›ؼeóAS”üÅ’‚ªÀˆO Q¥s…½äå˜,Ñ•Ž[¤[mßSã¿vÏfçÉŒŒüŒE¢Ù/ù/¸ñó¿5óF´‡°À*gpðn’y, €l^ Øz8Ï_ôbÊK¥â}ç(/¾|Nÿríµk×¼­ï·îNM½ÙߟÓoáÇš±4®rbØvñv›^mÊ錢l :± œœ²ÎΗE³UÓ'l´š¬*Y;µ ‘g0êwí”~æ Ì Ö~Äìbr3.úš ØÀ—&|%“)¼JçyÏÛ2Ë€mÙóËx9¸…÷)|U\4Eiʾef‰éé’Eú¢Œ¼ŒÍ¼Í)ßÅ¿ÛzSŠö…,[8ƒw·Éqðxm °å•’rB5Å,]UòHµ¨¾èºó’éoßN~›b,ÕS€­ãeËf„‡€êƒwÀ®ÞFÓï€Ë…¼ ùM7˜Y½4|DTioÑ+ÑZ6¶4==Ùk‹%Ž­v{Í1IIÑ,w?ëí"wÜÝŸòÌãÔ’MK·º (© !!ëeéS–fþÖ̪o²UŸ•UÞ´9+xæùFÏ´o”^!½d×a€bLÏ3Ë€Q l¤sqƒº­À’Žؤݤ´Ž‘q#/°Ï, „¹MBÄнtÁ` *•ίֈšã·ll,:ÿ€_iÇ”% çAcV'¾'š˜,¸lß2.a66ýãÅÇk/¯376Á›ùÓ9€Ižƒƒ·¸L`áU`M¸I8ãWg\åà]Æ›ÂÿRp²)ÿËe‚ŒS^òóŸWÍ2¿Üxm¯ÞìÙzÅé—ÙŠûœžLܾ_q¿Ußv㺹Ñ7wö®õÖ3¶ç%[“ÒVÌó.ˆH(ykån`åtãm¾ÌTogç‚©i«mO]¼xñ<€b8ÎxØna\V$ žXúÅz{½¹¸Š€¥Ü%PÕw½é¦›„§(‰`Ùëä}?Á’~þœ‰¾¾hQñƒô”í'¬Ì¬€5°ùbÊÂý ˜¾ú]udú+.®—&JE<îî+ÚxžßÍÍÍãÂÓwïf,|ÇkÊ;Ãt!/ï&îCÜÀØgjºØ´ºÂ5åÅUP£Ø”¿HÕIÕéÿ©k²ÞlÊË€™ýï÷ƒ¦6'Þ7îÌÙé?ç%ï—ÀTªiÞÞN^^Ïêµ$Ž˜™ oÍRZ½/«ç«—'È:·Jß÷Sž«) @ &ÂÀ¦7?°V Ù*ä@þëúz³Ÿ;^Ìeâ­\ L JÀ½à« „Á]`/ö¸¤ Õ ~¶êãçÎés‰Š2 ª¶vÞÛsÀjk߃f`›,µ…÷…„\]Àô é77S·Ò  .y+=ýÆ[`«ÉÔtÓ»e ÆÐÂ…û€y쥠ÇÂ/^,tÛèæf¦úÖ˹5m¯7›÷®û•‹®ƒfãž>-™Û*3G6mŽÖì9ç@ߘøöíÆ,i?¯|v¿Ã»¤y¦­­53x›¥>GæH¾ùÛ·ª^‹ËÒÒ’ÊCÙÕW, ¼@ JÅÀz‰ ˜æÓyCB€-.P®zt ·òsN o==`ØK¯M@AI`‹X’ÉÛ?J Y·óŒËãbc›êÂ&c.úlûd`^Ö×üü¼çCÒ_NÉ›˜w8ÅÊÀÉ ßq„˜–º•š¾›ñ¯`/dä͵”yCC8ŠxC–½àfƒ¢»éwÕáÝ»f“'K›³É곋~-›y {†¢¢°!c3þ¯m:Þæ ±eõΛ%‚¢å½E½ €Š”LÎq-c¶6€žM7, Fü>n3 D‚ÀF°’+Z–Îkú˜)÷q[pJÜEû„Ó=„›{Ê>%`Ð(e‡1JNvöÔª›iæURÿ.xØAùþ4cõÖì„©ii©©é‰ͪ¿AË•<Þ˱ þU`mzœ1û¿ Çd`MgjÊk _ÌÀ$âÆø’ÿüy`—ë¿©‡)qm-[-›~­óÞ´“wŒýæj>{¦°j'çÚXٽł^V ·Š_b!£ž?³|ÎZ½ÚyóRw¦6N˜7»±Dúÿ ž9iÕùù l’êêæy ªùùİŒ 4i,ô°·¢¯§ÂË˸ì|¬Ì5t€iÔ‘æ’Wâ’äç†Ë”«ÂSx¯È¿¼ËÒOx2r)3üñS4§nŽß.ê–»»ÇˆƒV"nP–é ¿»ñ10Ü6N^69}ßùe¦ÆNNú&¢Ùç›×ëíÕ7áÒßËVÛ°¢Ä÷‹¬Ÿ“tgDìüm³³–È”I(È´¶:™;OŸ¤^–¨5Wç™3{ê´ˆ9eSçL÷%¡,M_aj­¤“y‚³“ÌGÔó-dˆaÙ²e@_N áç&u~®)Àö$(ÓOÙiÛ)…°)ƒ°hæ%îóSB–quó…Wzúy~Ó…Ë6és(y„³²›Ç 3`צ¹ùiJJâöíîÀJ_<ñ„Õ Ðc³ë„…øÂ«Àh_*ÓÞczÁ”)À½ #ãd`Î0›œìõ ïÛ÷bá#úç€Y¼XpÊÂ}¯¸Š_ך˜p™èïU¶fµY ¬¸oÛÛêwÒÉ¢uWž¹÷µ¬íùli»ddœd¼,–ð{9²i‹c%Z%Ÿé\«5?b~ãí3‰Vg'§¼·YÿÓdx惒ð¦…!ܼ/%Ýy^`Qà Œ~Æ)@K½)‡Öhý­ x^÷ÀF_/0X„„’pQÈù^Ó«bÀIXz3õÆn'ø…k5祋þ”xÕ”uíO㛊¡ð&Ѐ7¨ShÊ ¬ê€Ý‹}Œ‚Ë.,Su ì$›¹]vð÷…äï»›¾páF~P»«˜mBíåâÚË\¢úÞÞÇb ?É÷Û%2Ó&pûÄE×ûÂ:êØÛåç…Í ™9“çkƒÅ³Ó®ÅÌ|&sÙœ'M¡ UýˆSþõ{<á<<%áÖÄÀÏÂÁ!ÌlŸ/ã•ÌÂS€ÍêeÀr¾($„ Ô“9$o ðXŽh0EéAÈînPÔ–M Yp…ƒ—cá21%^þwü¥fn¥'7òš Û³‰‚‚)ÀÜÌ_ÄuÜ„«8ï»j6£ #£)ÇUP–ñàUâàæl· n“Õ˜Ⱥü|`ËOXõ{ðïÓ-ÛL¿ñÒ Ø¾Q½ËŸÍ¦7AÏ[ÏÛû\±’‰þï¾ûÑ+vJ¤•ØôŠÔ¾¹x~掛ðÅ»cbVXKköoIšê\­P6›M¦QZ½¶ÖÉâTôá™s­ûÕmByxlˆ؃¦u`Y lY½\,ìxŠ"á}@¯-õè¦(ç…D;¨ì¥{`‚*ß´ XË.ãçØ¬¶å‹¯rps…4oÜXúδt£é ·fÀÌ}B0Qðã;`{ÀÀ¶•½°‚óØèÁ ðhˆÙÆff¦E€Í}á}ÅJÅ—/ggë_ÖåñrßÝ,Ñóç € ³ /®¼Ø,˜/¿à¿kºðÊ>ÑËÙÀ¶Ýe½Z=éû%«ö–)Ïöž}˜Ò5¦òì”Yœ sеãäõ¼‚Ù­ÀŠÞÆi±afãNßÅi­ i ;eˆá%oÑ”"~Á»“ù—-äz{Yú2^^Pãº÷À6ü2a`òáå†Lσ€—÷°$È¿i гË8\]À ì×½¥ `åý®´´tãÆ'Ì6ºÊ÷w3Ly¾{ðà°æX(8ù­ ‡¿ ™Û2áeŒ‚!n˜™ ®[§ ,¢/xp\åwXôÞ€cÓ¾}\&JµÅ—!p„ÑÔìFñ+~~`%oÿVXå_X¦ôbÙ² _ò/äw&’üê¢Î Sg;¹¿mvú?áZDBRRZëTï´ÞéΩ;¥m¬Ÿ-VX|[vŽìµSµ2 ÕÎóˆ”mAM`9ÃϱÔ¨æuSMw`ÛX»“?ïËŽeŵÀ­=p²VJf@äÁ» ä%ï Ø´‰ÃÔ4½º)ÄTX˜—n,-=±(R ¬ßJßÁ&Þw¼ïBLy4éÁ+¼l¡ ‡ )£°- %7 b‡E‹®[u6¾ä26“k•Ò/¼­>/ôö²tÁtÑÉ‚/Ïs£ŸØàYǵÎ9MÝ¢º–­¬µDºÁÂÂ\Ø`O(m‘›§0oΜy‹g_Û{jjšBYZÙ`ÃNf^OÊ´$€b™LÙ@Œo~pcŽ#„èyî)¼3€)ùü¡C›6MYvULO@ TÑOÕ{ò&Ë€ÅÿäÀæ÷ `輘r•{SÈ;Ó…3Lßq‡¼óؤÄ{âDió‰ïMK››=€•×;ÿIÔty÷ÎØxçà5]Æ–ofæ±N˜HÜÜÀ‰å„Ðó $-”À~.(о—fü#o߸[½õxkeoåžUÛ*™ï lí¤›½Õ¯MÓ“Y"r?lÑÓg e–ÈÌ™?-A'X¾¿Uzxj™ì„ÖS2­<§ZexxÔˆX¨cXŸ¨eÀÉ,€€Æk€à˜É‹¼¼<ÐóS@•Þ&ayp+‡÷¨Föàåvë§/Ø,êO¸-œÁ‘n%øà{âwAÁÄïÀ>ܹ߂ïx›½Ìµ®Ø[åvXšAÂÏëá±Ià¸èƒÀ¼avÂÍ X9”šÎ0ò 6ðÜÜÓMM‡n“ßòs«¾  öôQ=?Yõm~Âb™©dke&L7¯œgêNuõ]šd-ždàfðta`nß´ Ø•’?ôû!1 8sF@LX¬ƒ"íbJÀLÂ+hêÁ,$M=@%$ÿ2^UPVá:§ÏØ¼Ý ±¹YUǼ@…Lj†„OÉfñy˜šK^^wÀ"Dø`¼àd³“O¨òó›¾V÷ïÜ&›¾ã ŠL<Ÿ.èÌ馦ü‚Ì@ýx`_äü‘eo%Eo¼uÊwr¾|ÄÙÜýFuuVžEµzÞŠò_zéùl{Ø,¢wÍ]Á);¿¬užŒú¼y ¿æ$Ì›“ :Œ¯ €x¹šØ!HÚŸ¨Ü `~Ç;0(€=`Á…}jãšáà¶ÆJKÓ•Ù U`É0#¤¸ˆØÔåß¾<Ó¬ëy……]0ÕŠØ·3åm>±Ñ ”Ï€@‘·ôÝ.þ¦üÀ6°ÀYèá‘Èÿn#?ï;`²ò0Ûô²éBþ—wÝ6¼•Ì–¬v–Ô¿|ùòùtwUÑóoo8eåïz+yÃIÕÝÏ«aqìm`qàv£Öyªyë¼9¿ôÒœe¼[O%Ì‘HP˜p­üÔ„´%Y7ú²¼ˆáAȃ <§|(ÆyÈB^Ðø Ô× soZÀ½`7(eO™²IŒM ˜.ä)‰à PX‘' NÖeÀèO?,9Ó­ÐÊÌíÄ S·¥¥'<€9¨t2°czKÿÄ Ð¼&éÆÌ;f Ânƻҥ‰'€¥Ai)°›»pòdÆW¦w¾º ê¢Lfä÷†0?(Xd½½‘ï\›¦že•õÖ]}^·wíåNNëœ,D%¤Í–u f¾3¢ìvšäY“ßôeõõyɃ0°½Îõüà”dp¸! ¹8 e(Î!ñjë¨'#Ï- LýJ&Ü ÿs=6`&ó§ƒü?T•›žçåg46j7Nö'¥˜Ù ^`A`æô;¿$¨bÖÀÞÝÛ·ÀjÍC5Ý Ø† ªÛO„{õ¥fïN¼6Ž.@“ÉÀæ0_Kþ À.éÆ  Ž¿ÛF`HX¹Ä«f9™W«x=“¼Ìæ¬>oBÚ¼ ’ê 2À>jí*4™ÿ¢êµi­Gò%³œ€½¹¼Ö]YÀ@1p«n®"`äór{ÀÈh²ŽKߤÈ|Àjûê ÷RÚó=0åÓ0$@u÷¦)‡qs))M­2V[nVf¼Œ\Œç¦ˆ‹§H¶™ªU¢™;ÐÁ)Eüéé’ͪŒ’ÕGò²òÖ©ªNæWU};Ù,+kò `áç¶#0šS€=2Ð`°­g¦*º,ä<0=y0J>`ä-Ê6+âXšãº+hf`àþXòYÄ«_Ë¿a‘ÿ_–­ö²wZí)™%yYYÕNñ¤%$üˆöZ,;˜ÙætÎë·x6 €€Ýay`¿D‰ÍÄäx±×9`œŸÓÇ;ï`éÄÌú›@˜ôÁž?Nû@H1È îÇ-¶t-| z^þÉÀ8–k –‘É‚À:´ AÐ̓KÒ Üns;a`¶Q°øHþe}gýËç³%U—tUÕ#ÕÙ7€Ý—eÀ¢Ì Ôúüî ð-áM?ç̵î8Wºäºt`Èe§OžœžîÁ{~òd¿%NyY~K4e/ ½\]]}CRT•íT­¹³sZuÚ s‹©S[_⌽=•‡gÂ?æüª ~^AÁe Æë>¥t³eÊÊ&çyWzPtÜXƒêñMÀ‚ èE±CàT//)ö«>y‡¹§,Xl×rƒzrà¶‘‡?‡)ï>÷`±¬:ÙLTU¹qp5›®ÀTºÔtM).Özýœ¾èQ}U3s…ó¢ú@ïl¦€YVnf¦çÓùÍÜLÁ“x<<7³Ì:ÑtpO¦xW¶à@c'›W§›©‚¼˜&»w¶sþ IUuÑÉ¢Õ7T' :{¨Õÿí‰kê@KúÞöïw7â]êÄ ˜žÎ/ø4ÂÏØ,xÞ$DTUð¼²×qe=PÜ;¯ O³8µp€þæ>Ê ðQÀðY Vlë(bžßãݼÀ†&£©ÇFÞt`>åWÚÄ!Ì»p¡é U~`Ç~]Ñ‘ËÀ›ù9¶ãÀÎé‘#J’’ËD›é^Pôò:§f`t,ä6.ÙXN$š0›œ¨ÚgšÉ)>ΕÍ A'`nUØ+ú¬öÆÉ4¶jgÙZgs§Ú·}YÏœyjßæ³©›W×z×:_vʲØÕp²ïþ“°¾“^a'ï»»CúFþt~`À4Ç( ÈæÌ=3ÊçÀ¥þ&Pì{ 7%<÷°€²=·°Ø Z€ÀF00ê +Ëÿ… qVþw¼¼“Ecó[uÍŒÂúÅËøMO<àòH)væÒw6Ñ×ç211ÑÏÕ—/æ’””Íí¦ Ê¢ìe¦à±Ëwü 7‚šzÀ.+ÅÝÊÝ+?ïpVŠÓåb.ýË7$ß¾ÍRw­.>¢_,yD²ºZ´úÈå#N¢G.;ç×N­­UÏ7pÏr sßnà¾}{ß®û~aÆO¬nô=ñ3ö»@ Œé Á  ¯—=`Lg”LO¹° <ÐSVö~öû¦À„¿ Ò¶–íÀ.Û&`/€Vø@ošÂ;£SxrÌu}¹ž)±¨)!‚é\zlµlJòSÄLjkë +?§¯§Àv†MOßÄ„­VOAM¡XØžg|뱯ZRõ¥ àÂÀªØò}Šs`Íá&n0ÙjãdQ`׉ŸÑ„M_éÜ9.`þÆÁó뀽Éo­ÌT§îü_AÒìmú[Étó#oã—ô¹»;e½Í2ÈêËr^&òäýö¬0`Û ¯Æ¸ €€ýu`ÃÒÔj:Ÿ^\$ p®X@å XW^êëð‚bBÀ¼CàMÜ܃úܼÀ\Œ}y.á)!‚VüÀˆ_Æ ˆ«W¯Îá>£Ì-&|÷/®\Y¶pŸ‰À¹½ÞS«M®œ39Ç&0[YK^ ûeq6?ö[AF~w`­ìɾš" i0™™ žPM÷”,2 ï;„é/^0°J¿0ùí ç#’“­òE³žMžÜ|ãí Fsõ·ÍùNoUýúÌwô‰¿߯,ýó—H³ŠD«ïж„÷/2î P X0½ãç=1˜á_™(ÉŸÑSVR:“>Eà̶¼ÍFþƒ`ÜO~Z2Œ~hû©½#2#d!¨@ÞÒ ìù/Ûj\Ý´èûÜ MïžçZÈ¿L4›ë¸Iñq“ãllÞeµÙ\\l³•Lä98–ÃØ|›wãFz–Ó IFÉ·ÀÞ—ðÂ}‚7€å‘pÑ>.Æbá¢) f.¸[œht1c—ùqIA'É<}sÕ#K,nˆ¿•1wõŠïëë[âõÞ Ðï‰ÁÓ]N»œªŸ°žìÛîîÞ'þ$¬ €Nl´ Nñ,Nú@q`SŸ;„Ù ·yƒ´Hë*¨ÄÁ¡$&`" $ÏvùÜ9`ÔŸÓ¿|FOt °ÇÈ{ž—WØ àfùôçôgO6›ÌÏËËUT$Ê–mÂhZdÊZ¯Q Žsׯo—ÑÀÎÃ2Áuª'ã…ûülP#ÇÉÉɯ¯Ï½/Ë¢ÏÏúÿ¼˜é%»Ì-,¬ïß?éçtr{_˜ø“‰3ˆ!—ÿ„°¡)èlfzz¸™žà_   ô3ÐqÅÊkØ€1. lëN‡x`42/îØÊx(uðäAÝ=`Ê_pûË–»² ¸~¿zu0æAc¼ò ¸MŽ ›èÕ+íSVbËV2Ùg"º¬Ö¤¸È¤˜‹kÊÐÒä)ëŽÜP½‘-*YÄ_Tm¢Ä82½À¿Xk.äöï€Àáºûy`u—7û<°´rrÊR}¦ž·³ÑÂ|ò~U§g;ÍÝóún¸ç9yÿÞµ+Þ+ë~_5°KûìT q_X¼—ß}€bxΘΠôû; ÇùyCN{À„võŒ0Æ…€$°¨E70ò!Í|`Cg(ŸÛ¯ ˆ‡”ô°f8ãOáà½Ê»p7°©Ê 8^ÎภªEDÑB` ˜·ûMç…Ÿ«Í^¦t¼xJˆRQ±Ò9ýâó¢GŠ•²Õææ¢çí9Áâã ÍŒé'ÖqmlN7ƒ4w€‘þDÜà­Yúºl6Æ·7òTóÌoX°åKŠ®svN1H17¯¶0·x›çädáTm¡®°â±“EkàÉ]'g&m ¼ÿÔÝx×®™ôz³äƒßš›ùÙ™µÜÀ¾/ÿ&І6%`q·Fùø%P;÷<° êÛK;PÂ/;y ®26t…{/ §Kz`wXs=°Æ|Ѿl.Q ßMLÎ?ÇÅmræ h‰Ðß&lòÅ&{õ÷;wî×q®óÅç.É;b‘wƒ‘ Èåªæâ2yÏëôn"°‹ÄïÞçurûåãëöñg›½}+)höÖꆤ׳¬ÉªŒææyÕN<Îê7œnä›[ð4˜/NjÈRwÊÊ‹ïË’¹·k‰¹…õ.‹™Ä )z¼Xô<—ðq® áV`G¹Â@?* ‹™ˆ–óz 1è@i¶ãL@ÎûÀ2œùàÞì!xÊ—wïÅ”„ƒj`±°láÕ ù`¾Wââàf«ÕßǵlŸR±›(ps›œ+æv&JçôLÎÉëWR*J½,Êu¤ZÿÜqçóæú\éÙú—­@kÍÓ›‹6æ‰æ-Y" TÃjû©zùÅ/Y¡°x•ìÉ,Õ|Q`ïl^­n‘—oÐ<5Ó¼Á©U}IžÓÛ|¯< õ%ù^ùNKˆA´˜ËÌàýÝ—ç_¥]$òòÕ²!/•xåM΋$1Ðv®ãÜÀ$z\Iù °—êà>€y”¢A…¨ž÷ð‚úTè/8¤lðò^±TÓM9$¦¤¤´@8Ý´¨š1»4g«L:&ÜòÅû–™( Ô.ã8W\|4§¢Ù<[òˆh­—¨~Ú¹âuÙGØŠÍo¤Xô˜YõeÝÈû_›/ãœDR5X£›W³9ƒNqvb\')š¯ºNا¯­f[b^}§q…Eƒ“S޳Ǫ¿ï«¿NyK²ؤ1x²ñmzv6ÿ>`äòO^Æu×cÿËW””ä=_ ÌÓ ¨âgL@í{áu@|pl5Þ7Éâ†Vñ Æ=¸y * ”ÀeŸ¼¼00 ñ¾sóX¸pÓ!`O˜‹Ñdï9`ᜠ›€°É6.Ž…À5>cR$¬W¬ôìˆ0W-[mµùåìó&úÕ¢l—kÍô4ýãúææNêÀb 6›1?ûˆ°©îœïdî´kÉ[÷·VñY‡WÈÔ‚NìÈf\W-Y-Sm^]]«n±¢ÿ^´EkC–*°,†@_Þ’¬x'/€b8" *H¹$€R´‰ ° wüリüû–½\øòЫWwï^¸{—X¢ƒj2`sGLþÆñuÀºI2&é샲ý `_úÕ•CòàœªùAþž"ö½Ò”e =@MÒw ¹‹øyM”Lô§OáOŸR|ÎäEqÈÂáe\JÜ&J˸‹³Ù¼÷36k%UUÓÏ_®½¡Z­ž¦·w×y`?Ø×sv–v®EUÍ«/K>¨þï|#ËéíåjQïÚ˵iÇE‹Ò奤óeÑjýZ¶ÆPëy+dœn¼Uµzë”_ý8Ëê­@1?.ºîÁ‘ã븸€LØäœ(#×P™º/ÄcÙË}J//\ö3ïºÝ½ûòŇä›MŠAM>0æ á.ù¢»w=ç»—qWÅ=€‹p†8Ľi™i°&1s»`*¨šžÎu|Š/°1e*È+ ¬Ô€)aß %%Sáãçù—™ššì†‚°0¨m•<’}ÜXÜsþÛÉ}Yo›kÓdôfë{yNÇóÍõÏë;;WKšË8ó÷gýuÙëDUSþWK^–L—|«**Z]«ÐÚÐàT}ªüžE^Vþ³ÿ÷o,‰70 }`;ÚäøÁ`|[YǹLD‹MŽƒÍä•îNYæ¶ØöL7¸npÔu~JJÀ|pÎT–Ÿ‡h"ÞÌ`Y6è #J/ÈCÆqÁìåB»õS€ù Øa,Râ>nR+²®VŸË¼ö8W±‰ä>%®b.Pÿµ˜‹ßtŠ€’ð>6Ð$Ó^ïsûô_›ÔŠê³‰.ÉW50¶á&Ÿw:,Ù@>=Ÿ-i.Zm®Y†«ZýÈóìêZgÉËÀì"*zD•X:Ë4øµƒ*:Qõ]oU³ŽH«7J?ÎË †âb¥ã&ÙÍü€ÉWÌD‰û׺óÀ6‹cºð>yÆ%áMSÄ” 6¾zþå•)bÀvØ!yù+ g,8ôòª°m¾à.?°± ô—Ò ` YpR>‚Jzy1`ƶ”@Í`ïH4< l8(8tFù¯Ä`꯽¬"°÷Œ·„·‰žB-WñŽ)ÀÆØ÷ÎNjχ(ãW@¯øí®]^NY7ò_V¯®N“±8ry²°oÎvùÈñã—õõà2.®¢ô¢ÚÚuë$‹¹DEÙŸˆxñ4,‘tÊÏR½qãÿ½õÞg2kÿº~ T'IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon0.png0000777000000000000000000000700414163163576016613 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“MlTUÅÿ÷þß{÷½ÒW¦™ŽeÊ”/‰-­BGÑ í¢i0š˜&MLLdaÁ‡ìŒ+Cº0© ­4D$®0âGÔj ›åÃim …¦ÎL¾yï¾wß¼{Ý é†ô—œä,Î9»C”R°’Û3w÷|ÿÃ/‡GÇ~i¡°ØÐæÃA„[›IÄë‡m»vre^ûÏ!ìÏÎ~yöçáË{}î›5 zÂ(üÿÜb©¼»Î®½¾uËÆ‘ÿ?à"v¦àÒÄÄÔ6D45M£Õ(B©R P)¨QJ®‰ªQK±XN//;élç³§4D—(¥`èÜWÆ®^{5‚ù~@ël;ìho-纻¦3™æåRy©éá#g­S©˜a’(Œœx¼þR¶³ã´vgöÞމɩݦi"!Tæº_Î<°ÿ<\{,°í5YÈÞšœî›Ë7 !b³s÷÷e2éo°'÷Êå¥Ï ¢Ú°!Sy§ïÀQø¦Ày¬)I&âå|¾Ðã8&„¨"´h±Tn·,SÖÔX2×Óu.“¹˜É¤G9çèŸ_x^ ‚0f†°eó¦X…T*9èq¾OJI|?hÔ˜¡£T(»°:³®ë!RÊèÚX]À“&cò~~¡{µöô?·{]×CÏãˆFtÝS©ÃÐ3M9sgöÍÕ†½Òç8ŽÆ9§©Æd‘¶µnýŽL2fHîòÖ›üuòIåO¿¸ðçø­6§â¢ëzøBvÇ·Z"Ñðùúæô[‹¥R½®iµÅòÒk£¿ÝhnˆÅþN¥#s÷òüi¤ïúñNgÙ‰Y–E¥”²s{G±¥%=@”REÑ¡±«7¹ž[£ëºÔuÝ3 ½¨kúâ»ï{›Rª Ã&cŠ™Lv´·>zÿđ㌃Ͽ¸sûꦦ"ED4 !Í@È¥‘R’jT%{r»æOŸ<òcƹ•o ±[ÛÓ3NÅÝ_(”Ö{œÛc2™Œ‹]]; o¼Þ;¾ySË| U€¦Òd ¾€IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon1.png0000777000000000000000000000700114163163576016611 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“ßoSeÆŸó¾çœ÷ô´ëÖÖ2`¬‚ Û²P؆#N£Y‚˜e!£WÞxaŒ ÀM¼1Áxxƒñâ… $j4Ñl&‚ÁÀ‚Á::ÊÖ–nm=íùñžsÞ×4»!û$Oò\|¿Ÿ»G‘Rb-ÅbéÀ?ͽ~ùòÕ±r¥šô¹¯NL1¾ï¦‹E£–eÇ9÷Y=Í&ïÈçÛCƒ;gÔҽ僋7odŒiP16š[>:}øKWöØSX¼ýìr¹’‚°k¹\y¾§§û[úôÄs3õFs€RŠLoõò±©ã>P`=HÀl*•X©TkZ­6ó}?ƹÏHmeu1]0ÆÄØhîçðpÎmÞÔý›ëy„s_©Öj9âûAB×tiLd³½aÒéÔiÇqˆã8´Ñ°ºU¦3ª¡€”P×H)ï¶Û6BˆAâñ1& Ãåruÿz‚¿‹wµÛ6u]—¨”dC:Uaº."#¼soé…õ/ýþªã¸Ä¶]šL&jdÇcÛ¾×tMjš&=ÇÈÿU8þ°ç3g¿9½°PØmÛ6õ<—Œ ïþN‘Rn¾:ýÂj½ÑE(®i%3j^KtÆ ¶m—¤”ÊìÜÅWþÌ/ ÙŽ“Œš¦j†Ø›©¼tôÈS*€¥á¡Ç?˜¿–Ëu݈ªª›Â ìÌ/ö¾ýÎ{ã*¥‚1F̈0͈TEôe3­©'ß°D€RzfdxàÓô#©B(%‘b±ÔP )h4e|l_ùÍ7^;¡ëÚgk×èSJOôïxô¶ÕjO×j«[,«ÕiLPBe÷Æ Þøh®:9ùÌ[³½Ÿ8 €Hbl 4ÏIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon10.png0000777000000000000000000000676014163163576016704 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF IDATxÚ„“ÍoTUÆŸ{νsîÇL)¦íˆC§3·¶!B² 1FŒ¢I ‚B&¥éÂ+ƒÿ” š¸0¦‘iÄa!!$ŠH£­±i;3––Z¡3Ã0ß÷œsçÓ…¦¿äÙ½Ïoõ>šR Û©×cëÞ+m–GëÍf¯/}J ñâñhqÄMÇæ¶ßkÏÝn×*®¬Mn–*¯ !RÈ(÷¸É… œó®²%¥ßL½8ûÖ›‡?Óu½õ¯ ör~å|£Ñ<$}?)…Ô8çÄãœx'žçÎB)e=í[:}êx.duM)…ÕµOkµÆqéË>ß÷5 št«9пç‘R@¾¸’Z]]–+ó‰ÏGÜÌ­>œÈi ‹ùcS—¾üêè»Gš©Ô^¯'©d†S?øm+pÀÁë7n~povnP©I)½ÉÏåhb õõÝ_~ÍÞùù^o__oç·ß8à E­Üv³éÇ ‹ù#OžlZBJƹHÅ¥Â(¥DQJÕKnfÀ5ü?×½>~§ÕnS¯ã‘åå½V«[¡P(¥¿zàv`ÄÍ\nµÚJ)xœïÖMÆ”¯ëhÀ_صÑéx CLtÇîF"áîÒRñýêsóœh·Û”sNÂaÇ'/ï{lÛVÐ û?Ý=½“ P¼Tlý‰›.Ñ©©‹Éùù…q˲)}ÇóxÊͦoüWùÁúÃÉþD╱Q7¨VkzîÌÉiM)58ýíÌí|áϨi²€1VíïO,§S¿g3éë„hA©ôôp«Ó‚Â¥4I)QaÇy6´ï…šR BȳÓWf>/•Êc,°m«c[VÕ¶í²eš’™,b¡]†®›ºN•c;-7»oŠòÍó1RúŸÜ¼õcnuíAŒ1˜& ,Ëêš& Xˆ†¡+Ã0T<+§÷ÎB.Ú¶9ë&JåÊ©Bá~ºü´Ú£)#d(ÇqÄžxôY29°Þq®ø€cé~-íÑ IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon11.png0000777000000000000000000000677514163163576016713 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“ËoTUÇçÜsÏ}̫ӑÁÚBg(E-ÄD‚,H  T^ I’¸0.\( ‰îÔ4Ñ ýT(‰°•ðZ@Ð)-ö1L›iZ¬so;;sï9÷œÃ’nH?Éw÷ý|w_¤”‚•T*Õ-OæžîwwsµVKpÆ FÈoL%óÙö —R©äðÊ>z1 ¥4 Ó3ggq'c¬‰ó0Å7ƒ À> c¼†a¥µ¥ùÞî]oEñ€°†Ç&Ï"„¶"„Z0Æ`©$Jb)¤†¡Í9_;6þ(ýßB¹µ·çÀÕõ RJÁ¹¯¿»9üàa×À‡}óéô+ æ–eTÓkRsRJT˜.¶‹ËŽk2ư…ŸÉl¼Úsxßë9óÉç¿ !p, ¿9÷EnG÷[—àþót@×Õk7ŽæŒ¾ÊÇBˆêû}'ûµuë³?MM×#°)Ûî}4Ð÷œ€IX~žI¸žio›Ïïý¿ì˜cfàkðd¾ÐI©®(¥ò䉣À¼œ¡íÛÞ¼ãy5­V«kùGÛ‰çÕ,Û¶…uwm„UÈfÛÏW=ïR XÀ’IJLE¥!@f`UÔ¬Wõ4@tBnnnªÇ¢‘HÄÃ|~jßjz.7ÚëÕêš_÷±m[!~mKÇ\$ñx,¼Ÿ9±ÚÀ¯ÿØ÷}ìÎd6.à=ï¾3D„mYÒ-»™?þ¼ùíËä¿ÿ9[(L·ÃŒ1|pÿÞ‹Úàà÷®»tl©²lÕ*ËÕuÅÙÒžz­Þa´0¾¶Tš?^šz c¼ã×;ÉøDÞÞÙ½­ü^ïÁ~¤”Æø†®|鸋¶iÒ´ŒºeZŽe™ ¦irà ]'IBˆ©išRR›;2?ýùÅ™tÎÃOoݾ{z¶TJQ*Ë2¥T•Ô R'º"„¨ÆdC¹­µùÆøàhÅ );î©©©™ îâR”Ò¨A¥mÛ¬!¯45¥gbÑÈ/p B€g:{ý¶ÏfKIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon12.png0000777000000000000000000000700514163163576016677 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF IDATxÚ„“MlTUÅÿ÷¾ûÞ}ó^gJg¤Ì´3­[h+a&š‰1¦˜5Ý4wî4nˆºãÂ1Ä(D6 ×’˜´Š±‰Ÿ!Ä*I…BK Î Zj©óñfæÝwï»÷º¤Ò_rvçüvi­a3ž×\ûwã@½ÞØÝöý‚`„Xg,z+›Io»²¹î  :g.ÏŽE£ ¦„Û6± œsOJYK¥vüYx<÷!!F€øŒÅ½ýÞL©TI¿{ø­²ëº°F€”RX+e*©ÎyêæÒrrcã¿ô ÇÞ4 ñ0À'Ç?Ÿ¼~ýF¿ç5­¯ÎLî°m;èÞžXlxèJ~ÿè_==É»–i !B†2±úÏZá§Ÿ/ ó K/ývþbÁ0 Ðß·³6¼g`.ß $]¹üþÑܹó—Æç®-$Ã0ì˜_X<0:2ô´‘}dèT©|;ƒ‚ὃ£y¾€EðîeÎe3éµ¥›ÅçªÕš-„ˆïÂÅbiÐq"ÒuùòÄø¯0fjddè’ï3ìû ßZ^ÉÆ§Ãu¥Ööíù ¶ ¿/{Êóš/jÐÀX ®ëh)•ÄÖã•­t¥ÙjaM1q*•ôc±Žp[g,\.®Œm%˜»:?Þjµ¶ß6¨mI<°«oÕu麎œ»vcb+ÁÔÙé×}ßǾÏp6“^Ç…'òßRj+J©ª×ê.Î}ÐøÄÉÓggg¯îb,ÀœsüüسS¤·7yzïžG'JåJÜ4ÍH¹rûàôítoOr.îù#¤ªÕú“Œól±XÊs. ¥”~æ©ÂÝý'‘ÖÂ0|íûé_ŽÔ ǦTYÔòˆ}—ZtݶmALÒE‰7›ÍèGÇNdû²;[Ÿ~üþ;”Ò/ïŸÉ”RúýÙWWï¬%,ÓÔÔ¦ŠZ–2MS[–©!Ú0 ]¯7X>7zƦô8´éÎÆ«µú+åòß™†×Š"Ð!DÛ‘ïŒu4º»ª¸NäkøB€ÿgÇvfµgÛIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon13.png0000777000000000000000000000700214163163576016675 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“KlTU†ÿsνçÞ™Î2¶¶µ”*8´¶Vj…Bˆ`BÔßÑ+%jLª’¨‰&µºR‰D‹‘„wFEÃÈ¢ÖPKRðASB;±ÊÌô¾æœ{0Ý~É¿ûÿo÷¥V2?Ÿë•A¸©TZÞèz^\JÉàYqkfÝÚ†kÖTÿ±²O® <ϯþàß=öíΡ·^?}S"Q%…4+BPß÷¥¢(„¼Z_W;ÖÓÝõ®¦1(¸žŸxåÕÁ³ÇOœÜ©”"##£í¢ Š…aÀÂ0ŒH4J!;.ÏfúO~ÿÓ!¥õ¿à£áCŸMM¦70¦)B¦Ó—"ÕñØ•¶–äï]© õ7 W¤”DTDÝâbaëÈÏgÞmbj¦ï—3¿m1 ®@ˆzh÷ýs{_zî0€sׂD¢º§ëŽTÏè¯c»'&ÒõAĦÓ3»RmÉm¬½cÓp6»°ž2ŠŽöÖÒà›{_pÀ€òµLøq]Sc~æÒ쎫Ţ!„ˆV*•8ËdSU±h`Y±à±GúO8‚s¤£½uÔ÷}*„ s™ì]š”A4nY2T!éìL cš››9ŽÓ§ày~­‹U! à”\^M ”Ê”Ë6#„@×uNkkk¼h4X1KÎÎÍïXMpñâd¿mÛÌu]ʹÐÛnmZ0M3ŒDÌpb2ýøj‚cß|÷¬í8Ìq]¶¶±¡@{ºïç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“_hSWÆ¿sν¹7IM ¦I´ECL® )4YÛ•áF±:†2n"†S烲 ÆØÓ6„1s²el †²uì2'8çÖjÿ‘’šÆþM5©½½÷æžsÏ^:pÒ|oß÷{ûˆ”“Ïv]úáç7Ãáu²­mû¬‚Àòû}wcÑõ¿ƒÛ÷ÉÛ®Ï|òùw?ý¶KA3í¼²¿Ä9'uÇqÇ©:‡ŸÞ¶õcEaË €iÙ¡Óoput,—@¥”¤XœjAÙ•’ !T!„Ÿs¾qjz.ú°º¸¡»³ã„ª°% gÏ]8_(ªªEa2‹ZÙL{iSKs.•LŒ…Ã…)ŽÃ9áTÊ÷»ÿº1ô.(ù‰ÉÞ›·FžÑt€ÀíÛýüôÑ×õZ Ö® ¤ÑšLÿ34²'?1œïнÉä¦ïÙÎtö£ÒB%¡0†ÖTrñäñ7Žø À8€G+p%k*ß+ÎôTk5]á«Ûõ:_ZØhð‹àšßÓ÷Âï.âÉ\LnÙ|ݶmjÛu:33—V8¿ß/ 5•ü«°!û²ì>pV|^/q¥+! ”L®&RN™¦E cml Yº®»>ŸOLÏÌ=»š`|<¿{iÉ`†a2UU9Ç›Jº®¹^¯îNŠûVüzùC†a0Ó4iSd}…¶mj@×4W×4×4ÌÖá‘Ü©'Ïž»ðm.w'eš³l›f3í?)eìÚŸƒ¥r9¤ªªÔ<žÙàšÀȺPèN$¾"¥$_öóÞ­á±µj­Ñ£y¨GUewWvþÈáW»³™Žg®]|kÙ4½LaîðÎÕjÊ0–3ªªŠõ”Ë÷ýšæ‘ DnI&j_{ù}³c_wfÛ¿ŠG£…1IñRBãØáJ7ÝҼтS!y®§kîÓÇ>Ô4Ïùÿ½qåX/->2öÏÏ/4›–Õ€1J1<šóUÊòâÞÞÑÄæ–~—pøwZ»hEwymkIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon2.png0000777000000000000000000000700114163163576016612 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“ËoTeÆŸïv¾ÃÌ™Òv¦­´ZÄ ÕÁ¶é/E*™ PºB‚Ñ…[ݹñ²òÐq¦D£!R„DŒ±’” .r©h±´¦µ-ôÃÌ0sÎùÎ9ßçM7¤¿äIÞÅ“ßâMbŒÁrfgçž95öÖÅKW²óó )¥žH8ÕL¦ç¾½¯|J6þ¶¼Ïÿ;‚ pŽ=ññØØ¹ç]Ïs\׳ƒ0ä¾R´üÏ|¢Z«mÈõË•»—nð¥Ô *ê }óÕå+¿ç„õœsÉ#„cˆ6†lé{ºªµÎ–Ëwwÿ11ù~Eq Æ?ñãkã×wøJÅj5—Ù¶ íXWÜœÍLF‘&#?vn}îÙŠ6Z„aD¢(*Õ%œcuv|ÄçæoõÝœžî–„éí^ÜõÂîéÇ×o°ijz&—¿½Ø†aCþvagKKê$Û¾c÷{Å;¥.ƨi]ÓR}õå]ïø À€Ê½L8ÕP¿z©PXês]× Ã(¡‚€ÓbñN·”RK)u¦·gÀ0îÏpssê¬ï+ª”¢‹‹KOÒ Œ¥eiÛ–º½½ís¬@2ÙpÈó<êù>­Tª-\ Áµà!¯$0ÆÌTk.JˆMº„/¥Ô¶”:Ÿ/<µ’`fvn«[s©çº”2Ѧdã‚%„‘¶Ôs ·Wœ¿xy_µVc®çÓúÕuúȺö,Kh!„Q~°qâÆÔ;÷ýàw'?½99Ýåyõ}ŸnLw~OŒ1­WÇÿ.•ÊõœsÛ¥ŒŽŽžY›Û¶å c,ûåÜÞ¿&§ÒJ©Xl•mY–éîJç_Üž#ÆDQ´üú·=ÏqÎÌO#£Î·G· Á#)eèÄã¡“pŒ›U1[oXßQzcÿž-K¤Àûò‰ô†ÃÉdc1fNŸ>ÓD@(F‘¨€+¥hoOWþÍ×÷|bYâðÿ[X¶Ì ‹Å×¾ú:{ö×óÍ¥R™ !Lëš¼ÍÙL~` ÿÚÚ‡Ú†À¿QÐoiú´ÃIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon3.png0000777000000000000000000000676414163163576016632 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“ÛkUÆ¿sfæœÉÎLÒmKÌu{Ù¦[+]µ¡^ª¨ñ’¦Ð[C…¢}ðÑþB_ "ú$‚ R(^JM‚ÄPRh½¥5VÓÔÔl²mn›Ý˜f63gnçø`…”üà{û¾ßÛG”RXËÌìü#ß~÷ã‰ÑÑß÷-”7‰ 0R55þýùû®tuv¼—ÍnZÛ'ÿ â8¶ú¿ºðöO?_yÊóýZÏó-á Ýó}Í÷ ÃQ“¯û쥞ç_¥”  űóù½g®O<§éZ½®é)MÓ(ˆRŠ(¥ˆL¤fY)d·oËܘœ~=‘2: Ÿ.W–ö†nIɤcÛá–LËòƒì)ÈD’ ¿É ßpøPWÙqìzoU<[(Ürwd3o‘¹…Ò£½}_ÇI:Š"šÛ™-uuvœ0r'Р½xsö™¥¿–7'IB)çsmÛOê×Çog†QG)U ÷Ô¯tuvœpw3  /ÓÚôKµºzÊ +Ž“¦™™¹zÛuóŒ3É“ùüîáÿ¯åüÆé‚ ¤Q“åÛ+ûh%›8ãÒ¬1eKsã‡X‡tºîL4 BêV½s¦K)éõJ¡è‹€€F¨ImÛ8ç’›\V*K­'˜››\ø‚"  HhzCíc†äŒÉR¹rh=Á¯¿óyέV7hÅÕ«c¢· å±ý_:xàé¨F“‘‘ÑŠ7gra5rÎmÆ ¹+×V>x £“(¥ ¥<þÇDád©\©}óô;[]·ªéº.M“GŽm DZem+e‘TÊL¶dZÝt¿Á˜ñ1Jé§;Û¶}25UT++®Nù]*U“$‰…‘EÙ}ï®Åž£Ýï3fœ½ëw~Ñ=ñgáå¾þÁüåË£›«ÕU›\675úí{ó‹O>±¬¥¹ñ,€/Äð÷}®oE&8ÌíIEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon4.png0000777000000000000000000000677014163163576016630 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“KH\gÇÏ÷¼ó¸w&QgÔF'’0j´0‘ŠÖªÕbmK’]BÉ"›RÈ"B „J¡E¡ÐU¨­ˆô±h –Bm«QcR«ÐÚÔøˆF£ã¤£‰™¹ß½÷»÷ëÆ‚‹¶þà¬Îÿÿ[ƒ”R°ŸÍÍ­Ú‰Éß.ÌÌÌ&’[©=ó‚iŠC¦iꙌɅ8“Éb˲”í8ÐÜÔ0zùKç|>í1p¤4ú¾û±kþÞR;!4ÊÕ1!cJ)ä)K×åRJíûþÁ¶ëo½7êHW cco§Ó;µ”Ýó< äX'ª*vª«â÷¥ëâá‘ñcã·-X]}àÏÏÏs:ÚÛäÚÚÆëe¥Å Ídªnðç‘›Žíä9ŽD±Ø‘TkËó}0µ7 Hô~öÕùòò†.]×Ý,+-¹‚ÆïL~´ú`ý´ëº( e_íh½}ð›_º‘5EÀó\dèÁ¯ñ“'»ÏrÆ”Æ5¯²âøØÿ”úrsÝqK)ÑîÓÌI¬åjšæù|š‰|‡zmÛF–eãLÖŒPF)W„x{û•ƒJ©U˲ðÞøq0°8gŠk\mocF6™á°¡t]§~¿kšæEwÏœj‡RÚC:;;c|·¹¹ÁK§Ç—ï¯è”RÅ9ÆáŒqÊ(c”"B0TUÆ·^y¹µ›1ÚÚ÷ÎN/.­\ìÿaèÄôw›B`ÀêÊTÝs‰¹Haþçð-H€¿'§_“¸X®¯IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon5.png0000777000000000000000000000677414163163576016635 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“KlTe†¿ÿ~fæ&è8¥ØiÎtŠ Ie¼”´C¥Ô£LŒ £h‚‰ ØuC*‰ÑH41ºóŒ‰c¬4”ƶ ¡ ˜4¶RÒv„éÌ …é¹üÿ¹ünÔtA쓼»ïyvÒZÃJªÕzáÂÅ©g.]žëªÕêwºžÇ !Ü\vÓÔöb÷'Ú2?¯¼Gÿ‚ HŒŒž}czz¶WJ¹VJiÙ¶#lÛ!®ëãºZ)õö<8öÒ‹{ž‚ßú/à¡9üÃé¯W Jª”R>ò¤ÄÆ2]¶mbÛ6q;®G|ß²Ùso¿ùZ1f"­5ŒŸ™xg¡T~Bù¾¥¤Âœs¿¹9}ss>W Ÿ9—;wþBºTº—Râ ¡GqüõƒúhýÆ_÷WªÕŒ±¸ÖZgÛ7VŠ=€Éí›Ú {ž}ºðþ½0tü»Lk¦ÅËç³VcÙÞ‚&&}ïk•'Ã(B–i:ý½`nÏ>q4ŸÏùZkHÄc_âeÇéâ‚G‚ó(ßÑ>ö?2ÀÐC}=ÃA¢ ‘í¸÷aAŠ3¦…Q:úVÁ²ÌσÀG¾ïcϓ͔3Æ" h„ЬNIJ…B1‹ŠQ¦9çziéfa5»V»ÑíI‰¥RX„Ø2‹ŒQÍ‹~Û·Z`n¡ô”ô$ö•1£F›Òw:?qqÿ·'†Sóó¥Î+å«ß¿úò¾Go'ŸýeòÈb¥ÖEÆ„i¹û$ÒZ¯?|äèäÉS#ë0ÆšRµfZ*Åâ¶Ñ}=Ç(£þï3—»^­v~Ð&„XK)ÕZ3õmÝ[GZkðç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“]h“WÆÿç=ç¼yÖ|Ø4­±]›´£¨Tí¢Í´«z1d ŠzSdº/Zº±E™ Ã1ŠNDvå¦^û¤‹T­UÑÖHjjÒt‚Mߨi’sÎ{Î{v±*lëž»çùÝ=Hk s™.Ît>}´5›}²Ü.͆8ç”Ìã±æÑÕÉÄÙ¦¦E©¹}ô·@Jå»~cøp&“íæ\,àŒ¸f¥\ÅåJc²Ê˜›ìJ¤võnÛíñ˜¿ÿ#p¤zíç_R'ž=+®ŽSÇ97û3år¿|YÆ•JW«Ì`œ«XKóã#}û×y-O i­áÖð½ SO7 Gø9%DF"áÙö7ZóJ)4ts¤íöȽH>_ð1Î ÇqtO÷[©ƒö­G3v)1tcä´T*"‰EŠÉ®Î+pû¯$ ñÅ™s½—¯^kB®ëêÏO|´… O·QJÂc7 T“]ýp ^å7¸üþžwGÇÒ÷ŒùC¡ z˜Îl'UÆV˜¦é*¥P<özê_Æs¹´÷½›³¹·ãñ–ªa ¢5ª£”jBˆ‡k`ÚÛÛN)×]©”BRB¡›®6\„BOæh­óŽ#ä5<–%(¥:—/XéGãïÎ'°íÒ*!„!„0’Œ=„¯ÏÓZœ±iGÇ’¾3§}õ‚ÜäÔf!BA0àŸ6–.nÿ¡ô|–`Œu:iŸ×ôz-ËkY„Rªë#u/Öö$?ÅŸ5,Ë:w쓾7¼³~Ê4M—R‚ÆBPçº*ê*7$¥òH)Qcc´¸¶'9€1¾ðÊ€À¦ÜäTïààÐâìD.¨”Â>¯W-¨ ñÖx‹½¬cI&\ºß€øc¢~]lß]IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon7.png0000777000000000000000000000701614163163576016625 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF)IDATxÚ„“MleÇŸy?ffg¶ÝÛ­ºt[Ú´BÜÒÝ-«áàA# UŒ†hB¢QãÃÁÈI BÕƒÑD½S#1õ ÑD Å[`[",Ýí–ZvwvÑy?f^/`z~ÉÿöüÉsøkJ)XOµZ^<ñÙ«Kå¡F£åBJˆ×Ý\É}•LÞ7³þ^»%Ò·æN͸R\z„y¬ƒqa3cÈõ<Õj¹‚1&2™ôÌžçÆ^×uýÆÿ!ýðo'fÇ«×kyÎE\ó<†\×Þç¡[-Üj¹Èm¹ØcŒww'ÿ:ðæ¾í¦¡;šR ‹ï^[Yã‚Gçˆ*î‰G›ý}½¥ ´“§ú ógååJÈu=,¸FÞzlÿ¯îÐjN#[(œùXJy¯”¾ÖÕÕy=—Iÿ'o`F>Ÿ˜|áÇŸ~ÙÀ9G¾øã‡Þ~ïÝûò>Æy!ímm­­#[À§pnÜÌ8šÍ¤K³s…•Êjˆ Nç=Èã,K)Q”R•J%ÀÜž©'·?vŒq†„ÚüÂÙ!¢HBÆ âñØpòùÜ»Ÿ~j4i—±x¬IÊåJ´V¯ŽÓ ›7 ¬è”ÜQ ëôâ¶m£BH©@)Ì%9|ô×éø¹s†ú×ú6¦¦ï$¨Vë:Nó‰ 4L0GÃ[\Å+Œ±úvêûWîöBåÚßcBM©:]C»ŸÙy„Rª ]._*&?ùlâÈíÊœ=ÿ–Ssrœs$¥Ôº?kJ©ûôûôôL‚êTY–åöö¦®äsÃ3ù|vc쯖¯;~_úº®wBT¢³£6”~`LSJcì¥w¾w¸X,…-+äÛa›GÚÛšmá°c‡mf…B¦2-Ó0(Õ©êˆÇþy(Ÿycç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF%IDATxÚ„“Oh\UÅ¿{ß»ïÝ÷ÞhÞ´M:NҴŨ3“‰*ÄEP+E“øÅV©J‹v#".TP(¥Š ®T$´m]´¸hÅBi©%µÍ8“:1V“™ÎL“&“™$sÿ¼{Ÿ›¤dÓxàðmÎùÁ·8( CX­ÙÙj׸ĵB¡”¬Õj¾Ò$&i´ÆcWÓ]÷‰ÅZ.®Î£€Rʹ’ɽýïd±WpÑ,¤ð96çs!4cŒ !Y"qÏ…Á§žØgYVý Pʹ4úáì\õ!)dŒ ã˜1¶|9n4F£ÑÀŒqÇòoì}y€ÚV…aÙÜø»ÓÓ3ƒ2¾Èüèµ-í› Jk”Éä¶fÇòŠÅë.ã .TOOúÌk¯î@ÕùZ:;–ÿL+ ‚­_½™JnûF– Ð=CGŽ=ê—3m‚ ¬´–ï¿wàc÷žW^—2ØŽ1‚ˆç-¥»À€ú²óðk*™(Œ\ÝY*U!ጷc!ƒLÓ M“„ñøÆspn¯ãïè;Ë8Çœ <šÉuµýe&›»câïœõ½csr øQÿÆÐÐð®@xi©a™“SWJ‰ä7Ët­>X„üÕÛ»}¾Z7ëõÃÜØÒÌË• E‡¿_}摾‡?^ °¸¸”zîÙþ !„áîîdÙ²,M©­Oœ<õü*Ó3ýA ¥²ˆ9Ÿîr˜RªJõÔT±íëo~w»òÕ?'ÞªÏ×Râ P¨yÃúÓ( Ãø¡O¿:á·‘Jmñ¼zûæMã©ÎÄÅîtç!],–­-.nÕJßGL3fF¸.êÏ%¶u ¢0 s±û“ƒŸt½Tv]ÇÑ‘ˆÇ<Ï­y®;çyw]Ç¡X±!aÔoZH§‡ ÃøfeLD±øÇ{þÈŽ5»®£VÞr]GÙ”jjÛÚ¶-ÝÖŸIvÞû½a‡@¢Us6 ¿T¾ñâå+ÙŽB±äk¶më&¿‰·µÞ5×q÷–kQ¿é(üÀŽNŽa¯¥ù´IEND®B`‚src/uk/co/mccombe/terrain/images/busy-icon9.png0000777000000000000000000000677514163163576016642 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚ„“Mh\U†¿sî¹sî½g~’ÖÉIÚÐL231]1D¥]TPŠEÁ– mSÁ]± Q—âBq%±bÝIª´XÅ…X$"Š]$fH2‰M¢6óÓI:“ù»çœ{Ïq“J65¼»ï}¼Hk {©Õ꯭ÿ}²TªävÍ.ιiÒq=ËÃÙÁ×ÿ¾÷=A`ç —‹Åʸ¢‡ q@pI9çØã\qÎ;´Òý¿=ÿÜñ·LÓlþ'ðƒ€ý±°ôn½¾3.¤ì•B"sÌ=ŽÛŽÁ=Ž=αyž×vÝøâkSgÏÐP¨Ž´ÖPXY{g{»ö’ðe·”ØÑk¤úzï)¥P~qùðÊÊ݃åÊ}»ãyXpáÉýxáÜéÓ¨¾Ó8º²º~E)• ‚Å¢‘ílfà{¸³€Q½þõ­W~šý¥O ‰¤ï‹·ß¼ôª1ùôñ}?aŒ̱۹lú}˜€4vS€Û¹ìཹùü‰b©l !Cç}—æµ7¿M,.Ø3“ã·ãñƒÀ£Y2 2>ûó¯CRJÜjµ»puk‹!„`mmÃîíK~û0<œù¸ÝîžÇq±TŽBL ô×~­õ?އB!p2éz–e)Û¶Ôü|þÔ~‚Íbi"•Jr!ŽD¾qîüÅ©ÍÍRÔ²¨j6[©cÏNLÿŸàA}çõ±§F»ÇÆž¬ä2øä '®3æaÆ‚J¥ššùꛫ*¯þ¹ñF«Ñ~Â÷ÔÝó''ÆfÖº÷“O¿˜›[x̲-f¬–Lº…ÌPúN6“¾Iñ7‹åcÍV»_+•%&IÃбhôÁÀ‘C/#­5!§¦¯~þ^å~ÕÙý‡ç8v9lËqlaÛ£45M“Bt4i çÒaŒ¯=“)¥¼|ë».,Vã”ReYT9ŽX–¥,J¥T™&Ñ ×­ öφq$Ú3g/–+Õ³ ù¥#åJ5Za¢* ‹D¢§vøPj=| 7Àøw Aƒ5GÁ?IEND®B`‚src/uk/co/mccombe/terrain/images/FrameIcon.png0000777000000000000000000005304614562672217016475 0ustar ‰PNG  IHDR÷ü̲æä pHYs  šœUØIDATxœíÝwxTÕÚð5„0„ЛRT¬ôÞQTŠôÞä"ŠTÄŠPDŠô"¨  Ò‘&QŠHU¤†B óýñ~"%3™ÙïÙç¬ßóøÜ{f–\˜5gŸ³÷ëóûý~>¿0xùe 9Y;7ß L›/®„œ`ï^à¾û€;µ“„NáÂÀ¢E@ÚIÈc|,w‡™?hÖ 8rD;‰™2Æ­Zi'!M,v¢J£€.S£°v-Pª”v3âãÖ­=€sç´Ó;QÈñÊÝ©€nÝ€‘#µ“˜S¥ 0e 'v2…ÅN¼rwªÈH`Ä`èP Cí4füôPº4°|¹v2ÅN6,w§{òI`áB ~í$fìÛ'øC‡j'¡pb±…—åmqð Ф °d‰vsÚµ>ýTV1È=XìDaÇ+w[äÎ-OÒ÷è¡ÄœÑ£J•€={´“P¨°Ø‰Œà•»&L\ž4÷‚ìÙI“€ÐNB©Áb'2†Wî6jÑB:»åí$fÄÆµjï¼#‡ý}XìDFñÊÝfÇŽÉá/³gk'1§aC`Ô( sfí$”R,v"ãxån³¬Y™3W_|>í4fL›”+lݪ„R‚ÅN¤‚Wîn1s¦œôvâ„v3¢£/¿{L; ] ‹H ¯ÜÝ¢N`õjàÎ;µ“˜qò$Рл·wíØ„ÅN¤ŠWîntì(ǸzÅCãÇËSõ¤ÅN¤ŽWîn%ÛÆ Ò¦ÕNcÆ?eÊë×k'!;‘#ðÊÝÍ,ñ±‡k'1#cFÛºµvob±9¯ÜÝìþûe|l™2ÚIÌ8shÓèÞãcMc±9 ¯Ü½ !xê)™2ç•*S§yój'q?;‘ãðÊÝ "#/¾%k¯Œ]¶ (UJþ“‡ÅNäH,w/éÜX¼¸ñFí$fìß/Å3dˆvwb±9—å½èÐ!»x±vsÚ´>ûLº£Ôc±9¯Ü½(W.`Þ< gOí$æŒ#÷áwíÒNb?;‘ãñÊÝë&M:uNŸÖNbFöì22÷Áµ“؉ÅNd^¹{]³f2>öÖ[µ“˜ <òп?ÇÇŠÅNd ^¹“8~\ÆÇΚ¥ÄœúõÑ£e ]‹È*¼r'̘ôé¤ñÈo‹¯¿æøØ”`±Y‡Wîô_ß}'G¸?®ÄŒÌ™å ¾Aí$ÎÃb'²’G.Ñ( µk«Vwß­ÄŒS§€F€—^âøØK±Ø‰¬Å+wºº¸8àñÇå‰z¯xàù÷õúøX;‘ÕxåNWLœ¼ÿ¾wÆÇΛ”. ¬[§D‹Èz¼r§”Y´HNµóÊøØÈH9Ñ®m[í$f±Ø‰\Wî”2Õ«ËÕlÙ²ÚIÌHHÚµºuµÓ˜Áb'r ^¹S`Ξ•yéÇk'1§bE›/Ÿv’ða±¹ Ë‚óÅ2#þìYí$fäÉ#_¹²v’Ðc±¹—å)8:K–7ݤČ€ûï>þX;Ih±Ø‰\‰Wî”:‡M› j'1§ukyØ.S&í$©Ãb'r-^¹SêäÌ üø#ðì³ÚIÌ;Öþñ±,v"Wã•;…Îäɲ\§ÄŒlÙ€ñãZµ´“†ÅNäz¼r§ÐiÚX±(RD;‰G> ¼õ–=ãcYìDžÀ+w ½ãÇ6m€™3µ“˜S¯0fŒ³ÇDz؉<ƒWîz11À·ß}ûzg|ì·ßÊ?›7k'¹2;‘§ðÊÂköl eKF’)sNÁb'ò\V‘šGÖ¬î¹G;‰qqrÿ /8c|,‹È“xåNfœ>-ãc'NÔNbN2>6G÷g±y¯ÜÉŒn&L òÎøØùóR¥€µkÍ¿7‹ÈÓxåNæ-^,ÛæÔNbFd$0dСƒ™÷c±y¯ÜɼjÕäj¶\9í$f$$;]»†Ð‹ˆÀr'-ùóË|—.ÚIÌùôS)Þ}ûÂóú,v"ú\–'}#GݺÉ®äÎ L™T­º×d±Ñ%xåNú:t~úÉ;â<|ôQh^ÅND—á•;9ÇáÃ@³fÀ‚ÚIÌiÑ><øñ±,v"º^¹“s\Û«àói§1c bE`ÇŽÀ.‹ˆ®‚WîäLS§Êr½WÆÇfÍ*ãc~8e?žÅND×À+wr¦ÆŸn»M;‰Ç޵ko¾yýñ±,v"º^¹“³8!ãcgÌÐNbN:Àر@–,ÿýg,v"J^¹“³eÉ|ó\Ñze|ìÌ™@™2Ào¿ýûï³Ø‰(…xåNö˜3GÆÇ;¦ÄŒ¨(`Ä™2Çb'¢°ÜÉ.;w 7j'1Ãç“izsç»vi§ ;QX±ÜÉ>ññ@çÎòt9Ù‡ÅNv¹‰I®’)0nœœð–.v ‹È^¹“Ý–,‘ñ±h'¡ëa±Ãr'ûíÛ'ûâW¬ÐNBWÃb'2ŠËòd¿üù¥8ž|R; ] ‹È8^¹“»Œ-%ï•ñ±NÇb'RÁr'÷Y»hØسG;‰·±Ø‰ÔpYžÜ§T)`Í™™N:XìDªXîäN9rß<ÿ¼wÆÇ:‹H—åÉý¦MÚµóÎøXM,v"G`¹“7lÙÔ¯lۦĽXìDŽÁeyò†bÅ€U«€ÇÓNâN,v"Ga¹“wDGÓ§ýúÚi܃ÅNä8\–'oúþ{ U+ 6V;‰ÝXìDŽÄr'ïÚµKÆÇnØ ÄN,v"gJNæ²}xåNøäàÙgsç´“8‹È¹æÍzˆåNô¥K&M€ýûµ“8‹È¹Šä²<Ñ?*W–ck+VÔNâL,v"çJNZ¶À­pDÿ–/ŸX×®ÚIœ…ÅNälýû üó?¹,Ot5cÆ]ºp|,‹ÈÙ-’AYÉÉÿü-–;ѵ¬_/ÛåvïÖN¢ƒÅNäl‡%Jÿý¯¿Íey¢k)QBîÃ׬©Ä¼‚YìDNæ÷mÚü§Ø–;ÑõeÏÌ™¼ô’·ÆÇFFgÏj§ ¢«y÷]à‡®ø¸,OˆéÓöí“'µ“˜Œ Ô®­„ˆ.µt)pß}@RÒÿ1¯Ü‰Ñ ðóÏ@Ñ¢ÚIÌ8~¨[èÛ8^; 2£eË«;À+w¢àlÞ ”- œ>­ÄœGÆ—«y"Òá÷õê3g^ó‡ñÊ(P{÷uêx«Ø`öl tià—_´“y×\·Ø^¹fï^¹Ïµs§v=7Ü 4o®„È[V­ªT¯ûCyåN”R,vqú4Тгç5ïùQ?4mš¢bXîD)Ãbÿ¯?”S±þÿ,k" £:L‹åNt=,ö«[¼(UJvQx  |ýu@?…÷܉®…Åž22}$gñQè¬[T¨âåø XîDWÃb\‡À!rº¥ÎÉ“²2öÇÿT.Ë] ‹=8#GU«Ê¯¥NçÎA;Àr'ú/{ê¬^-ûá/™-MD6 ˜<9èŸÎey¢K±ØC'"xçàÙg½5p‡(µ6n”ûìgÎý,w¢ XìáѸ±,×GEi'!r¾¸8 L`ëÖT½ —å‰{8M ”/üþ»v"ç{òÉT;Àr'b±›ðÛo2hgÆ í$DÎ5j0n\H^ŠËòäm,v³|> woàõ×4¼¶ úÇæÍ²’—c¹“w±Øõ<ü°ŒÍšU; ‘¾øxYÙúí·½$¿:“7±ØuÍ™#Ûå6nÔNB¤¯{÷;Àr'/b±;ÃÎ@ÅŠrOäUãÇËn’ã²àå—e|lD„v¢ÿš5 ¨SÇØ}öK±ÜÉn,ö‹‹:t¦N y,ǪUKvdÏ®„è¢?ÿJ”bcUÞžËòd/ûEEÉùìxçjöûï2eÂz”'Q@’“-ÔŠ`¹“­XìWçóÉ~Úræ I4ÇÛµ ¨T ;V; ðê«ÀÒ¥ª¸,Oöa±§ÜÞ½²}l͚о®“uï¼ÿ>ÇÇ’Ž”[EÊÕÊr'»°Ø—t댞×w¢Ê•e8GÞ¼ÚIÈKöïŠÒNÂey²‹=8‘‘Àˆ2…*C†ð½“,]*ÇÖ._®„¼"9hÙÒŰÜÉ,öÔëÒEÞ/~3ï§íï¿e|ìСÚIÈ Þ|X¸P;Å?¸,OÎÇb­ƒ¦MŋͿ·–6m€aÃ8>–ÂcáB fM¹zw–;9‹=<’’dˇêe0­D `út P!í$ä&Êï­ýûµ“ü —åɹXìá“6-0h0a‚wÆÇ®_/÷áçÎÕNBnqþ¼¬ 9¬Ø–;9‹ÝŒæÍ+€[nÑNbFl,ððÃÀÛo«oU"xçÙúæ@\–'ça±›wü¸<é;{¶vs4F¢£µ“–.•6tŸýR¼r'ga±ëˆ‰fÎúôñÎøØéÓr倭[µ“mŽ‘U/‡;Àr''a±ëJ“èÛøö[){/غU ~útí$d ¿h×øë/í$×Är'g`±;G:ÀªUÀ]wi'1ãäI Q#ëà+1rˆe”«Ãñž;éc±;S\Щ“L™óŠ”KW²r%Pµ*pîœv’ëb¹“.»³ùýÀ/¾({ã½ P!Y¦/QB; 9ɱcò{bÏí$)ÂeyÒÃbw>ŸxöYoݽ¨X3F; 9…ß´ooM±,wÒÂb·Ë}÷k×eËj'1#!hÛV¦éY°KaöÉ'ò ©E¸,Oæ±Øíuö¬ÞˆÚIÌ©TIÆÇæË§„4¬Y#¿µ“„åNf±ØÝaøp {w){/È›W ¾reí$dÒ‰@É’V~^qYžÌa±»ÇãK–7ިČýûûï—åYòŽÎíü¼Ê’…Wîd‹Ýš4‘_¯hÝZÆÇf̨„ÂéÓO®]µSgòd–;Àbw·¤$à…dËœW/.Ûå ÖNBá°aP¡‚ý”åNaÆb÷ŽI“d¹>.N;‰Ù³ãƵji'¡P:u (SضM;IàŠ—)‘‘Hã™?ˆd‹Ý[š5–/ŠÑNbFl,P»6п?ÇǺɓOÚYìQQò;2eËr*…‹Ý›î¾[Î¥¯][;‰ÉÉ@ïÞ2>öäIí4”Z_|Œ¯"8Ÿ}Ü~û?ÿÓçüÈœYæ7l¨˜Œ\ƒÅNçÏo¾ ¼ñ†üw/¸ývàë¯bÅ´“P0~ûMiŠ×N¸Žå‹É%¤Ü‹ÇL¾ý66­F¶`A`Ú4¹`"çÙºU–ãOŸÖN¸Ö­¯9¹ðêå~AË–ÀçŸ_ñ²ŸˆÅN[¼XNµ;tH;‰‘‘rÚY»vÚIèRgÎåË¿ü¢$pE‹Êí®¨¨«þëŸ-?~¼ülßÊhä,v Fµj2>¶\9í$f$$È,ð®]­›,æjO?mg±gÌLž|ÍbR:8fÓ&Ù"0cF(¢‘°Ø)5n¼Q®à;wÖNbΧŸÕ«ûöi'¡I“dEÚFƒ÷ÜsÝvýeùýhðÒK²w5""5ñÈf,v ¥#äÙÏñFž2D;EpJ– è§·,¹víäa‘ÿŸFC.Ãb'Ó&O:uòÎøØlÙd|ìÃk'q§Ý»%ì<¹A9+!@¡)w@~á¦M“Mr;iùí7 ~}ïlÃM“xýu™2ç•• e•yÕ*í$+TX¿ˆ‰ ø§¿,¹õëå>üìÙ!{IRÆb'MwÞ)ÏõÔ©£ÄŒóçW_{ 8qB;{¼ô’Åž>½¬`Qì@(ËŽ•?ˆ}ûzgÌ£[±ØÉ ²d¾ýV¶ß¦ íÇ•c͘!çŠlÞ¬Ä~3gʾpõï/¿‚ºeùË=ü°ÜCÊ–-,/OaÄb''š=[ž.?vL;‰QQÀÈ‘2eŽ·w¯Ü.>zT;IàêÔ‘/µ©¸=¾räuÚ4ù&;°ØÉÉvìýð7j'1Ãç“í[ï¼ÃƒÃ‘”$§.[¦$p Èv½ìÙSõ2á]çÚµK¶yŒÖ·¡a±“ÓÝr‹Œ£nÑB;‰~¿lƒzðAàðaí4öxå;‹=mZ9 .•Å„»Ü9R²C9CúìÙ°¿‰ÅN¶È”Iö‡ø¡|zÁ‚òÀòš5ÚIœïûï÷ÞÓNœ7ß*U ÉK…wYþreÊ_}Å[§a±“­–,‘ñ±j'1#2RbéÐA;‰3ýý7P¼¸«µjÉs%!ÚiöñÓÕ«R¥€¹s¾-]‹lVµª[[¡‚v3€Ž'žàJèå’“åvÅž/ðå—!=ßÀüÞ’#GäIú·Þ’ûI¤‡ÅNn??°p¡žW Æñ±—ëÛWÆÛ&"Bî³çÊÒ—5»,¹ºuåÛJ›ô)XìäF#Gݺyg|lîÜrÐIµjÚItÍŸ<ô\½Ûæ7äð¢Ó-w(RDîç`ø<…‹ÜlíZ9{ï^í$f¤M <ý´vÈvë´“®F –†mŽúG>mß.÷ËÆÓNâ ,vr»R¥¤àkÔÐNbFRг§ÜoöÚøØäd uk;‹=wné½0_ _î€ü†lÝxê)™sLáÁb'¯È‘C®ˆž{Î;CX&N” ¥;´“˜óöÛÀ¼yÚ)!Åž'OØÞBYþr*S§ÊC2:,vòª¯¾Ú·÷ÎøØ˜9à‘G´“„×’%Àý÷ÛyŸ½wo _¿°¾…óʧ'M’2¢Ôc±“×mÞ,÷á·mÓNbFš4Àk¯É_n\¹8r¸÷^Ù×n›ªUåP¢0'ìŒeùË:$Ç-Èír©Åb'î¸CÆ~Ö­«ÄŒóçekX½zÀñãÚiBËïÚ´±³ØsäÛ'æ8³ÜyHä¹çd"ÒÉ“ÚiìÄb'º(:øæYõÊøØ™3el诿j' ÷ÞæÌÑN8Ÿ3F¬1ñvŽ\–¿\Ñ¢2]îŽ;´“؃ÅNtuß´liç8Ð`DE_|4mª$u–/—Ã{l|ðú…dºŸ!v”;àžßœ&°Ø‰®o×.¹¿aƒvs.ŒµqàÎÑ£²ŸÝÆó *V”ϯt錽¥=kSqq@³fÀ3ÏÈ’=]‹(e –± ­Zi'1çý÷íë÷ËŽ‹=[69^Ö`±6•ûƒÉö-7;Q`2eÆŽ>úÈø‡¯š… å ŸU«´“¤Ü‡3fh§œÏŒ,hþ­­Y–¿\Þ¼À”)@åÊÚIœÅN”:K—ʼ^¹pÈAÆÇvì¨äÚV¯–ÏùÄDí${úi¹ U`o¹òM{À Gí$ºXìD¡±oŸÌ‡_¾\;‰9?|ò‰”½Ó?”,)ÏGئLù˜>½ÊÛÛ·,©sçä›QóæÞ9}êr,v¢Ð¹0>¶kWí$æ .«üõ—v’ÿêÔÉÎbÏ’E¦õ);`{¹_0iP¾¼wNŸº€ÅNzéÓËrõèÑ@d¤v3V­’+äE‹´“\4dˆl¶ÑˆòY¦ÈîeùËEGËÈúõµ“„‹(üÖ­“ír{öh'1#mZàÝweW’¦õëeÎÈÙ³º9‚ѵ«|1Qæ®räéÄçžú÷7rÄŸ ;‘9±±² ׯécÁjÖL–룢̿÷É“@éÒ2Ü6%J+V8âùw,Ë_Êï—ã kÖ´o/gJ°Ø‰ÌÊž]N´{ñEwa¹’I“äà‚}â ;‹=sf¹Ïî€bÜXî,\(ߢV®ÔN:,v"2;ü«¯äCÜ 6m’sé¿ûÎÜ{~þ¹ V±Ñ°a@‘"Ú)þáÞrd[KõêÀСÚIRÅN¤¯AàçŸeÞ…?.“åúö•Isá´i“ì~²QçβkËAÜwÏýjZ·>ûLN¤² ‹ÈYNžÚµ¾þZ;‰9> ŒÄÄ„þµOŸ–ûì[·†þµÃíî»e…ØaÝâî+÷K+÷vìÐN;‘óDGË6­·Þr—›5KfÙ´)ô¯Ýµ«Å~à rRªÃŠÒ Y3í ælÜ(ßMÞCJ ;‘sù|ÀË/KéeϮƌ?þ-j“&…î5¿üRæœÛhèPÇÞ¢‰è»iS_dÍ ÌŸþ{*N ¿1“’€jÕ€4]¼`±ÙáÖ[åLú%K¼q.ý¹s²jqò$P£Fê>C·l{ÌÎùìmÛ}úh§¸*Ÿßï—{î?ý$³Ò÷ïWŽdЃÊ(>§}ëf±ÙçÌÙÆeëUh0ªU“eé\¹ÿ¹ññ@¹rÀ¯¿†>W¸+&mn¸A;ÉU]üÊU¥ŠœÆT¥ŠbÃ~üQF®Y£ä";‘2f”%æO>ñÎøØÅ‹å3ôçŸÿ¹O?mg±gÊ$_h\ìÀåÔåÉ,Xôì©GÁž=ò…æ‹/´“°Ø‰Ü੧䜼yµ“˜ñ×_rÿùç)ÿ9&È x6úðCà®»´S\×ÅeùËMž,y¼4m­C9XcX‹È]öï—{ñË–i'1§cG`ðàk†þþ»<Ø|ꔹ\¡Ò¼¹|1±ÀÕË6o6´s‹B°J•’S¨ 2÷ž,v"w:wN†° ¬Äœ2eä»›núï?KH-ÉëכϕZ·Ý&·p-9¡ðÚ9Þq‡ÜKiØÐPX»V¾U~ÿ½™÷c±¹WºtrþË/åž¼¬^-I ü÷Ÿ=û¬Åž!ƒì²²¤Ø”b L  ã½ 6VNczãðnd±yC›6²<¯<ãۘLJ”a^€ôˆ­GðÌ*±Èµ—å/·p¡Œ½ìg·°Ø`ǤK'OŽëÈóÃâ̙յ+pölp¯Áb'¢ ê×—–‹ÓNBWòî»òpµ¥_–¿Ü/¿È=‰íÛCÉåÊÉ=˜+mõ¸;]É©Srá0}ºvº ^=çëói' ZꧦÜs°jP·nâXâçŸe«Çüù)ûñ,v"ºšÌ™ål·ßöÎøX'+X9ÒêbB5Ï=&øæ _?ïüæ¼°ÕãzŰ؉èz|>àÅå| § ²ò’t选lÙ´“¤Zê—å/7w®lõ8r$¤/ëh=&Û]þp ‹ˆµgР ò"³Þ}xþyí!úr¤Ô5’“м¢H¹gva ‹ˆ‚• ãcC±C‡Ræ‘G€ï¾³~9þ‚ð”; O”wïnïäŸ`ÜpƒLFª\™ÅND©7t¨LéLLÔNânùó69rh' ™ð•û£FÉö±„„°¾£dÉbçILWÃb'Ò³|¹ö÷ßÚIÜ)"B>ߪTÑNR¡y îZÚ·—ßœ^9S`±QèT¬(­*WÖNâN¯¿îºbL\¹_pô(Ъ0gŽ‘·£`±9ǹs@¯^ÀÇk'qš5e‡Bšð_çšf®Ü™°öÆÀ›o†wÚ¥‹È™Æºtâ㵓Ø-O¹Ïž;·v’°0[îÌž ´n-Wóä<,v"gÛ°A¶ËíÚ¥ÄN2™ïþûµ“„ÎZÄ#kÖ„nÊ…‹ÈùŠ—ÏÐZµ´“Øéå—]]ì€V¹ò€ÝŠr¦29‹ÈÙ²³f½{»fo¶Õ«}úh§;eùË ôèü´5J=;‘½fÌ['Oj'q¶\¹d>{¾|ÚIÂÎåÈivÉÉnd‹È~Û¶É}øÍ›µ“8“Ï'»µzH;‰Îyþ¿L¹‡ôÀÚI¼…ÅNä·ß.+5ÒNâL/¼à™bœTî3§ì9|ùeÞC2ÅNä.QQÀ”)À{ïygBgJT®,[°=Ä9Ëò—›1hÛ8~\;‰;±Ø‰Ümþ| Y3oMè¼’ìÙežÇ>ëœuå~©ºuU«€»ïÖNâ>,v"÷«QCŽ­-UJ;‰Ÿ=Ú“ŸuÎ-w@ƨ®\ ´l©Ä=XìDÞQ °t)СƒvÏ<Ô®­B…³Ë2e’ã?ùH—N;ÝXìDÞ Œ!ãc3dÐNcNúô@:Ú)Ô8÷žû•,_4iìÛ§Ä>,v"Z±BÆÇzå34}z`Ð ;î1v•;<(‰,Z¤Ä,v"ºààA¹HZ²D;‰9mÛŸ}&«áüeùËåÎ Ì+£¹]îúXìDt©Ü¹åIú=´“˜óå—@¥JÀž=ÚIŒ±ïÊýRÓ¦íÛ§Ni'q&;]Ë„ Àã{g|löìÀ¤Iž8,;+÷K5l(ÛåŠÓNâ<,v"ºž-äY¦[nÑNbFl¬LÒ{çÀâëÚ”°ûÊý‚¸8 cG9™‰XìD©å÷{ë¶ß±c@«VÀìÙÚIÌiÐ@öÀgά$,ÜQî|𜜔¤D‹(õ.|,z©àׯÊ–õÖçg±bÀôé@Ñ¢ÚIBÎ]åÈ M›h'1ÅN:ÉÉRîiì¾{™"qq@éÒ2YÎk¢£å ¾~}í$!å¾ßµU«Ê‘‹•+k'1+mZ9è‡ÅNiÒHÁ»ìú犞|Ò›Å'OÊó[½{Ëÿß.á¾r€|ù€ ¼µÕ#)I~ƒŽ©„È|>™¬$&j§ Ÿ‘#åP/óûþýG‘‡î\À}Ëò—›8èÜY–¼¢S'`ð`o5I.ÉÉÀÑ£òà•ÛAÙ¼(SÆ;[áR¢paÙf]¢„v’Tq¹À¯¿õê;wj'1§tiù Êez¢Ô;sFF§fÏ.ó.Ü >^Š}ófí$Γ1£œhצv’ ¹sYþrÑÑÀùóÚ)ÌZ³(YøñGí$Dö˘Q>GŽuÏ}÷î,ö«9sFެíÞ8wN;MPÜ_î{÷÷ÝìޭļØX¹‡Ô¯Ÿ7 " §,YdiþØ1k?ðÿ1nŸÏI‰Áƒ¥?öï×N0w/Ë_(v/-Ç_M:À˜1@LŒv"»:œ>-eŸ1£všÀmÛ&·í¼ôRjåÍ L*çÓ[½Wî,ö›9Sî¯mܨ„ÈnQQò׉²|k“„9„Řýû¥OÖN’bî,wû•ýñP±"0v¬v"{ù|À 7HÁÇÆJaÚ¢gO~ÁÖ¹sr¾m[+¾Ô¹oYžÅž2ݺÉq½éÓk'!²ÓùórÊ©S@þüÎ?Énòd Y3íîP¢„ìF*\X;ÉU¹«ÜYì)_øê+ù`"¢À?/[ä.¼S÷ÁïØ”*%·(4²gÆzH;É9ü«fXì[¹R¾.\¨„ÈNiÒ9sÊ ØöíÎ< &1hÒ„Åj±±À£ÊÉv¼FvG¹³Øƒwø0P³&0`€#ƒ9žÏ'Wí™3ËgÑéÓÚ‰þ­W/`Ý:í,gÒ7l(·hÄþey{è4hŒ%‡uQ`Ο—§ª“’¤ìÓ¦ÕN|ýµåóV(ZTÆÇ+¦€íåÎb½Ûo—Eî¼S; ‘}Ο—SìΟ—3%4Xݽ[N©>ÖÎrg±‡W\œ|0ôìiÿ1›D¦¥I#Ãe¢£ed¬†Þ½ŸÖyo¯óûwÞjÕRkß²<‹Ý¬*Uä*>o^í$D”ß}Ô­ËûìNP° Ü‡/YÒø[ÛUî,vyòS¦HÑ‘sýù§loU¼b¤ËDFÊøØ¶m¾­=Ëò,v=5j~¨„ˆ®&) hÑ‚Åî4 @»vr*¨ÁqÁv”;‹]ß¹sr¾Y3 r¢×^–.ÕNAW3t¨ôؾ}FÞÎùåÎbw–É“r倭[µ“Ñ?ü qÙ¨~} Cíf,_.ãv | sv¹³Øióf)øéÓµ“Ñþý@›6v>@÷àƒr®Æ’%ÀM7i§1ãÀàþû?ëÛ8·ÜYìÎvò¤ìç|þy¹×GDæ%'Ë}öC‡´“.o^?íóeËk×Êg¾œ;ôè´n¶yÎ,w»ü~9“þÁíüp!²Ýo‹i§\D0a+×Å¿—3'ðãÀ³Ïêå2mÜ8 R¥°tóÊÅnŸ… eçÊ•ÚIˆ¼cÁà­·´SçÕWêÕÿû÷Ó¦&M’Sþ¼`àLàûïCú²ÎÚçÎb·[úôÀÈ–" Ÿƒe?ûþýÚIwÿýr…~½Óû6m’3ÚÿøÃL.miÒÈJÌË/Ë­ŠÔ¾\"…†‹ýæ›Ï?÷ΔµÄDà©§Âz‰ÈóΟ—?c6{îܲ}­b÷ûåßñÖ[åA»ÚµÍåÓtþ<ðÊ+Àc…d|¬3Êݭžp!ðøãÀêÕÀ]wi'2gÜ8 Bï|ã&2éw€¹sµS.My€îjGYûýò×¹s2tÇçrä¾ýèÛW~¾̘!nÞœª—Ñ_–ws±(pñïÅÅ;'êå2-&3¨SG; ‘;üô“|^*O ÊË/_û åîó]yYzöl eKàøñ°Et”¨(`Ô(Ù•Ýr÷J±_êã^½¼3mÍç“?Ô¯¿®7!‹È Ž‘ûìý¥$pUªÈçâÕ>Ο—bO“æÚ÷›wìûð¿üžœNãóI_¼ývÀŸŸzåîÅb¿`éR iSàï¿Íär‚š5eÕ"{ví$DöñûåÞóìÙÚI—#°~=pã¡y½øx S'o­‚Ö¨!;räHñOѹ‰áåb€Ê•åÀ†ªUßË)æÎ•írkÖh'!²ÏÀv»Ï|ùå¿‹ýÂò{°2e’=òƒÉÖ9/˜?(UJz#…Ì—»×‹ý‚à‹/€‚åj=mÚðï”Éøì3`ÄאַX!÷á—,¹â?¹³ØS.sfY¢0À;ŠÄÆÊSÀ¯¿.ÛaˆH®zŸ^;Epºu“UH­¯:ÈY¡þlvªƒåIú>úÏ? ï²<‹=x‹ÍšÉÿy^ñÈ#rº]Ö¬ÚIˆôœ8!÷ÙwíÒN¸R¥€eË®y/؈ÇåósÁÝ&µh!+gÊ œWî,öÔ©^]¶=T¬þ÷rŠÙ³åÃaÃí$DzÜÎbÏ’EVµ‹¸8>¶W¯Ð<™oƒ ¤/vì®rg±‡Fþürß½»¹÷Ô¶k—<(2z´v"󆕇¤l4|¸|N:ED„Üâœ<Ù;ãc7n”ç˜fÏò<‹=<Æ—³é½4m-Û=ˆ\cýz¹òJHÐN¸'Ÿ”/&Nµy³ì<øýwí$f¤Iârg±‡×¦M²Ÿsûví$æ”-+W2Nøõ' —S§äŠkÛ6í$+QX¾ÜùO©Ÿ8´i#S×< tËò,öð»ûn[·®vsV­J—–“¨ˆÜê‰'ì,öÌ™åŒw§; Ï|ó ðæ›žšC»9~ƒ¾õ–w¦¬> Ôªô3©‰œè‹/äa(}öpûíÚ)RÎç^yøî;×ïÊIý²<‹]ϼy²ýáðaí$æÔ«'ƒ(²dÑNB”z¿þ ”+gç³4:Ù='bçNÙ¿q£v’°H]¹³ØõíÝ 4n,Ë×^Q¤0mšÜ¦ ²U|¼ÜrÚ²E;IàîºKÎrÿÿ=ÕÖŠ—•Ç×NrÁ/˳ء@9~°Kí$ælß”/oïR&tíjg±gÊ$ûÙm/v@þÆ“ÞÒ¥ÓNRÁ•;‹ÝY.œ«™­ž7¯½í§MÚµââ´“\וËÅîn«WË}é½{µ“˜SµªÌuvÑ>V²À´iògÍFƒݺÿóÏž•£±”+ßœ9eWKÆŒRú¶Ú²EÆÇ:|ÐÏËÅî GŽÈÃ=sçj'1'_>yâ·R%í$ä»v%KÊg¶iÐ@¾˜„BRpì˜lŸKJ’+øèhÙ+oë•üÉ“@Û¶2ÄË¡þ]î,voINúôñÖ´µté€åD*¢pIL*W–U2Û. ¬['ÛÞB-1ñâ_>Ÿ\ÅgÈ`畼ß/Ÿ}úÈg©Ã\,w»wÍœ ´icçF°š7>ÿ\º# µgž ÒN¸ôéŸ~Ê– ïûøýro>9Yþ3"Bž®÷ù.þe‹ï¿ZµrÜvc)w;mß.÷ùE;‰9wÝ%K·Ý¦„ÜdæLMlãjØÈ¨ZΟ—_7ÛJ~×.¹•±aƒv’øü{öøYì@†±té"S’¼":Zx<ö˜vrƒ½{åéò£Gµ“®NàÛoS¨¾]øO§—ý™3òù9v¬v€ÏóÍ~;ýË!²¬˜˜¨Ä Ÿxá _?û÷ã’ž¤$ Z5`ùrí$+P@öág˦Ä~Ÿ|<û¬ú´JŸ°píè*X졳b…þ²oŸvsî¿_†ÏäÌ©„lôâ‹rž‚mÒ¦/–éŠK—MšÈñ¼J\r> Xì¡V¡‚<1{ß}ÚIÌY°(U øùgí$d›9s€÷ÞÓNœ~ýXì¡V¹²œì§øëêŽrg±‡G®\À?Ï=çì{]¡ô矲´jù¸G2èï¿eϳÐ=ü0ðüóÚ)Ü)_>`Ñ"9ö[ýËò,v3¦M:t°sðE°Ú´‘’Ï”I; 9Ur²ÜÎY²D;Iàòå“§»y*üÆŒ‘‡í ŽÇµûÊÅnNƲ\}ÇÚIÌ3FN³sÓ§Z}ûÚYì2‚–ÅnF›6ò e¡BÆÞÒÞrg±›W´¨|Ó¦ÚIÌÙ°AîÃÏš¥„œfÞ<àí·µS§o_™·@æ”(!÷ákÖ4òv}¾FÞ)”XìzÒ§—Ãnbb€ùóåÐ ·KH«œäd¹ï•çèêz8uJ;Iàx6Ì=óÖm’)“Ìô8wX¶,¬oeß=w»süô“l÷8p@;‰9µjÉ!?Ù³k'!-ÉÉRìóçk' \ž<²ŸÓõMŸ´o¶ç˜ìúêÆbw–*Ud»\•*ÚIÌùþ{ ti`íZí$¤åí·í,öˆùbÊbw† ä6gÑ¢ayy{ÊÅîLyóÊ]ÚIÌÙ½[ö±Ž¡„L[¼XîWÛ襗€5´SÐ¥.<ÇÔ AÈ_ÚŽey»&Mˆ‹ÓNbNÇŽÀàÁ@d¤v ·Ã‡âÅe_»mªU“/á<^Ù™ü~àw€W_ ÙøXç—;‹Ý.¿ý&Ûæ¶mÓNbNéÒÀW_ j'¡pñûG‘Û2¶É™Sv}ä˧„®gî\G‚ñ±Î^–g±ÛçÎ;U«Â²ÌäXkÖÈv¹~ÐNBáòÞ{v»Ï'SYìv¨YS>OJ”HõK9·ÜYìöŠŽ–+ÙwßõÎ2`l¬\Ùõëgç1¤tuË—¯¼¢"8Ï?/GÌ’= ’ßsmÚ¤êeœ¹,Ïbw… åЛǵ“˜S»¶ÌtމÑNB©uô¨\EíÝ«$p+Ê€iÓj'¡`  <ýtPãcWî,v÷Ù·O¾Y¹R;‰9·Ü"«Å‹k'¡`ùý@½zÀÌ™ÚI—-›ìgçç¨ý–-“óD|ÓYËò,vwÊŸ_u:’Š;ä\ú1c´“P°>üÐÎb÷ù€Ñ£ù9ê•*É}øÊ•úiÎ)w»»eÈ "eç•)kññ2 ´[7àìYí4ˆU«€_ÔNœ§ŸêÔÑNA¡”7/°`н{ŠŠ3–åYìÞ²q£,Óÿñ‡vsÊ—¦Nn¼Q; ]Ïñã@É’À®]ÚIW¶¬ >½v —±ce|ì™3×üaúWî,vï¹÷^`õjyðÌ+V®”ÂX°@; ]OÇŽv{LŒ$Åbw·Ö­åiúÂ…¯ùÃtËÅî]11ÀŒÀ›ozg»ÜáÃÀƒÊAn—s¦!Cd ‡FŒ¸î>¹DñârT«ÖUˆÞ²<‹.øñGƒ‚S™¬Q¿¾<ô­„.X·N¶Ùø|D·nr 2yKr2Чпÿ.tÊÅN—Û»WŽ­]³F;‰9·ÝL›Üu—v:yRŽÞ¾];IàJ–”eÚ ´“–o¾‘‡w/k~YžÅNWR €<Ô©“vs~ÿ¨PAî“’®.]ì,öèhùýÃb÷¶Ç“ÅŠýó·Ì–;‹®%2>\îzeÊZ\œ Šò* Ï?·÷ Ö°a@‘"Ú)È n¿] ¾aC&—åYìˆuëä7éîÝÚIÌ©\˜<™C>LúåÙ¦xmEŽÔ¹³”;Ñ¥ü~à½÷ •;‹‚ ´jeç4®`åÉ#_µªv÷;}Zî³oݪ$p÷Ü#Û+3fÔNBþey;+{v`Ö,àµ×€4úG2qàP£0hv÷{òI;‹ý†ä ‹®!¼Wî,v •Y³äð†cÇ´“˜Ó¤‰<¥Ä}FÚ·×Nœ1cäÏÑ5„¯ÜYìj;wÊ}ø ´“˜S¬˜ªR´¨v÷زE–ãã㵓®];`Ô(ídð”;‹ÂåÌYNýòKí$æDG#Gþó,¥B|Ü[í4fìÛT«ÆA!)• _m,öÛo>ûL;Y(õåÎb'm2é믽3eíÜ9 {w9äÇÆ§¾Mzæ`ýz틌&N2gÖNBJݲ<‹œfûv A;š Ö=÷_}Å3ƯdêT9/ÀFC‡Ê΢ _î,vrªÓ§åÜí ´“˜#ÛëÖÕNâ;wÊ8Ô'´“®qc`Êíd±à–åYìäd7Ü  }ô.v3Ž—±½{ÉÉÚiô%&M›ÚYì7ß,OÇ¥BàWî,v²É²eò!¿oŸvsx@îÕæÈ¡DOÀÇk§\úôò{¶tií$d¹À®ÜYìd›J•äT¯jÕ´“˜3oPª°zµvß~ |ò‰vŠà Àb§Hù•;‹l–”¼ø"ðÁ2ïØ 2d[]ºh'1gÏ D ; Õ«';>¼²¥“Â*eåÎb'·˜:èЈ‹ÓNbN»vr\od¤v’ð:w¨ZUæœÛ¦`AÙ®—5«vr‰ë/˳ØÉM7–åj/MY=¨XصK;Ixõîmg±§K'G ³Ø)„®]î,vr£¢E¥à7ÖNbÎúõr/wölí$á1k0p vŠà¼õP¾¼v r™«/˳ØÉíü~¹ÿâ‹Þ™¶–& ðê«Àk¯Éwƒ¿þ’ûìGŽh' Ü£3gò>;…Ü•ËÅN^²x1Ьpà€vsy;È–M;Iê$'ˤ¼¥Kµ“îÆeEÅË[)lþûÕÅN^S­šl—«TI;‰9³gË2½g®_êµ×ì,öˆžE@aõïrg±“WåÏ/¿÷»w×Nbή]ò Ý¨QÚI‚3w.ðÎ;Ú)‚óÆ@åÊÚ)ÈÅ..˳؉Ą r6ýéÓÚIÌéÜYNtËA;IÊìß/÷ÙÔN¸æÌqÏ3äHRî,v¢ûõW™.·}»vsÊ”‘érNÿHN–‚\°@;Iàòæ•[!¹sk'!—KÃb'º‚»î’ûðõêi'1gõj9¶vî\í$×Ö¯ŸÅŒÇb'#Ұ؉®":ZŽíß_>˜½àÈàá‡åßÙ‰Çô.Z¼ù¦vŠà¼ò pÿýÚ)È#|~¿ÿ9Ìüù@óæÀáÃÚIÌ©[WfÄÇÄh'‡Å‹ËývÛT¯.}¼ò%‘Ա܉RêÏ?F€U«´“˜S¤ˆÜ‡¿çÝ~?P«ðãº9‚‘+°aƒÜo'2„k¥ÔM7K–O<¡ÄœíÛ ä^±¦wÞ±³Ø}>9,ˆÅN†ñÊ(_~ <ù$pæŒvsžzJŽëM—Îìû.]*ËÚÉÉfß7^zIž_ 2ŒåN¬ €† ;µ“˜S¡‚ŒÍÍŸßÌûÅÆÊ~ö?ÿ4ó~¡T¹²<Èû준ËòDÁ*^\¶Ë=ú¨vsV¬J–”Ò 7¿hÛÖÎbÏž]Cb±“–;QjdÍ Ì˜¼þºwN;t¨YSF¬†sáïý÷e”«m|>¹msÓMÚIÈø,O*ß´j%KÉ^Ѱ!0r¤œ J+WÊ@ŸÄÄо® ½zh§ c¹…Ò®]²]nÝ:í$æ- L›ÜqGh^ïØ1Yúß½;4¯gRùò2B8}zí$äqYG$2¤pa`Ù2 Cí$ælÝ ”+L™’ú×òûå×ÎÆbÏšUƸ²ØÉXîD¡ Œ .ÿÝ â…Mgž’’‚Áƒo¾ Y,c|>¹=Q¨v"\–' ¯5kä¾ôÞ½ÚIÌ©RE®âóä ìç­['[íl¼ÏÞ½»ŒÌ%r–;Q¸ÅÆ-ZØyÂZ°òæ•‚¯\9e?þÄ ™H·cGxs…C©RÀòå\Ž'Gá²í4fìß/Ð>ú(e?¾K;‹=K`òd;9ËÈ„ˆUúí·Î™²nçÎO?-Óôââ®þã>ûL ÒFŸÜr‹v ¢ÿà²<‘iü!Ûå6nÔNbÎwÓ§·Ýöï¿¿q£lKHÐÉ•O<|ú©v ¢+b¹iˆ—r;V;‰9ÑÑÀèÑ@ýúò¿ãâ€Ò¥mÛTcåÞ{å ¯ì† ëpYžHC¦LÀ˜1À!Þ¹_{ò¤ìxá™ðöÄv{T”ÜF`±“ƒñÊHÛÊ•²L¿oŸvsŠ•Ãol4nв¥v ¢kb¹9ÁáÃrÌÂ…ÚIèZ:tŠˆŽåNäÉÉÀË/ËÐþ±tž;ïV­’[*DÇr'ršéÓöíå59C¦LÀêÕ¡ŽCf| ŽÈi4+Ä;ïÔNB Ìb'«°Ü‰œèöÛåA»fÍ´“P«V²’Bd.Ë9݇Ï?/'¾‘YE‹Êr|T”v¢€°Ü‰lðÓOò4ýþýÚI¼#cF`Å 9°†È2\–'²A•*2µJí$Þ1h‹¬År'²Ež<À‚2Œ…«iS™TGd).ËÙhòd S§kO[£àÜz+°v­œ…Od)–;‘­6o–³Úm=ÆÕ‰Ò§—ûì%Kj'!J.ËÙêŽ;€Ÿ–}ñï¿Ïb'Wà•;‘íü~`à@9º6)I;½ê×—Ó‰\€åNä Ê¡7‡i'±O¡BÀúõ@LŒv¢à²<‘[ÜwŸl—«PA;‰]Ò§—Yìä",w"7ÉŸX´èÖM;‰=ú÷Ê–ÕNAR\–'r«qãd¯v|¼vçª]˜1ðù´“…ËÈÍ~ùE¶Ëýñ‡vç)P@ncdÏ®„(ä¸,Oäf÷Ü#ƒOêÔÑNâ,iÓãdzØÉµXîDn|û-Я¡ÆÞ|¨\Y;QØpYžÈKæÎZ´ŽÑN¢ç¡‡€9sxŸ\åNä5{÷Ê}ø5k´“˜—/ŸìgÏ•K; QXqYžÈk –.\;‰y:°ØÉxåNäe£F]» ÚIÌùßÿä¸Þté´“… ËÈëÖ¯—eú]»´“˜S©’œJ—?¿v¢°à²<‘ו(!Ûå~X;‰9Ë–¥KK–h'! –;É~ïï¾úôÒxäcáÀ F àƒd²‘‹pYžˆþmöl U+àØ1í$æ4n Œ DEi'! –;ý×®]@ƒÀ† ÚIÌ)ZøúkùO"˱܉èÊΜn» øë/í$ædÎ,Wði'!JÜ\#¢€}ò‰·ŠNš4zõ’’´ÓWîDô_+VÕªçÎi'ÑS­0i'v¢€±Ü‰èߎJ–öìÑN¢/~`Ê bEí$Dá²<]ä÷Ë­,v±oP½ºÜ¢ ²ˈ.úè#K;'GÖ¶l œ>­†(E¸,ODbÍ9–51Q;‰sÝ}70mP¤ˆv¢kâ•;'NÈSâ,ökÛ´ (S†«äx,w":uòÖà˜Ô8q¨_xùe 9Y; ÑqYžÈ놺uÓNa§5€‰œ9µ“ý ËÈËÖ¯*TΞÕNb¯€©S²eµ“ýƒËòD^uêЬ‹=µöîªV>ûL; Ñ?XîD^Õ¥ ðûïÚ)ÜáìYàÉ'v퀄í4D\–'ò¤áÃεS¸SñâÀôé@áÂÚIÈÃXîD^óë¯@¹r@|¼v÷Êš;xôQí$äQ\–'ò’Ó§e?;‹=¼ŽêÖúôΟ×NCÄr'ò’nÝ€-[´SxÃùóÀoµk±±ÚiÈc¸,OäcÆmÛj§ð¦Â…¯¾’i{D°Ü‰¼`ëV ti>Ñ "S÷ˆÂŒËòDn/÷ÙY캀Že—Ï 0c¹¹]Ïž2ð„œaøp re9ü†(L¸,Oäf'-Zh§ +Éž˜0xðAí$äB,w"·Ú¾(UJŽ™%gŠˆ^]&Ìù|ÚiÈEXîDntö¬ „Y¿^;Iàn¼QîO9¢Äœ:ud7CLŒvr Þs'r£gž±³Ø3dfÌìåÊi§1gæL L`ãFí$ä,w"·™6Mf´Û胀%äê}ñbÆâüT¬(ÇÖ¥—å‰Üd×.)Ç'´“®aC9èårcÆHÉ{éÈÜnÝä‹NúôÚIÈR,w"·HL”-V«Wk' \Ჟ%Ë•ÿù† Rþ;w¥ª|yù²“?¿v²—å‰Üâ…ì,öôéÉ“¯^쀌Q]»Ö[SÖV®”U˜… µ“…XîDn0cðÑGÚ)‚óî»ò0ÙõÄÄȃgo¼¤ñÈG×áò~À€‹¬.ËÙnï^¹Â;zT;IàêÖ¾ù&ð=Þ?ü´lé­ik £FÑÑÚIÈ,w"›;T¯,_®$p ëÖÙ²÷ówï5’åz¯¸ývÙ qçÚIÈá<²¶EäR¯¾jg±§K'G¯[ìP¨°t© cñŠmÛäA»É“µ“ñ܉l5gðÞ{Ú)‚Ó¯ŸìéN­ÈHà‹/ä¯ÈÈÔ¿ž ââ€fÍd йsÚiÈ¡¸,Od£¿ÿî½×Î#Z~˜5+ôg©¯Y#Ëô{ö„öu¬J¹ŠÏ›W; 9 ¯Ü‰l“œ 4ong±çÏ/‡Ò„cHJéÒrÿý¡‡BÿÚNõÓO2è§Ÿ´“ð܉lÓ§°d‰vŠÀEDÈ}ö9Â÷Ù³³g˳^™²¶?P£ðá‡ÚIÈA¸,Od“yó€ZµäêÝ6ýú½{›{¿ï¾Z·Ž7÷žÚš6•碢´“2–;‘-“ÚÔN¸½é¦ŸÙ±CîÃoØ`ö}5Ýq0}ºl›#Ïâ²<‘ ’“V­ì,öû«¯ÊYðN±r%и1ð×_ÚI̹ï>Ù.—3§v2ˆåNäd‡ɹñÿ­$pÕ«Ë€ÚIþíða9fÁí$æäÏ/ãcË—×NB†pYžÈ©ü~ m[;‹=W.YŽwZ±rûã2"×+ÛåöíªU† ÑNB†°Ü‰œêÝwï¿×N8ŸOîqç˧äê""€wÞ‘!,^™²–˜<õ”lŒ×NCaÆey"'Z¶L–µm|êÅ·ßÖN‘r¿ÿ4lüú«vsî½W–éo½U; … ËÈibc’%eN»m*U-Ò¦ÕN˜¸8 sg`âDí$æÄÄÈQÀuêh'¡0à²<‘“øý@ûöv{öìr¼¬mÅȉn&È®éÒi§1ãøq ^=à•Wì܉A×Är'r’Aƒ€™3µSÎçF ÐN’:=zÈSôN~^ ”ü~à­·dR_l¬v !.Ë9ŪU2Â31Q;Iàž}8P;Eè8 ç´Û8 'X Ȇ¥Kk'¡`¹9Áñ㲟}÷ní$+[VFަO¯$´’’ä„·Aƒ´“˜“!ðÉ'Àãk'¡Tâ²<‘tìhg±ÇÄÈégn+v@žøàù÷óÊ”µ³gåÁÂŽ„í4” ,w"mƒÛ;äcäH P!íáÕ¤‰Ü2)ZT;‰9#GÊ·]»´“PXîDšÖ­zõÒNœ§žê××NaF±bRð j'1gÝ: L`Îí$Þs'Òrò$PªðÇÚIW²$°b…;—ã¯ÅïÞxé%; Fš4Àk¯É ±½–;‘–æÍI“´S.:X»ÖÛ§›-Z$ÃgÔNbΣcÇY³j'¡à×0" ÆÙYìðùçÞ.v@Ž^»¨PA;‰9³fÉJÓ† ÚI(XîD¦ýò г§vŠàté"û¿Iƨ.^,ÏxÅ®]ò…æË/µ“ÐupYžÈ¤¸8yHiëVí$»÷^¹Ïž1£vç?^¶yiÚÚOÈq½2h'¡+`¹™Ô¦Ü·´MT°zµ·¶ƒjÓ&yš~ûví$æ”- Ljÿ±Ã.Äey"SF²³ØàÓOYì×s÷ݲ]®n]í$æ¬Z%ÇÕΛ§„.Ãr'2aË{ïͶo´j¥Â11À7ßÈ0–ˆí4f> Ôª¼ý¶l$Gà²þ¸óNív+P@&æué¢ÄœíÛe»Ü„ ÚI<ËòDá²m›\Åœ:¥$p-[ãÆi§p—Ñ£'ŸôÖ´µîÝå¸Þté´“xË(äêÅÆÓ¼n¿]î³{eÌ©Ië×Ëv9/M[«X˜2Eý!c¸,OÏ·ËÂey¢PÚ¹SÆ¡ž8¡$pMš“'k§ð–9sä £Gµ“˜Ó¨0r$9³vWc¹…Jb¢Ü_\»V;Iàn¹Ers²y»vÉ}øõ뵓˜S´(0}ºœ£@aá‘5!"ž{ÎÎbOŸ^®ØYì: –ûÒíÛk'1gëÖ‹çÒSX°Ü‰Bá›o€O>ÑNœå'Ò)KÕÆygÊZ\œŒ~öY )I;ëpYž(µöì‘§ ÓN¸Ç“åQŸO; ]°zµÜ—Þ»W;‰9U«ÊêQž<ÚI\ƒåN”‰‰@µjÀÊ•ÚIW¨°n5«vºÜ‘#@‹ÀܹÚIÌÉŸ_ ¾R%í$®Àey¢Ôxå;‹=}z`âD»SåÈ!OÒ÷îíU•}û€ûî“c)ÕxåN¬Y³€:uìÜ·;`Ы—v J‰3€¶mãǵ“˜Ó¼9ðùç}´S [·Êt¹Í›µ“˜S¼80mpóÍÚI‡åNt-‡ÉÈþýÚIwß}r|iD„v2%.èØ˜2E;‰911À¸qr~ýƒËòDWsþ<кµÅž;·,dzؽ%*JÎgÿàY²÷‚ãÇå¤È×^³sK˜°Ü‰®æÝwå%Û¤IŒ#Ò‘7õì ,Xà)k~?ðæ›rõnãáRaÀey¢+Yº¨^ÝÎûì/¿ ¼õ–v r‚ýû&Mì¬ÄœÚµeÖBLŒv’°â²<Ñ'ŸØYì>0j‹‚wß}Àºu²úãß}”. lØ $¬Xîämk×Ê6!uïÔ«§‚lwã²}²kWí$æìØTª$Wð.Åeyò®'€’%;µ“®ti`Ù2 }zí$ä&cÇO<ÄÇk'1§kW9®7Cí$!År'ïjÚÔÎY²ÈR*'aQ8lÜ(ÓåvìÐNbNùò26×E[I¹,OÞôÙgv;|þ9‹ÂçÞ{eûXíÚÚIÌY¹RVñ,ÐN2,wòž d°†ž|RÎ ' §˜`Æ Æâ•£Œ|xï=ÙAc9.Ë“·ÄÅÉýêmÛ´“®xq`Å O¥IŠ~øhÙÒ[ÓÖê×—££µ“Wîä-Ol’Q}©IEND®B`‚src/uk/co/mccombe/terrain/images/idle-icon.png0000777000000000000000000000644014163163576016471 0ustar ‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF;IDATxÚ„“?haÆßw—är—´½$B¨IÿDq)í ¸éTüCÁÍÍ¥CÁÅÁÅ¥«[7¡h]…º ]:¸Š&mC©M“6¹Ë]r¹Ï%WâÐæoyÞçyyyßïJ)Æá8î­æiû~¯ç•‚áÐÐ5ÍI&r63óͲ̟ãz5ÃШÖ/§·¬”Ê9H(”œ!Ž,+¹?W˜ý ¥ô. ÃÐ,Wª¯|¿(p5ê‰D|¯´8÷N“ÒJ)ÿ¼t÷©BÙ1=ÖO&{fú Õ>Ë÷z~z âѲ,óÓÂüõ÷z§ëÜî:Î*`L¥S͹âì.°?z¤ÓÖ2°\­5œwºY…²»Ž³Úé:_Eå ö¦ë¸ÏŒD½ycþ-°sÉøý>|íù¾ ²ÌÒóý•¨jÛÓ{W˜vF<ß_‘Aæ#bj*½5aÿi‚ ÌKÆXýɸÐ0¤¦kƒˆ8?ï,Mrk4]H#?ŽˆÓöÙÃI Æ5F"~,sÙÌnDô½þRµÖX¿Ì\­5Öû^ÿb‚\6³+”R³•ƒú¶ãº3#¾¦Çô¦a”m{ú;@«uvÏõ¼R0îEË4Û‹ …5¡”" ÃçåJm#º/à'ñw”…k@0£ÿRZ,nJ)·¢0ÅÂ0ܨ՞tºÝÌU;H§R§ÅBþ³”rˆ±8ëÀ#Çí­5›­‚ÛóÒáp¨HM ̤ÑÉfíºe&·/@ðoœ 0š„5ÜIEND®B`‚src/uk/co/mccombe/terrain/images/pos1.png0000777000000000000000000000170214163163576015504 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PmIDATWcð­0éËPÏu`²žBW@Ä <ˆXÃ"$¹E Qg7¬Üp6ò솳.G1”o8·áì•ò+g7œ»RÎЂdf Ô”r¨)Ç^ý²¢ ¼Ç_!Ù÷øñG¸[Z ÊëÁÜ l@6f±¸acIEND®B`‚src/uk/co/mccombe/terrain/images/pos2.png0000777000000000000000000000170114163163576015504 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PlIDATWcð­0éËPÏu`²žBW@Ä <ˆXÃ"$¹E Qg7¬Üp6ò솳.G1”o8·áì•ò+g7œ»RÎЂdf о:ˆ}ö@²áåÝÇ^+ˆzùÌke¹sQyA9Ø]È6­Ü3¿ÈIEND®B`‚src/uk/co/mccombe/terrain/images/pos3.png0000777000000000000000000000170414163163576015510 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PoIDATWMN¹À S›ŠÅ`€0SA“,B—΋˜&l@ǹÏåP!Ÿå“,ß ;Õà;Œy mnCóˆË- ß,»°ðcAœX2eá” ç’yNÍ”ñÏ*õÖJË¿­8\¿¯Bë{OGú%ý5+(mŸ1IEND®B`‚src/uk/co/mccombe/terrain/images/pos4.png0000777000000000000000000000166714163163576015521 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PbIDATWcð­0éËPÏu`²žBW@ÄZ‘äê ?†Ë-b(ôx决‘g7œÝp9 (ǹáÜ•ò+gA$CÝp3[M)‡š ±/ ÊÃm_+È‹`î¼§3_}kúIEND®B`‚src/uk/co/mccombe/terrain/images/pos5.png0000777000000000000000000000167714163163576015523 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PjIDATWcð­0éËPÏu`²žBW@Ä ¼((oˆ~u÷ˆZÄuvÃÊ ?ÞpvÃå(†ò ç6\Ùwÿ• ç®”3´€”ü¨û¢ZÀúàfB샙‰j_+’[Z@î\s'X‰5RÃÇIEND®B`‚src/uk/co/mccombe/terrain/images/pos6.png0000777000000000000000000000167414163163576015521 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PgIDATWcð­0éËPÏu`²žBW@Ä <ˆX Ã"¸Ü§s¢ÎnX¹áläÙ g?*d(ßpnÃÙ+åWÎmà|\ÀÐ7óñãr¨¾r¨™û¢Pl€Ù׊ä–V;ÁÜ ¤É3MéñgüIEND®B`‚src/uk/co/mccombe/terrain/images/pos7.png0000777000000000000000000000170714163163576015517 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PrIDATWMN±€ üÖ*‹É2]¬b£‹ÐÙe‘ØÈtL!èY$ŸÏ'9™k‰8pÎD$µˆ6„ÑL4;à~c‘IEND®B`‚src/uk/co/mccombe/terrain/images/pos8.png0000777000000000000000000000171014163163576015512 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PsIDATWMŽ¡€0 D³A£Ð)pAC©Ãe&H µ¨*\G€ŠÆÜû÷rɧopA›û¦Öª²©<÷OdÔÎf”TÜï¹Aø`™……¯È™¥`Îa6÷žCóìŸ ìÚÚ)¹Ú3Ѥ5É¿ð6ˆN¾}<IEND®B`‚src/uk/co/mccombe/terrain/images/pos9.png0000777000000000000000000000170614163163576015520 0ustar ‰PNG  IHDR E5NgAMA± üaPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ3333f3™3Ì3ÿff3fff™fÌfÿ™™3™f™™™Ì™ÿÌÌ3ÌfÌ™ÌÌÌÿÿÿ3ÿfÿ™ÿÌÿÿ3333f3™3Ì3ÿ3333333f33™33Ì33ÿ3f3f33ff3f™3fÌ3fÿ3™3™33™f3™™3™Ì3™ÿ3Ì3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿff3fff™fÌfÿf3f33f3ff3™f3Ìf3ÿffff3fffff™ffÌffÿf™f™3f™ff™™f™Ìf™ÿfÌfÌ3fÌffÌ™fÌÌfÌÿfÿfÿ3fÿffÿ™fÿÌfÿÿ™™3™f™™™Ì™ÿ™3™33™3f™3™™3Ì™3ÿ™f™f3™ff™f™™fÌ™fÿ™™™™3™™f™™™™™Ì™™ÿ™Ì™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌÌ3ÌfÌ™ÌÌÌÿÌ3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌfÌf3ÌffÌf™ÌfÌÌfÿ̙̙3Ì™fÌ™™Ì™ÌÌ™ÿÌÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿfÿf3ÿffÿf™ÿfÌÿfÿÿ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿDb°PqIDATWMN± À ËÚ)Áå ¶t¢ <ÂÖ-„¥|ÀÆ%-jkÉŠâȱÁ…ôd`GEl@'â6¸4?7Õz'Èï 1ƒ>XVaáÓqa©T…K%ˆïOÄ8}ôh3ÏÍíËÓ[úùhÏ|÷ Á^±ð4B¾·jNIEND®B`‚src/uk/co/mccombe/terrain/InfoMessage.java0000777000000000000000000000220014573074222015676 0ustar package uk.co.mccombe.terrain; import uk.co.mccombe.util.Severity; import javax.swing.JOptionPane; /** * * @author Mike */ public class InfoMessage { public InfoMessage(String title, Object[] msg, Severity status){ message = msg ; level = status ; heading = title ; } public InfoMessage(String title, String msg, Severity status){ message = new String[1] ; message[0] = msg ; level = status ; heading = title ; } public void display(java.awt.Component parent){ JOptionPane.showMessageDialog(parent, message, heading, mapType(level)); } public Severity getSeverity() { return level ; } private int mapType(Severity s){ int i = s.value(); return msgTypes[i]; } private static final int[] msgTypes = { JOptionPane.INFORMATION_MESSAGE, JOptionPane.WARNING_MESSAGE, JOptionPane.ERROR_MESSAGE, JOptionPane.ERROR_MESSAGE, JOptionPane.ERROR_MESSAGE}; private Object[] message ; private Severity level ; private String heading ; } src/uk/co/mccombe/terrain/LatLongDialog.form0000777000000000000000000001666514573075134016227 0ustar
src/uk/co/mccombe/terrain/LatLongDialog.java0000777000000000000000000003455614573075134016204 0ustar /* * LatLongDialog.java * * Created on 25 February 2008, 16:46 */ package uk.co.mccombe.terrain; import uk.co.mccombe.mapping.*; import java.awt.event.ActionEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.DefaultComboBoxModel; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JTextField; /** * * @author Mike */ public class LatLongDialog extends javax.swing.JDialog { /** A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; /** Creates new form LatLongDialog */ public LatLongDialog(java.awt.Frame parent, boolean modal, MappingToolkit toolbox, Spherical location) { super(parent, modal); initComponents(); toolkit = toolbox; datum = Datum.WGS_1984; ellipsoid = Ellipsoid.GRS80; java.util.Vector datumList = toolkit.getDatumList(); datumSet.setModel(new DefaultComboBoxModel(datumList)); datumSet.setSelectedItem(datum); java.util.Vector ellipsoidList = toolkit.getEllipsoidList(); ellipsoidSet.setModel(new DefaultComboBoxModel(ellipsoidList)); ellipsoidSet.setSelectedItem(ellipsoid); latText.setInputVerifier(new LatVerifier()); longText.setInputVerifier(new LonVerifier()); datumSet.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { datumSetActionPerformed(evt); } }); ellipsoidSet.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { ellipsoidSetActionPerformed(evt); } }); ellipsoid = (Ellipsoid) ellipsoidSet.getSelectedItem(); datum = (Datum) datumSet.getSelectedItem(); Spherical sph = location; place = sph.getPosition(); String[] lls = sph.toString().trim().split("\\s+"); String initLat = String.format("%s %s %s %s", lls[0], lls[1], lls[2], lls[3]); String initLon = String.format("%s %s %s %s", lls[4], lls[5], lls[6], lls[7]); latText.setText(initLat); longText.setText(initLon); } private void datumSetActionPerformed(ActionEvent evt) { writeTextFields(place); } private void ellipsoidSetActionPerformed(ActionEvent evt) { writeTextFields(place); } /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */ public int getReturnStatus() { return returnStatus; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); longText = new javax.swing.JTextField(); latText = new javax.swing.JTextField(); ellipsoidSet = new javax.swing.JComboBox(); datumSet = new javax.swing.JComboBox(); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); okButton.setText("OK"); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); jLabel1.setText("Latitude"); jLabel2.setText("Longitude"); jLabel3.setText("Ellipsoid"); jLabel4.setText("Datum"); longText.setText(" "); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel2) .addComponent(jLabel1) .addComponent(jLabel3) .addComponent(jLabel4)) .addGap(14, 14, 14) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(datumSet, javax.swing.GroupLayout.Alignment.TRAILING, 0, 246, Short.MAX_VALUE) .addComponent(longText, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) .addComponent(ellipsoidSet, javax.swing.GroupLayout.Alignment.TRAILING, 0, 246, Short.MAX_VALUE) .addComponent(latText, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE))) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton) .addContainerGap()) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) .addComponent(latText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(longText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) .addComponent(ellipsoidSet, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel4) .addComponent(datumSet, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 27, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)) .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed if (parseTextFields()) { doClose(RET_OK); } }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed /** Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog doClose(RET_CANCEL); }//GEN-LAST:event_closeDialog private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } public Datum getDatum() { return (Datum) datumSet.getSelectedItem(); } public void setDatum(Datum d) { datumSet.setSelectedItem(d); datum = d; } public Ellipsoid getEllipsoid() { return (Ellipsoid) ellipsoidSet.getSelectedItem(); } public void setEllipsoid(Ellipsoid e) { ellipsoidSet.setSelectedItem(e); ellipsoid = e; } public Position getPosition() { return place; } public void setPosition(Position here) { writeTextFields(here); } /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { LatLongDialog dialog = new LatLongDialog(new javax.swing.JFrame(), true, new MappingToolkit(), new Spherical(new LatLong(0.0, 0.0), Ellipsoid.GRS80, Datum.WGS_1984)); dialog.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } private boolean parseTextFields() { String latval = latText.getText().trim(); String lonval = longText.getText().trim(); boolean res = parseLatitude(latval) && parseLongitude(lonval); if (!res) { return false; } LatLong ll = new LatLong(lat, lon); Spherical sph = new Spherical(ll, ellipsoid, datum); place = sph.getPosition(); return true; } private void writeTextFields(Position p){ place = p; ellipsoid = (Ellipsoid) ellipsoidSet.getSelectedItem(); datum = (Datum) datumSet.getSelectedItem(); Spherical sph = new Spherical(place, ellipsoid, datum); String[] lls = sph.toString().trim().split("\\s+"); String initLat = String.format("%s %s %s %s", lls[0], lls[1], lls[2], lls[3]); String initLon = String.format("%s %s %s %s", lls[4], lls[5], lls[6], lls[7]); latText.setText(initLat); longText.setText(initLon); } private boolean parseLatitude(String val) { Matcher match1 = realRegex.matcher(val); Matcher match2 = latDMSRegex.matcher(val); if (match1.find()) { String res = match1.group(0); lat = Double.parseDouble(res); return true; } else if (match2.find()) { String degs = match2.group(1); String ns = match2.group(2); String mins = match2.group(3); String secs = match2.group(4); int deg = Integer.parseInt(degs); int min = Integer.parseInt(mins); double sec = Double.parseDouble(secs); try { lat = LatLong.latDMS(ns, deg, min, sec); return true; } catch (LatLongFormatException e) { } } return false; } private boolean parseLongitude(String val) { Matcher match1 = realRegex.matcher(val); Matcher match2 = lonDMSRegex.matcher(val); if (match1.find()) { lon = Double.parseDouble(val); return true; } else if (match2.find()) { String degs = match2.group(1); String ew = match2.group(2); String mins = match2.group(3); String secs = match2.group(4); int deg = Integer.parseInt(degs); int min = Integer.parseInt(mins); double sec = Double.parseDouble(secs); try { lon = LatLong.lonDMS(ew, deg, min, sec); return true; } catch (LatLongFormatException e) { } } return false; } private class LatVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String val = field.getText().trim(); return parseLatitude(val); } } private class LonVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String val = field.getText().trim(); return parseLongitude(val); } } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JComboBox datumSet; private javax.swing.JComboBox ellipsoidSet; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JTextField latText; private javax.swing.JTextField longText; private javax.swing.JButton okButton; // End of variables declaration//GEN-END:variables private MappingToolkit toolkit; private Position place = null; private double lat, lon; private int returnStatus = RET_CANCEL; private Ellipsoid ellipsoid; private Datum datum; private static final Pattern realRegex = Pattern.compile("^[\\-+]?\\d+(\\.\\d+)?$"); private static final Pattern latDMSRegex = Pattern.compile("^(\\d{1,2})\\s*([NS])\\s+(\\d{1,2})\\'?\\s+(\\d{1,2}(\\.\\d+)?)\"?$"); private static final Pattern lonDMSRegex = Pattern.compile("^(\\d{1,3})\\s*([EW])\\s+(\\d{1,2})\\'?\\s+(\\d{1,2}(\\.\\d+)?)\"?$"); } src/uk/co/mccombe/terrain/LocationDialog.form0000777000000000000000000002551414573075134016430 0ustar
src/uk/co/mccombe/terrain/LocationDialog.java0000777000000000000000000004251414573075134016405 0ustar /* * LocationDialog.java * * Created on 19 January 2008, 16:24 */ package uk.co.mccombe.terrain; import uk.co.mccombe.mapping.*; import java.awt.Component; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JTextField; import javax.swing.JLabel; import javax.swing.ListCellRenderer; import javax.swing.SwingConstants; /** * * @author Mike */ public class LocationDialog extends javax.swing.JDialog { private PropertySet properties; /** Creates new form LocationDialog * @param parent * @param modal * @param toolbox * @param propertyset */ public LocationDialog(java.awt.Frame parent, boolean modal, MappingToolkit toolbox, PropertySet propertyset) { super(parent, modal); initComponents(); toolkit = toolbox; properties = propertyset; gridRefText.setInputVerifier(new GridRefVerifier()); ewText.setInputVerifier(new NumericVerifier()); nsText.setInputVerifier(new NumericVerifier()); spacingText.setInputVerifier(new NumericVerifier()); for (int i = 0; i < positions.length; i++) { java.net.URL imageURL = TerrainFrame.class.getResource(String.format("images/pos%d.png", i + 1)); if (imageURL != null) { PositionItem pos = new PositionItem(positions[i], new ImageIcon(imageURL)); posList.addItem(pos); } } posList.setRenderer(new PositionRenderer()); ourParent = parent ; } int getReturnStatus() { return returnStatus; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { gridRefText = new javax.swing.JTextField(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); exampleText = new javax.swing.JLabel(); jLabel5 = new javax.swing.JLabel(); coordNameText = new javax.swing.JTextField(); ewText = new javax.swing.JTextField(); nsText = new javax.swing.JTextField(); spacingText = new javax.swing.JTextField(); posList = new javax.swing.JComboBox(); jLabel6 = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); gridRefText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); gridRefText.setText("ST430969"); jLabel1.setText("Grid Reference"); jLabel2.setText("E-W Range (m)"); jLabel3.setText("N-S Range (m)"); jLabel4.setText("Spacing (m)"); okButton.setText("OK"); okButton.setPreferredSize(new java.awt.Dimension(65, 23)); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); exampleText.setFont(new java.awt.Font("Arial", 0, 10)); exampleText.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); jLabel5.setText("Selected Coordinate System"); coordNameText.setEditable(false); coordNameText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); ewText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); nsText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); spacingText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); posList.setMaximumSize(new java.awt.Dimension(80, 40)); jLabel6.setText("Grid Ref is at"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() .addComponent(jLabel6) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 310, Short.MAX_VALUE) .addComponent(posList, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 123, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel5, javax.swing.GroupLayout.PREFERRED_SIZE, 187, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 49, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(gridRefText, javax.swing.GroupLayout.DEFAULT_SIZE, 238, Short.MAX_VALUE) .addComponent(coordNameText, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 238, Short.MAX_VALUE))) .addGroup(layout.createSequentialGroup() .addComponent(cancelButton) .addGap(18, 18, 18) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel4) .addComponent(jLabel3) .addComponent(jLabel2)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 270, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) .addComponent(spacingText) .addComponent(ewText, javax.swing.GroupLayout.DEFAULT_SIZE, 131, Short.MAX_VALUE) .addComponent(nsText))) .addComponent(exampleText, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(coordNameText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel5)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(gridRefText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(1, 1, 1) .addComponent(exampleText, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(ewText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel2)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(nsText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel3)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(spacingText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel4)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(posList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel6)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 50, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(cancelButton)) .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed GridRefVerifier checker = new GridRefVerifier(); if (checker.verify(gridRefText)) { doClose(RET_OK); } else { javax.swing.JOptionPane.showMessageDialog(ourParent,new String[] {"Incorrect format for grid reference","Please correct or cancel"}, "Warning", javax.swing.JOptionPane.WARNING_MESSAGE); } }//GEN-LAST:event_okButtonActionPerformed /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { LocationDialog dialog = new LocationDialog(new javax.swing.JFrame(), true, new MappingToolkit(), new PropertySet("terrain.properties", new DefaultProperties())); dialog.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } public String getGridRef() { return gridRefText.getText(); } public String getEW() { return ewText.getText(); } public void setEW(String t) { ewText.setText(t); } public String getNS() { return nsText.getText(); } public void setNS(String t) { nsText.setText(t); } public String getSpacing() { return spacingText.getText(); } public void setSpacing(String t) { spacingText.setText(t); } public void setGridRef(String sampleCoordinate) { gridRefText.setText(sampleCoordinate); } public void setExample(String sampleCoordinate) { exampleText.setText(sampleCoordinate); } public void setcoordName(String coordname) { coordNameText.setText(coordname); } public class GridRefVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String val = field.getText().trim(); try { String typename = properties.get(TerrainProperties.COORD); String ename = properties.get(TerrainProperties.ELLIPSOID); String dname = properties.get(TerrainProperties.DATUM); Ellipsoid ellipse = toolkit.getEllipsoid(ename); Datum datum = toolkit.getDatum(dname); CoordinateSystem c = toolkit.makeCoordinateSystem(typename, val, ellipse, datum); } catch (Exception ex) { return false; } return true; } } private class NumericVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String val = field.getText().trim(); java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); java.text.ParsePosition pos = new java.text.ParsePosition(0); int n = val.length(); double x = nf.parse(val, pos).doubleValue(); if (pos.getIndex() != n) { return false; } field.setText(String.format(formatString, x)); return true; } } private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } public int getNSAlignment() { int i = posList.getSelectedIndex(); return verts[i]; } public int getEWAlignment() { int i = posList.getSelectedIndex(); return horzs[i]; } /** A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; private class PositionRenderer extends JLabel implements ListCellRenderer { public PositionRenderer() { setOpaque(true); setHorizontalAlignment(LEADING); setVerticalAlignment(CENTER); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { PositionItem p = (PositionItem) value; if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } setIcon(p.getIcon()); setText(p.getText()); setFont(list.getFont()); return this; } } public int getAlignment() { return posList.getSelectedIndex(); } public void setAlignment(int i) { posList.setSelectedIndex(i); } private class PositionItem { public PositionItem(String text, Icon pic) { label = text; image = pic; } public Icon getIcon() { return image; } public String getText() { return label; } private Icon image; private String label; } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JTextField coordNameText; private javax.swing.JTextField ewText; private javax.swing.JLabel exampleText; private javax.swing.JTextField gridRefText; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; private javax.swing.JTextField nsText; private javax.swing.JButton okButton; private javax.swing.JComboBox posList; private javax.swing.JTextField spacingText; // End of variables declaration//GEN-END:variables private int returnStatus = RET_CANCEL; private static final String formatString = "%12.2f"; private MappingToolkit toolkit; private String[] positions = {"SW", "S", "SE", "W", "Centre", "E", "NW", "N", "NE"}; private static final int[] verts = {SwingConstants.BOTTOM, SwingConstants.BOTTOM, SwingConstants.BOTTOM, SwingConstants.CENTER, SwingConstants.CENTER, SwingConstants.CENTER, SwingConstants.TOP, SwingConstants.TOP, SwingConstants.TOP }; private static final int[] horzs = {SwingConstants.LEFT, SwingConstants.CENTER, SwingConstants.RIGHT, SwingConstants.LEFT, SwingConstants.CENTER, SwingConstants.RIGHT, SwingConstants.LEFT, SwingConstants.CENTER, SwingConstants.RIGHT }; private java.awt.Frame ourParent ; } src/uk/co/mccombe/terrain/MissingDataFileException.java0000777000000000000000000000120414573074222020363 0ustar /** * MissingDataFileException -- thrown when we're unable to open a particular file * containing tile data. This happens if the user has failed to download the required tile * or has put it in the wrong place. Go gently here and try to be helpful! * * @author Mike McCombe * */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class MissingDataFileException extends Exception { /** * Constructs an instance of MissingDataFileException with the specified detail message. * @param msg the detail message. */ public MissingDataFileException(String msg) { super(msg); } } src/uk/co/mccombe/terrain/MosaicPanel.form0000777000000000000000000000674314163163576015741 0ustar
src/uk/co/mccombe/terrain/MosaicPanel.java0000777000000000000000000004277614573074222015717 0ustar /* * MosaicPanel.java * * Created on 21 January 2008, 10:40 */ package uk.co.mccombe.terrain; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.*; import uk.co.mccombe.mapping.XYZ; /** *

MosaicPanel is used to display a coloured relief map and a height key (in * sensible units). It also handles mouse events, firing "mouse" * PropertyChangeEvents as the mouse is moved over the map. Handling of this * component's size etc. is left to the parent's LayoutManager.

* *

Mouse events contain a XYZ object where the x and y components indicate * the position of the mouse as a proportion of the width and height of the map. * These are double values between 0.0 and 1.0, with [0.0, 0.0] at the * bottom-left. The z() component contains the height (or MISSING) contained in * the databuffer. The value MOVED_OUT is fired when the mouse goes outside the * bounds of the map.

* * @author Mike McCombe */ public class MosaicPanel extends JPanel { private double stepSize; /** * Creates new form BeanForm */ public MosaicPanel() { initComponents(); stepSize = 0.0; scrollpane.setViewportView(mapCanvas); } /** * Set the height data for the map and determine the height scale for the * key * * @param buffer - a double[][] array containing the map data. First * dimension is "northing", second is "easting" */ public void setDataTable(float[][] buffer, DEMReader reader) { dataTable = buffer; minval = 1.0E99; maxval = -1.0E99; for (float[] row : dataTable) { for (float x : row) { if (x != reader.missingValue()) { minval = Math.min(minval, x); maxval = Math.max(maxval, x); } } } ydim = dataTable.length; xdim = dataTable[0].length; int digits = String.format("%d", (long) maxval).length(); double topLimit = Math.pow(10.0, digits); if (topLimit / maxval > 5.2) { topLimit /= 5.0; } else if (topLimit / maxval > 2.0) { topLimit /= 2.0; } stepSize = topLimit / 10.0; mapCanvas.revalidate(); } /** * Paint the key panel and background * * @param g - the Graphics object from awt */ @Override public void paint(java.awt.Graphics g) { super.paint(g); if (stepSize == 0) { return; } Font font = g.getFont().deriveFont(Font.PLAIN, 11.0f); g.setFont(font); Rectangle r = keyPanel.getBounds(); int dy = r.height / 15; int xgap = 5; double xbase = r.x + r.width / 2.0; FontMetrics f = g.getFontMetrics(); g.setColor(Color.BLACK); for (int i = 0; i < colours.length; i++) { String val = String.format("%d", (int) (i * stepSize)); Rectangle2D siz = f.getStringBounds(val, g); int x = (int) (xbase - siz.getWidth()); int y = (int) ((colours.length - i) * dy + siz.getHeight()); g.drawString(val, x, y); g.setColor(colours[i]); g.fillRect((int) (xbase + xgap), (int) (y + siz.getCenterY()), r.width / 4, dy); g.setColor(Color.BLACK); g.drawRect((int) (xbase + xgap), (int) (y + siz.getCenterY()), r.width / 4, dy); } } /** * Translate altitude into the requisite map colour * * @param altitude - in metres * @return the corresponding Color */ private Color selectColour(float altitude) { if (altitude <= 0.0) { return colours[0]; } if (altitude == SRTM2Reader.MISSING) { return Color.BLACK; } double range = maxval - minval; double above = altitude - minval; int i = 1 + (int) (altitude / stepSize); return colours[i]; } /** * Add a PropertyChangeListener (for the "mouse" property changes) * * @param l */ @Override public void addPropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } /** * Get this object's internal PropertyChangeListener (note: slightly * non-standard) * * @return the current PropertyChangeListener */ public PropertyChangeListener getPropertyChangeListener() { return listener; } private class MapCanvas extends JLabel implements Scrollable { public MapCanvas() { super(); setAutoscrolls(true); addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1); scrollRectToVisible(r); Point p = e.getPoint(); convertPoint(p); } public void mouseMoved(MouseEvent e) { Point p = e.getPoint(); convertPoint(p); } }); addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { double v = SRTM2Reader.MISSING; XYZ val = new XYZ(v, v, v); pcs.firePropertyChange("mouse", oldPoint, val); oldPoint = val; } }); } public Dimension getPreferredSize() { JViewport v = scrollpane.getViewport(); Rectangle r = v.getViewRect(); float xscale = (float) r.width / xdim; float yscale = (float) r.height / ydim; float scale = Math.min(xscale, yscale); pixPerCell = Math.max((int) scale, 1); // pixPerCell = 1; w = xdim * pixPerCell; h = ydim * pixPerCell; return new Dimension(w, h); } private void convertPoint(Point p) { if (w == 0 || h == 0) { return; } double dx = (p.getX()) / (double) w; double dy = 1.0 - (p.getY()) / (double) h; if (dx < 0.0 || dx >= 1.0 || dy <= 0.0 || dy >= 1.0) { //If the mouse moves outside the edge of the map, fire a "mouse" event containing MISSING //to tell other components. Used to erase the GridRef display in the parent frame. double v = SRTM2Reader.MISSING; XYZ val = new XYZ(v, v, v); pcs.firePropertyChange("mouse", oldPoint, val); oldPoint = val; return; } int ix = ((int) p.getX()) / pixPerCell; int iy = ydim - 1 - ((int) p.getY()) / pixPerCell; double z = dataTable[iy][ix]; XYZ val = new XYZ(dx, dy, z); pcs.firePropertyChange("mouse", oldPoint, val); oldPoint = val; } @Override public void paint(Graphics g) { JViewport v = scrollpane.getViewport(); Rectangle r = v.getViewRect(); float xscale = (float) r.width / xdim; float yscale = (float) r.height / ydim; float scale = Math.min(xscale, yscale); pixPerCell = Math.max((int) scale, 1); w = xdim * pixPerCell; h = ydim * pixPerCell; // x0 = r.x + (int) ((float) r.width / 2.0 - (float) (w) / 2.0); // y0 = r.y + (int) ((float) r.height / 2.0 - (float) (h) / 2.0); x0 = (int) ((float) r.width / 2.0 - (float) (w) / 2.0); y0 = (int) ((float) r.height / 2.0 - (float) (h) / 2.0); for (int j = 0; j < ydim; j++) { for (int i = 0; i < xdim; i++) { float z = dataTable[j][i]; int xpos = i * pixPerCell; int ypos = (ydim - 1 - j) * pixPerCell; Color c = selectColour(z); g.setColor(c); g.fillRect(xpos, ypos, pixPerCell, pixPerCell); } } } public Rectangle getMapRectangle() { return new Rectangle(w, h); } private int x0 = 0; private int y0 = 0; private int w = 0; private int h = 0; private XYZ oldPoint = new XYZ(0.0, 0.0, 0.0); private int pixPerCell = 0; @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return maxUnitIncrement; } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { if (orientation == SwingConstants.HORIZONTAL) { return visibleRect.width - maxUnitIncrement; } else { return visibleRect.height - maxUnitIncrement; } } @Override public boolean getScrollableTracksViewportWidth() { return false; } @Override public boolean getScrollableTracksViewportHeight() { return false; } private int maxUnitIncrement = 20; } /** * Private class built on a JPanel to hold the map and handle mouse events */ private class MapPanel extends JPanel { public MapPanel() { super(); addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); convertPoint(p); } public void mouseMoved(MouseEvent e) { Point p = e.getPoint(); convertPoint(p); } }); } private void convertPoint(Point p) { if (w == 0 || h == 0) { return; } double dx = (p.getX() - (double) x0) / (double) w; double dy = 1.0 - (p.getY() - (double) y0) / (double) h; if (dx < 0.0 || dx >= 1.0 || dy <= 0.0 || dy >= 1.0) { //If the mouse moves outside the edge of the map, fire a "mouse" event containing MISSING //to tell other components. Used to erase the GridRef display in the parent frame. double v = SRTM2Reader.MISSING; XYZ val = new XYZ(v, v, v); pcs.firePropertyChange("mouse", oldPoint, val); oldPoint = val; return; } int ix = ((int) p.getX() - x0) / pixPerCell; int iy = ydim - 1 - ((int) p.getY() - y0) / pixPerCell; double z = dataTable[iy][ix]; XYZ val = new XYZ(dx, dy, z); pcs.firePropertyChange("mouse", oldPoint, val); oldPoint = val; } @Override public void paint(Graphics g) { JViewport v = scrollpane.getViewport(); Rectangle r = v.getViewRect(); float xscale = (float) r.width / xdim; float yscale = (float) r.height / ydim; float scale = Math.min(xscale, yscale); pixPerCell = Math.max((int) scale, 1); // int pixPerCell = 10; w = xdim * pixPerCell; h = ydim * pixPerCell; x0 = r.x + (int) ((float) r.width / 2.0 - (float) (w) / 2.0); y0 = r.y + (int) ((float) r.height / 2.0 - (float) (h) / 2.0); for (int j = 0; j < ydim; j++) { for (int i = 0; i < xdim; i++) { float z = dataTable[j][i]; int xpos = x0 + i * pixPerCell; int ypos = y0 + (ydim - 1 - j) * pixPerCell; Color c = selectColour(z); g.setColor(c); g.fillRect(xpos, ypos, pixPerCell, pixPerCell); } } } private int x0 = 0; private int y0 = 0; private int w = 0; private int h = 0; private XYZ oldPoint = new XYZ(0.0, 0.0, 0.0); private int pixPerCell = 0; } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { keyPanel = new javax.swing.JPanel(); jSeparator1 = new javax.swing.JSeparator(); scrollpane = new javax.swing.JScrollPane(); javax.swing.GroupLayout keyPanelLayout = new javax.swing.GroupLayout(keyPanel); keyPanel.setLayout(keyPanelLayout); keyPanelLayout.setHorizontalGroup( keyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 125, Short.MAX_VALUE) ); keyPanelLayout.setVerticalGroup( keyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 354, Short.MAX_VALUE) ); jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(keyPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(404, Short.MAX_VALUE)) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(155, 155, 155) .addComponent(scrollpane, javax.swing.GroupLayout.DEFAULT_SIZE, 386, Short.MAX_VALUE) .addContainerGap())) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(keyPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jSeparator1) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(scrollpane, javax.swing.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE) .addContainerGap())) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JSeparator jSeparator1; private javax.swing.JPanel keyPanel; private javax.swing.JScrollPane scrollpane; // End of variables declaration//GEN-END:variables private static final Color PALE_BLUE = new Color(150, 150, 255); private static final Color MID_GREEN = new Color(149, 255, 52); private static final Color PALE_GREEN = new Color(200, 255, 150); private static final Color PALE_YELLOW = new Color(255, 255, 102); private static final Color PALE_ORANGE = new Color(255, 200, 50); private static final Color MID_ORANGE = new Color(255, 153, 0); private static final Color PALE_RED = new Color(255, 203, 203); private static final Color MID_RED = new Color(255, 153, 153); private static final Color MID_PURPLE = new Color(153, 102, 255); private static final Color PALE_PURPLE = new Color(204, 153, 255); private static final Color[] colours = {PALE_BLUE, MID_GREEN, PALE_GREEN, PALE_YELLOW, PALE_ORANGE, MID_ORANGE, PALE_RED, MID_RED, MID_PURPLE, PALE_PURPLE, Color.WHITE }; private float[][] dataTable = null; private double minval = 1.0E99; private double maxval = -1.0E99; private int xdim = 0; private int ydim = 0; private MapPanel mapPanel = new MapPanel(); private MapCanvas mapCanvas = new MapCanvas(); private PropertyChangeSupport pcs = new PropertyChangeSupport(this); private PropertyChangeListener listener = new java.beans.PropertyChangeListener() { public void propertyChange(java.beans.PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); String propertyValue = evt.getNewValue().toString(); //Property change handling goes here, if needed. } }; public static final XYZ MOVED_OUT = new XYZ(SRTM2Reader.MISSING, SRTM2Reader.MISSING, SRTM2Reader.MISSING); } src/uk/co/mccombe/terrain/NASADEMReader.java0000777000000000000000000000452714573074222015707 0ustar /** * Implementation of a DEMReader for the NASA 1 arc-second dataset. * The complete set of tiles is held at https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_HGT.001/2000.02.11/ * This dataset is not auto-downloadable by TerrainTool. * To access the data you need to be logged in to a NASA Earthdata account * You can set up one of these by registering at https://urs.earthdata.nasa.gov/users/new/ */ package uk.co.mccombe.terrain; import javax.swing.JComponent; /** * * @author Mike McCombe */ public class NASADEMReader extends SRTM3Reader { public NASADEMReader(JComponent item) throws MissingDataFileException, DataFileException { super(item); } @Override public String formatstring() { return FILENAMEFORMAT; } @Override public String zipEntryName(String name) { int i = name.length(); String s = name.substring(i - 7); return s + ".hgt"; } @Override public String datasetName() { return NAME; } @Override public String[] copyright() { return copyright; } @Override protected String eastWest(double val) { if (val >= 0) { return "e"; } return "w"; } @Override protected String northSouth(double val) { if (val >= 0) { return "n"; } return "s"; } @Override public boolean downloadable() { return DOWNLOADABLE; } @Override protected String getDownloadSiteName() { return demURL; } @Override protected void setDownloadSiteName(String s){ demURL = s; } protected String demURL = "Not Defined"; protected static boolean DOWNLOADABLE = false; protected static final String SITENAME = "https://e4ftl01.cr.usgs.gov/MEASURES/NASADEM_HGT.001/2000.02.11/"; protected static final String FILENAMEFORMAT = "NASADEM_HGT_%1s%02d%1s%03d"; protected static final String NAME = "NASA 1 arc-second"; protected static final String[] copyright = {"Data derived from:-", "", "NASA JPL (2020). NASADEM Merged DEM Global 1 arc second V001. ", "NASA EOSDIS Land Processes DAAC.", "Accessed from https://doi.org/10.5067/MEaSUREs/NASADEM/NASADEM_HGT.001"}; } src/uk/co/mccombe/terrain/OffsetDialog.form0000777000000000000000000001624314163163576016110 0ustar
src/uk/co/mccombe/terrain/OffsetDialog.java0000777000000000000000000002555214573074222016063 0ustar /* * OffsetDialog.java * * Created on 03 February 2008, 12:54 */ package uk.co.mccombe.terrain; import java.text.ParseException; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JTextField; /** * * @author Mike */ public class OffsetDialog extends javax.swing.JDialog { /** A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; /** Creates new form OffsetDialog * @param parent the parent frame for the Dialog * @param modal true if the Dialog is to be modal * @param east - initialiser for the East offset field * @param north - initialiser for the North offset field * @param ht - initialiser for the height offset field */ public OffsetDialog(java.awt.Frame parent, boolean modal, double east, double north, double ht) { super(parent, modal); initComponents(); eastText.setInputVerifier(numbercheck); northText.setInputVerifier(numbercheck); heightText.setInputVerifier(numbercheck); eastText.setText(String.format(formatString, east)); northText.setText(String.format(formatString, north)); heightText.setText(String.format(formatString, ht)); } /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */ public int getReturnStatus() { return returnStatus; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); eastText = new javax.swing.JTextField(); northText = new javax.swing.JTextField(); heightText = new javax.swing.JTextField(); setTitle("Set offset values"); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); okButton.setText("OK"); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); jLabel1.setText("The following offsets will be added to data when it is saved"); jLabel2.setText("Easting"); jLabel3.setText("Northing"); jLabel4.setText("Height"); eastText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); eastText.setText("0.0"); northText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); northText.setText("0.0"); heightText.setHorizontalAlignment(javax.swing.JTextField.TRAILING); heightText.setText("0.0"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap(250, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton) .addContainerGap()) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 325, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(65, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel3) .addComponent(jLabel4)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(heightText) .addComponent(northText) .addComponent(eastText, javax.swing.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE)) .addContainerGap(201, Short.MAX_VALUE)) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(eastText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) .addComponent(northText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel4) .addComponent(heightText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 16, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)) .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed eastOffset = parseDouble(eastText.getText()); northOffset = parseDouble(northText.getText()); heightOffset = parseDouble(heightText.getText()); doClose(RET_OK); }//GEN-LAST:event_okButtonActionPerformed private double parseDouble(String s){ String val = s.trim(); if (val.startsWith("+")) { val = val.substring(1); } java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); java.text.ParsePosition pos = new java.text.ParsePosition(0); return nf.parse(val, pos).doubleValue(); } private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed /** Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog doClose(RET_CANCEL); }//GEN-LAST:event_closeDialog private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { OffsetDialog dialog = new OffsetDialog(new javax.swing.JFrame(), true, 0.0, 0.0, 0.0); dialog.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } public double getEastOffset() { return eastOffset; } public double getNorthOffset() { return northOffset; } public double getHeightOffset() { return heightOffset; } private class NumericVerifier extends InputVerifier { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String val = field.getText().trim(); if (val.startsWith("+")) { val = val.substring(1); } java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); java.text.ParsePosition pos = new java.text.ParsePosition(0); int n = val.length(); double x = nf.parse(val, pos).doubleValue(); if (pos.getIndex() != n) { return false; } field.setText(String.format(formatString, x)); return true; /* boolean res = val.trim().matches("[+-]?\\d+\\[,.]?\\d*$"); if (res) { double x = Double.parseDouble(val.trim()); field.setText(String.format(formatString, x)); } return res; */ } } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JTextField eastText; private javax.swing.JTextField heightText; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JTextField northText; private javax.swing.JButton okButton; // End of variables declaration//GEN-END:variables private int returnStatus = RET_CANCEL; private static final String formatString = "%12.1f"; private double eastOffset = 0.0; private double northOffset = 0.0; private double heightOffset = 0.0; private NumericVerifier numbercheck = new NumericVerifier(); } src/uk/co/mccombe/terrain/Pathnames.java0000777000000000000000000000071614573074222015430 0ustar package uk.co.mccombe.terrain; /** * Define interface for specifying paths for locating the properties and data files. * * @author Mike McCombe */ public interface Pathnames { /** * * @return String containing the pathname for terrain.properties */ public String propertiesPath(); /** * * @return String containing the pathname for the data-cache directory */ public String dataPath(); } src/uk/co/mccombe/terrain/PropertySet.java0000777000000000000000000000417714573074222016015 0ustar /* * PropertySet.java * * Created on 12 May 2006, 10:08 * */ package uk.co.mccombe.terrain; import java.io.File; import java.io.IOException; /** * * @author Mike McCombe */ public class PropertySet { /** Creates a new instance of PropertySet */ public PropertySet(String pathname, java.util.Properties defaults) { File dir = new File(pathname); if (!dir.isDirectory()) { boolean madeDirectory = dir.mkdir(); if (!madeDirectory) { return ; } } String filename = pathname + "terrain.properties"; file = filename ; boolean propflag = true; properties = new java.util.Properties(defaults); java.util.Properties prop = System.getProperties(); try { properties.loadFromXML(new java.io.FileInputStream(filename)); java.util.Enumeration enums = properties.propertyNames(); while(enums.hasMoreElements()){ String s = (String) enums.nextElement(); String val = properties.getProperty(s); prop.setProperty(s,val); } propflag = false ; } catch (IOException ex) { //Do nothing here. "propflag" causes new property file to be created. } if(propflag) { try { properties.storeToXML(new java.io.FileOutputStream(filename), ""); } catch (java.io.IOException ex) { } } System.setProperties(prop); valid = true ; } public String get(TerrainProperties key){ return properties.getProperty(key.toString()); } public void set(TerrainProperties key, String value){ properties.setProperty(key.toString(), value); } public void save() throws java.io.IOException { properties.storeToXML(new java.io.FileOutputStream(file),""); } public boolean isValid() { return valid; } private java.util.Properties properties ; private String file ; private boolean valid = false ; } src/uk/co/mccombe/terrain/RegionSelect.form0000777000000000000000000001050114163163576016114 0ustar
src/uk/co/mccombe/terrain/RegionSelect.java0000777000000000000000000001331514573074222016072 0ustar /* * RegionSelect.java * * Created on 20 January 2008, 17:07 */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class RegionSelect extends javax.swing.JDialog { /** A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; /** Creates new form RegionSelect */ public RegionSelect(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); } /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */ public int getReturnStatus() { return returnStatus; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); regionSet = new javax.swing.JComboBox(); setTitle("Select data region"); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); okButton.setText("OK"); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); regionSet.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Africa", "Australia", "Eurasia", "North_America", "South_America", "Islands" })); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(27, 27, 27) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(regionSet, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createSequentialGroup() .addComponent(cancelButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap(22, Short.MAX_VALUE)) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(19, 19, 19) .addComponent(regionSet, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(42, 42, 42) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); pack(); }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed doClose(RET_OK); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed /** Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog doClose(RET_CANCEL); }//GEN-LAST:event_closeDialog private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } public String getSelection(){ return (String)regionSet.getSelectedItem(); } public void select(String region){ regionSet.setSelectedItem(region); } /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { RegionSelect dialog = new RegionSelect(new javax.swing.JFrame(), true); dialog.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JButton okButton; private javax.swing.JComboBox regionSet; // End of variables declaration//GEN-END:variables private int returnStatus = RET_CANCEL; } src/uk/co/mccombe/terrain/SRTM2Reader.java0000777000000000000000000000474414573074222015507 0ustar /* * Implementation of a DEMReader to download and decode the SRTM2 dataset */ package uk.co.mccombe.terrain; import javax.swing.JComponent; /** * * @author Mike McCombe */ public class SRTM2Reader extends DEMReader { public SRTM2Reader(JComponent item) throws MissingDataFileException, DataFileException { super(item); } public String datasetName() { return NAME; } public boolean downloadable() { return DOWNLOADABLE; } public int recordlength() { return RECORDLENGTH; } public String formatstring() { return FILENAMEFORMAT; } public String extn() { return EXTN; } public boolean littleendian() { return LITTLEENDIAN; } public int missingValue() { return MISSINGVALUE; } public String zipEntryName(String name) { return name + ".hgt"; } @Override public String[] copyright() { return copyright; } @Override protected String getDownloadSiteName() { return SITENAME + getProperty(TerrainProperties.REGION); } @Override protected String eastWest(double val) { if (val >= 0) { return "e"; } return "w"; } @Override protected String northSouth(double val) { if (val >= 0) { return "n"; } return "s"; } @Override protected void setDownloadSiteName(String s) { demURL = s; } /** * Maximum latitude of SRTM2 dataset * Latitude must be less than 61.0 * @return double 61.0 */ @Override public double maxLat() { return 61.0; } /** * Minimum latitude of SRTM2 dataset * Latitude must be greater or equal to -56.0 * @return double -56.0 */ @Override public double minLat() { return -56.0 ; } protected String demURL = "Not Downloadable"; protected static final String SITENAME = "https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/"; public static final boolean DOWNLOADABLE = false; protected static final int RECORDLENGTH = 1201; protected static final String FILENAMEFORMAT = "%1s%02d%1s%03d"; protected static final String NAME = "Shuttle Radar Topography Mission"; protected static final String EXTN = ".hgt.zip"; protected static final boolean LITTLEENDIAN = false; protected static final int MISSINGVALUE = -32768; protected String[] copyright = {"SRTM DEM data is public-domain."}; } src/uk/co/mccombe/terrain/SRTM3Reader.java0000777000000000000000000000255714573074222015510 0ustar /* * SRTM3Reader a DEMReader abstract implementation to read and decode SRTM3 datasets */ package uk.co.mccombe.terrain; import javax.swing.JComponent; /** * * @author Mike McCombe */ public abstract class SRTM3Reader extends DEMReader { public SRTM3Reader(JComponent item) throws MissingDataFileException,DataFileException { super(item); } @Override public int recordlength() { return recordlength; } @Override public String extn() { return extn; } @Override public boolean littleendian() { return littleendian; } @Override public int missingValue() { return missingValue; } /** * Maximum latitude of SRTM3 dataset * Latitude must be less than 61.0 * @return double 61.0 */ @Override public double maxLat() { return 61.0; } /** * Minimum latitude of SRTM3 dataset * Latitude must be greater or equal to -56.0 * @return double -56.0 */ @Override public double minLat() { return -56.0 ; } protected static final boolean DOWNLOADABLE = false; protected static int recordlength = 3601; protected static String extn = ".zip"; protected static boolean littleendian = false; protected static int missingValue = -32768; } src/uk/co/mccombe/terrain/TerrainFrame.form0000777000000000000000000003325514573130635016116 0ustar
src/uk/co/mccombe/terrain/TerrainFrame.java0000777000000000000000000024762114573130635016100 0ustar /* * TerrainFrame.java * * Version 1.02 17th July 2009 * Fixes problems with parsing numbers in locales other than UK and US * * Please take time to read the licence terms below:- * GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS * Created on 19 January 2008, 16:04 */ package uk.co.mccombe.terrain; import uk.co.mccombe.mapping.*; import uk.co.mccombe.util.*; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.TimeZone; import java.util.concurrent.ExecutionException; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.SwingConstants; import javax.swing.Timer; import javax.swing.UnsupportedLookAndFeelException; /** * * @author Mike */ public class TerrainFrame extends javax.swing.JFrame { /** * Creates new form TerrainFrame */ public TerrainFrame() { initComponents(); setIconImage(new ImageIcon(getClass().getResource("images/FrameIcon.png")).getImage()); try { javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException | IllegalAccessException | ClassNotFoundException | InstantiationException ex) { } // status bar initialization - message timeout, idle icon and busy animation, etc int messageTimeout = 1000; messageTimer = new Timer(messageTimeout, new ActionListener() { public void actionPerformed(ActionEvent e) { eraseMessage(); } }); messageTimer.setRepeats(false); int busyAnimationRate = 30; for (int i = 0; i < busyIcons.length; i++) { java.net.URL imageURL = TerrainFrame.class.getResource(String.format("images/busy-icon%d.png", i)); if (imageURL != null) { busyIcons[i] = new ImageIcon(imageURL); } } busyIconTimer = new Timer(busyAnimationRate, new ActionListener() { public void actionPerformed(ActionEvent e) { busyIconIndex = (busyIconIndex + 1) % busyIcons.length; statusAnimationLabel.setIcon(busyIcons[busyIconIndex]); } }); java.net.URL imageURL = TerrainFrame.class.getResource("images/idle-icon.png"); if (imageURL != null) { idleIcon = new ImageIcon(imageURL); statusAnimationLabel.setIcon(idleIcon); } statusAnimationLabel.setVisible(true); progressBar.setVisible(false); // connecting action tasks to status bar via TaskMonitor PropertyChangeListener listener = new java.beans.PropertyChangeListener() { public void propertyChange(java.beans.PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); String propertyValue = evt.getNewValue().toString(); if ("state".equalsIgnoreCase(propertyName)) { if (propertyValue.equalsIgnoreCase("STARTED")) { if (!busyIconTimer.isRunning()) { statusAnimationLabel.setIcon(busyIcons[0]); busyIconIndex = 0; busyIconTimer.start(); processing = true; } progressBar.setVisible(true); // progressBar.setIndeterminate(true); } else if ("done".equalsIgnoreCase(propertyValue)) { busyIconTimer.stop(); statusAnimationLabel.setIcon(idleIcon); progressBar.setVisible(false); progressBar.setValue(0); processing = false; } } else if ("message".equals(propertyName)) { String text = (String) (evt.getNewValue()); if (text == null || text.trim().length() == 0) { eraseMessage(); return; } statusMessageLabel.setText(text); messageTimer.restart(); } else if ("progress".equals(propertyName)) { int value = (Integer) (evt.getNewValue()); progressBar.setIndeterminate(false); progressBar.setValue(value); } else if ("mouse".equals(propertyName)) { XYZ point = (XYZ) evt.getNewValue(); if (point.equals(MosaicPanel.MOVED_OUT)) { // statusMessageLabel.setText(""); gridrefdisplayed = false; eraseMessage(); messageTimer.restart(); return; } if (processing) { return; } gridrefdisplayed = true; double east = e0 + wid * point.x(); double north = n0 + hgt * point.y(); double asl = point.z(); ENPair en = new ENPair(east, north); try { Projection project; if (usedCentre instanceof UTM) { int zone = ((UTM) usedCentre).getZone(); boolean northernHemisphere = ((UTM) usedCentre).getNorthernHemisphere(); project = (Projection) toolkit.makeCoordinateSystem(currentCoordType, en, zone, currentEllipsoid, currentDatum, northernHemisphere); } else { project = (Projection) toolkit.makeCoordinateSystem(currentCoordType, en, currentEllipsoid, currentDatum); } String heightString = ""; if (asl != SRTM2Reader.MISSING) { heightString = String.format(" Z = %7.1fm", asl); } String text = project.toString() + heightString; statusMessageLabel.setText(text); messageTimer.restart(); } catch (Exception e) { //Do nothing if an exception occurs in the toolkit } } } }; try { reader = new NASADEMReader(statusPanel); reader.setDownloadSiteName(demURL);///// } catch (DataFileException miss) { InfoMessage error = new InfoMessage("Initialisation error", miss.getMessage(), Severity.FATAL); error.display(this); System.exit(0); } catch (MissingDataFileException miss) { InfoMessage error = new InfoMessage("Missing Data File", miss.getMessage(), Severity.FATAL); error.display(this); System.exit(0); } statusPanel.addPropertyChangeListener(listener); pcs.addPropertyChangeListener(listener); pcs.addPropertyChangeListener(mosaic.getPropertyChangeListener()); mosaic.addPropertyChangeListener(listener); jPanel1.add(mosaic, BorderLayout.CENTER); PropertyChangeListener[] readListen = reader.getPropertyChangeListeners(); for (PropertyChangeListener hear : readListen) { pcs.addPropertyChangeListener(hear); } javax.swing.filechooser.FileNameExtensionFilter svxFilter = new javax.swing.filechooser.FileNameExtensionFilter("Survex raw survey data", "svx"); javax.swing.filechooser.FileNameExtensionFilter csvFilter = new javax.swing.filechooser.FileNameExtensionFilter("Comma-separated variables", "csv"); javax.swing.filechooser.FileNameExtensionFilter thFilter = new javax.swing.filechooser.FileNameExtensionFilter("Therion surface data", "th"); chooser.setAcceptAllFileFilterUsed(false); chooser.addChoosableFileFilter(thFilter); chooser.addChoosableFileFilter(csvFilter); chooser.addChoosableFileFilter(svxFilter); // boolean auto = downloadSetting.equalsIgnoreCase("true"); // reader.setDownload(auto); /* String datasource = properties.get(TerrainProperties.FTP); if (datasource != null) { properties.set(TerrainProperties.FTP, datasource); //Make sure data host appears in properties file }*/ String t = properties.get(TerrainProperties.EW); if (t != null) { wid = Double.parseDouble(t); } t = properties.get(TerrainProperties.NS); if (t != null) { hgt = Double.parseDouble(t); } t = properties.get(TerrainProperties.SPACING); if (t != null) { spacing = Double.parseDouble(t); } this.addWindowListener(new MainFrameListener()); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { datasourceGroup = new javax.swing.ButtonGroup(); jPanel1 = new javax.swing.JPanel(); jSeparator2 = new javax.swing.JSeparator(); statusPanel = new javax.swing.JPanel(); statusAnimationLabel = new javax.swing.JLabel(); progressBar = new javax.swing.JProgressBar(); statusMessageLabel = new javax.swing.JLabel(); jSeparator1 = new javax.swing.JSeparator(); menuBar = new javax.swing.JMenuBar(); fileMenu = new javax.swing.JMenu(); createMenuItem = new javax.swing.JMenuItem(); latLongMenuItem = new javax.swing.JMenuItem(); saveAsMenuItem = new javax.swing.JMenuItem(); exitMenuItem = new javax.swing.JMenuItem(); optionsMenu = new javax.swing.JMenu(); coordMenuItem = new javax.swing.JMenuItem(); offsetMenuItem = new javax.swing.JMenuItem(); therionMenuItem = new javax.swing.JMenuItem(); helpMenu = new javax.swing.JMenu(); aboutMenuItem = new javax.swing.JMenuItem(); setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); setTitle("TerrainTool"); setIconImages(null); jPanel1.setLayout(new java.awt.BorderLayout()); jPanel1.add(jSeparator2, java.awt.BorderLayout.PAGE_START); javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel); statusPanel.setLayout(statusPanelLayout); statusPanelLayout.setHorizontalGroup( statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE) .addGroup(statusPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup() .addGap(0, 237, Short.MAX_VALUE) .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(statusAnimationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(statusPanelLayout.createSequentialGroup() .addComponent(statusMessageLabel) .addGap(0, 407, Short.MAX_VALUE))) .addContainerGap()) ); statusPanelLayout.setVerticalGroup( statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(statusPanelLayout.createSequentialGroup() .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 5, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(statusAnimationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(statusMessageLabel)) .addContainerGap()) ); fileMenu.setText("File"); fileMenu.setFont(fileMenu.getFont().deriveFont(fileMenu.getFont().getSize()-1f)); createMenuItem.setFont(createMenuItem.getFont().deriveFont(createMenuItem.getFont().getSize()-1f)); createMenuItem.setText("Create... "); createMenuItem.setToolTipText("Create a surface mesh"); createMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { createMenuItemActionPerformed(evt); } }); fileMenu.add(createMenuItem); latLongMenuItem.setFont(latLongMenuItem.getFont().deriveFont(latLongMenuItem.getFont().getSize()-1f)); latLongMenuItem.setText("Lat/Long..."); latLongMenuItem.setToolTipText("Specify position in Lat/Long format"); latLongMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { latLongMenuItemActionPerformed(evt); } }); fileMenu.add(latLongMenuItem); saveAsMenuItem.setFont(saveAsMenuItem.getFont().deriveFont(saveAsMenuItem.getFont().getSize()-1f)); saveAsMenuItem.setText("Save As ... "); saveAsMenuItem.setToolTipText("Save results as..."); saveAsMenuItem.setEnabled(false); saveAsMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { saveAsMenuItemActionPerformed(evt); } }); fileMenu.add(saveAsMenuItem); exitMenuItem.setFont(exitMenuItem.getFont().deriveFont(exitMenuItem.getFont().getSize()-1f)); exitMenuItem.setText("Exit"); exitMenuItem.setToolTipText("Goodbye!"); exitMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { exitMenuItemActionPerformed(evt); } }); fileMenu.add(exitMenuItem); menuBar.add(fileMenu); optionsMenu.setText("Options"); optionsMenu.setFont(optionsMenu.getFont().deriveFont(optionsMenu.getFont().getSize()-1f)); coordMenuItem.setFont(coordMenuItem.getFont().deriveFont(coordMenuItem.getFont().getSize()-1f)); coordMenuItem.setText("Coordinates..."); coordMenuItem.setToolTipText("Choose the coordinate system"); coordMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { coordMenuItemActionPerformed(evt); } }); optionsMenu.add(coordMenuItem); offsetMenuItem.setText("Offset..."); offsetMenuItem.setToolTipText("Apply an X, Y and Z offset when saving data"); offsetMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { offsetMenuItemActionPerformed(evt); } }); optionsMenu.add(offsetMenuItem); therionMenuItem.setText("Therion..."); therionMenuItem.setToolTipText("Telll Therion the name of the coordinate system"); therionMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { therionMenuItemActionPerformed(evt); } }); optionsMenu.add(therionMenuItem); menuBar.add(optionsMenu); helpMenu.setText("Help"); helpMenu.setFont(helpMenu.getFont().deriveFont(helpMenu.getFont().getSize()-1f)); aboutMenuItem.setFont(aboutMenuItem.getFont().deriveFont(aboutMenuItem.getFont().getSize()-1f)); aboutMenuItem.setText("About "); aboutMenuItem.setToolTipText("Present information about TerrainTool"); aboutMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { aboutMenuItemActionPerformed(evt); } }); helpMenu.add(aboutMenuItem); menuBar.add(helpMenu); setJMenuBar(menuBar); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(statusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 313, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(statusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) ); pack(); }// //GEN-END:initComponents private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitMenuItemActionPerformed doExit(); }//GEN-LAST:event_exitMenuItemActionPerformed private void createMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createMenuItemActionPerformed reader.resetCounts(); dlg.setGridRef(currentGridRef); dlg.setcoordName(currentCoordType); dlg.setExample("e.g. " + toolkit.getExample(currentCoordType)); dlg.setNS(String.format("%13.2f", hgt)); dlg.setEW(String.format("%13.2f", wid)); dlg.setSpacing(String.format("%13.2f", spacing)); dlg.setAlignment(alignment); dlg.setVisible(true); try { if (dlg.getReturnStatus() == LocationDialog.RET_OK) { currentGridRef = dlg.getGridRef(); properties.set(TerrainProperties.GRIDREF, currentGridRef); wid = parseDouble(dlg.getEW()); hgt = parseDouble(dlg.getNS()); northSouthAlignment = dlg.getNSAlignment(); eastWestAlignment = dlg.getEWAlignment(); alignment = dlg.getAlignment(); properties.set(TerrainProperties.ALIGNMENT, String.format(java.util.Locale.UK, "%d", alignment)); spacing = parseDouble(dlg.getSpacing()); properties.set(TerrainProperties.EW, String.format(java.util.Locale.UK, "%10.2f", wid)); properties.set(TerrainProperties.SPACING, String.format(java.util.Locale.UK, "%10.2f", spacing)); properties.set(TerrainProperties.NS, String.format(java.util.Locale.UK, "%10.2f", hgt)); reader = new NASADEMReader(statusPanel); reader.setDownloadSiteName(demURL); CreateResults worker = new CreateResults(); PropertyChangeListener[] listeners = statusPanel.getPropertyChangeListeners(); for (PropertyChangeListener ear : listeners) { worker.addPropertyChangeListener(ear); } worker.execute(); } } catch (java.text.ParseException ex) { String[] text = {ex.getClass().getName(), ex.getMessage()}; InfoMessage msg = new InfoMessage("Format error in numerical value", text, Severity.ERROR); msg.display(this); return; } catch (DataFileException miss) { InfoMessage error = new InfoMessage("Initialisation error", miss.getMessage(), Severity.FATAL); error.display(this); System.exit(0); } catch (MissingDataFileException miss) { InfoMessage error = new InfoMessage("Missing Data File", miss.getMessage(), Severity.FATAL); error.display(this); System.exit(0); } }//GEN-LAST:event_createMenuItemActionPerformed private void saveAsMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsMenuItemActionPerformed int result = chooser.showOpenDialog(mainFrame); if (result == javax.swing.JFileChooser.APPROVE_OPTION) { Saver worker = new Saver(); PropertyChangeListener[] listeners = statusPanel.getPropertyChangeListeners(); for (PropertyChangeListener ear : listeners) { worker.addPropertyChangeListener(ear); } worker.execute(); } }//GEN-LAST:event_saveAsMenuItemActionPerformed private void aboutMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_aboutMenuItemActionPerformed AboutDialog box = new AboutDialog(this, true); box.setVersion(VERSIONID); java.util.Calendar cal = java.util.Calendar.getInstance(TimeZone.getTimeZone("GMT"), java.util.Locale.UK); cal.clear(); cal.set(BUILDYEAR, BUILDMONTH - 1, BUILDDAY); String dateString = String.format("%1$Te-%1$tB-%1$TY", cal); String javaVersion = System.getProperty("java.runtime.version"); String userHome = System.getProperty("user.home"); String runtime = System.getProperty("java.runtime.name"); box.setDate(dateString); box.setJavaVersion(javaVersion); box.setUserHome(userHome); box.setJavaRuntime(runtime); box.setVisible(true); }//GEN-LAST:event_aboutMenuItemActionPerformed private void latLongMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_latLongMenuItemActionPerformed latlondialog.setPosition(currentPosition()); latlondialog.setVisible(true); if (latlondialog.getReturnStatus() == LatLongDialog.RET_CANCEL) { return; } setCurrentPosition(latlondialog.getPosition()); try { currentGridRef = ""; Projection newPoint = (Projection) toolkit.makeCoordinateSystem(currentCoordType, currentPosition(), currentEllipsoid, currentDatum); currentGridRef = newPoint.toString(); } catch (NoSuchMethodException ex) { } catch (GridFormatException ex) { } catch (IllegalAccessException ex) { } catch (IllegalArgumentException ex) { } catch (InvocationTargetException ex) { } catch (InstantiationException ex) { } }//GEN-LAST:event_latLongMenuItemActionPerformed private void therionMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_therionMenuItemActionPerformed TherionCSDialog tdlg = new TherionCSDialog(this, true); coordSystemString = properties.get(TerrainProperties.THERIONCS); tdlg.setCSName(coordSystemString); tdlg.setVisible(true); if (tdlg.getReturnStatus() == TherionCSDialog.RET_OK) { coordSystemString = tdlg.getCSName(); properties.set(TerrainProperties.THERIONCS, coordSystemString); } }//GEN-LAST:event_therionMenuItemActionPerformed private void offsetMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_offsetMenuItemActionPerformed OffsetDialog off = new OffsetDialog(this, true, eastOffset, northOffset, heightOffset); off.setVisible(true); if (off.getReturnStatus() == OffsetDialog.RET_OK) { eastOffset = off.getEastOffset(); northOffset = off.getNorthOffset(); heightOffset = off.getHeightOffset(); properties.set(TerrainProperties.EASTOFFSET, String.format(java.util.Locale.UK, "%11.1f", eastOffset)); properties.set(TerrainProperties.NORTHOFFSET, String.format(java.util.Locale.UK, "%11.1f", northOffset)); properties.set(TerrainProperties.HEIGHTOFFSET, String.format(java.util.Locale.UK, "%11.1f", heightOffset)); } }//GEN-LAST:event_offsetMenuItemActionPerformed private void coordMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_coordMenuItemActionPerformed currentCoordType = properties.get(TerrainProperties.COORD); currentDatum = toolkit.getDatum(properties.get(TerrainProperties.DATUM)); currentEllipsoid = toolkit.getEllipsoid(properties.get(TerrainProperties.ELLIPSOID)); coordset.setSelectedCoordType(currentCoordType); coordset.setEllipsoid(currentEllipsoid); coordset.setDatum(currentDatum); coordset.setVisible(true); if (coordset.getReturnStatus() == CoordinateDialog.RET_OK) { currentDatum = coordset.getDatum(); currentEllipsoid = coordset.getEllipsoid(); currentCoordType = coordset.getProjection(); properties.set(TerrainProperties.ELLIPSOID, currentEllipsoid.toString()); properties.set(TerrainProperties.DATUM, currentDatum.toString()); properties.set(TerrainProperties.COORD, currentCoordType); properties.set(TerrainProperties.EXAMPLE, coordset.getExample()); try { Projection latest = (Projection) toolkit.makeCoordinateSystem(currentCoordType, currentPosition(), currentEllipsoid, currentDatum); String tempGridRef = latest.toString(); currentGridRef = tempGridRef; properties.set(TerrainProperties.GRIDREF, currentGridRef); return; } catch (Exception ex) { String[] text = {ex.getClass().getName(), ex.getMessage()}; InfoMessage msg = new InfoMessage("Failed to set coordinate type", text, Severity.ERROR); msg.display(this); return; } } }//GEN-LAST:event_coordMenuItemActionPerformed private Position currentPosition() { return currentLocation.getPosition(); } /* * Parse a String for a double value using the current locale */ private double parseDouble(String s) throws java.text.ParseException { java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); return nf.parse(s.trim()).doubleValue(); } private void setCurrentPosition(Position pos) { currentLocation = new Spherical(pos, Ellipsoid.GRS80, Datum.WGS_1984); LatLong latlon = currentLocation.toLatLong(); double lat = latlon.lat(); double lon = latlon.lon(); String latstr = String.format(java.util.Locale.UK, "%15.10f", lat); String lonstr = String.format(java.util.Locale.UK, "%15.10f", lon); properties.set(TerrainProperties.LAT, latstr); properties.set(TerrainProperties.LON, lonstr); } private void eraseMessage() { if (!gridrefdisplayed) { statusMessageLabel.setText(""); messageTimer.restart(); } } private class CreateResults extends javax.swing.SwingWorker { public InfoMessage doInBackground() { try { currentEllipsoid = toolkit.getEllipsoid(properties.get(TerrainProperties.ELLIPSOID)); currentDatum = toolkit.getDatum(properties.get(TerrainProperties.DATUM)); Projection startPoint = (Projection) toolkit.makeCoordinateSystem(currentCoordType, currentGridRef, currentEllipsoid, currentDatum); ENPair en1 = startPoint.toEN(); if (eastWestAlignment == SwingConstants.LEFT) { e0 = en1.east(); } else if (eastWestAlignment == SwingConstants.RIGHT) { e0 = en1.east() - wid; } else { e0 = en1.east() - wid / 2.0; } if (northSouthAlignment == SwingConstants.BOTTOM) { n0 = en1.north(); } else if (northSouthAlignment == SwingConstants.TOP) { n0 = en1.north() - hgt; } else { n0 = en1.north() - hgt / 2.0; } ENPair en2 = new ENPair(e0 + wid / 2.0, n0 + hgt / 2.0); int zone = 0; boolean northernHemisphere = true; if (startPoint instanceof UTM) { zone = ((UTM) startPoint).getZone(); northernHemisphere = ((UTM) startPoint).getNorthernHemisphere(); usedCentre = (Projection) toolkit.makeCoordinateSystem(currentCoordType, en2, zone, currentEllipsoid, currentDatum, northernHemisphere); } else { usedCentre = (Projection) toolkit.makeCoordinateSystem(currentCoordType, en2, currentEllipsoid, currentDatum); } setCurrentPosition(startPoint.getPosition()); int xpoints = (int) (wid / spacing); int ypoints = (int) (hgt / spacing); resultsTable = new float[ypoints][xpoints]; long totpoints = xpoints * ypoints; long donepoints = 0; Projection point; for (int j = ypoints - 1; j >= 0; j--) { for (int i = 0; i < xpoints; i++) { double x = e0 + i * spacing; double y = n0 + j * spacing; ENPair en = new ENPair(x, y); String type = startPoint.getName(); if (startPoint instanceof UTM) { point = (Projection) toolkit.makeCoordinateSystem(type, en, zone, currentEllipsoid, currentDatum, northernHemisphere); } else { point = (Projection) toolkit.makeCoordinateSystem(type, en, currentEllipsoid, currentDatum); } Position q = point.getPosition(); Spherical t = new Spherical(q, Ellipsoid.GRS80, Datum.WGS_1984); LatLong latlon = t.toLatLong(); resultsTable[j][i] = (float) reader.getHeight(latlon); donepoints++; int percentdone = (int) ((donepoints * 100) / totpoints); setProgress(percentdone); } } double hitrate = (double) reader.hits() / (double) reader.tries(); String[] message = {String.format("Calculated %d points", reader.resultcount()), String.format("Encountered %d missing data points", reader.missing()), String.format("Cache hit-rate = %6.2f%%", hitrate * 100.0) }; return new InfoMessage("Calculation complete", message, Severity.SUCCESS); } catch (MissingDataFileException ex) { String[] msg = {"Unable to retrieve height data", ex.getMessage(), String.format("Manually download the file from %s", demURL), String.format("...and move it to %s", paths.dataPath())}; return new InfoMessage("Failed", msg, Severity.FATAL); } catch (DataFileException e){ return new InfoMessage("Can't retrieve terrain height", e.getLocalizedMessage(), Severity.FATAL); } catch (Exception ex) { String[] msg = {"Fatal Error"}; return new InfoMessage("Failed", msg, Severity.FATAL); } } @Override protected void done() { try { InfoMessage message = get(); unsaved = message.getSeverity() == Severity.SUCCESS; //Save info about the Coordinates, in case the user changes CoordinateSystem usedEllipsoid = currentEllipsoid; usedDatum = currentDatum; usedType = currentCoordType; saveAsMenuItem.setEnabled(unsaved); if (unsaved) { mosaic.setDataTable(resultsTable, reader); } mosaic.repaint(); message.display(mainFrame); } catch (InterruptedException | ExecutionException ex) { } } private void setMessage(String msg) { firePropertyChange("message", lastMessage, msg); lastMessage = msg; } private String lastMessage = ""; } private class Saver extends javax.swing.SwingWorker { public InfoMessage doInBackground() { try { extDefault = ((javax.swing.filechooser.FileNameExtensionFilter) (chooser.getFileFilter())).getExtensions()[0]; } catch (ClassCastException e) { InfoMessage msg = new InfoMessage("Error", "Unsupported file type ", Severity.ERROR); return msg; } String ext = ""; java.io.File outfile = chooser.getSelectedFile(); try { String name = outfile.getCanonicalPath(); int j = name.lastIndexOf("."); if (j < 0) { name += "." + extDefault; outfile = new java.io.File(name); ext = extDefault; } else { ext = name.substring(j + 1); } if (outfile.isFile()) { String s = String.format("File %s already exists. Do you want to overwrite it?", name); int response = javax.swing.JOptionPane.showConfirmDialog(mainFrame, s, "Confirm Overwrite", javax.swing.JOptionPane.YES_NO_OPTION, javax.swing.JOptionPane.QUESTION_MESSAGE); if (response == javax.swing.JOptionPane.NO_OPTION) { return new InfoMessage("User abort", "Operation cancelled by user", Severity.SUCCESS); } } java.io.PrintWriter out = new java.io.PrintWriter(outfile); int ypoints = resultsTable.length; int xpoints = resultsTable[0].length; String saveMessage = String.format("Saving %s", outfile.getCanonicalPath()); setMessage(saveMessage); java.util.Locale locale = java.util.Locale.UK; if (ext.equalsIgnoreCase("svx")) { //Create a Survex data file //Generate comments containing info about the projection etc. out.printf(locale, "; Survex terrain data created by TerrainTool version %s from %s data%n", VERSIONID, reader.datasetName()); out.println(";"); for (String line : reader.copyright()) { out.printf(locale, "; %s%n", line); } out.println(";"); out.printf(locale, "; Used coordinate system %s with ellipsoid \"%s\"%n; and datum \"%s\"%n", usedType, usedEllipsoid.toString(), usedDatum.toString()); Position pt = usedCentre.getPosition(); Spherical sp = new Spherical(pt, Ellipsoid.GRS80, Datum.WGS_1984); out.printf(locale, "; Grid centred at %s%n", usedCentre); LatLong latlon = sp.toLatLong(); double lat = latlon.lat(); double lon = latlon.lon(); out.printf(locale, "; Lat/Long of centre = %s (%s %s) relative to WGS84/GRS80 datum%n", sp.toLatLongString(), LatLong.toDMS(lon, "EW"), LatLong.toDMS(lat, "NS")); if (usedCentre instanceof TransverseMercator) { double gc = Math.toDegrees(((TransverseMercator) usedCentre).gridConvergence()); out.printf(locale, "; Grid convergence at centre = %7.3f degrees (%s)%n", gc, LatLong.toDMS(gc, "EW")); out.printf(locale, "; Point scale factor at centre = %10.7f%n", ((TransverseMercator) usedCentre).pointScaleFactor()); } else if (usedCentre instanceof Orthomorphic) { double gc = Math.toDegrees(((Orthomorphic) usedCentre).gridConvergence()); out.printf(locale, "; Grid convergence at centre = %7.3f degrees (%s)%n", gc, LatLong.toDMS(gc, "EW")); } if (eastOffset != 0.0 || northOffset != 0 || heightOffset != 0) { out.printf(locale, "; Output offset by adding (%10.2f,%10.2f,%8.2f) to calculated results", eastOffset, northOffset, heightOffset); } out.printf(";%n; TerrainTool (c) 2008 - %4d Mike McCombe %n", BUILDYEAR); out.println(";"); long toBeDone = 2 * ypoints + xpoints; long doneSoFar = 0; int q0 = name.lastIndexOf(java.io.File.separator); String p0 = name.substring(q0 + 1); int q1 = p0.indexOf("."); String p1 = p0.substring(0, q1); out.printf("*BEGIN %s%n", p1); for (int k = 0; k < ypoints; k++) { for (int i = 0; i < xpoints; i++) { out.printf(locale, "*FIX N%dE%d %10.1f %10.1f %8.2f%n", k, i, eastOffset + e0 + i * spacing, northOffset + n0 + k * spacing, heightOffset + resultsTable[k][i]); } doneSoFar++; reportProgress(toBeDone, doneSoFar); } out.printf("*DATA nosurvey station%n"); out.printf("*FLAGS surface%n"); boolean broken = true; for (int k = 0; k < ypoints; k++) { for (int i = 0; i < xpoints; i++) { if (resultsTable[k][i] != SRTM2Reader.MISSING) { out.printf(locale, "N%dE%d %n", k, i); broken = false; } else if (!broken) { out.printf("%n"); broken = true; } } out.printf("%n"); doneSoFar++; reportProgress(toBeDone, doneSoFar); broken = true; } for (int i = 0; i < xpoints; i++) { for (int k = 0; k < ypoints; k++) { if (resultsTable[k][i] != SRTM2Reader.MISSING) { out.printf(locale, "N%dE%d %n", k, i); broken = false; } else if (!broken) { out.printf("%n"); broken = true; } } out.printf("%n"); doneSoFar++; reportProgress(toBeDone, doneSoFar); } out.printf("*END %s%n", p1); out.close(); } else if (ext.equalsIgnoreCase("csv")) { //Create a CSV file out.printf(locale, "%6d,", ypoints); for (int i = 0; i < xpoints - 1; i++) { out.printf(locale, "%10.2f,", e0 + i * spacing); } out.printf(locale, "%10.2f%n", eastOffset + e0 + (xpoints - 1) * spacing); long toBeDone = ypoints; long doneSoFar = 0; for (int k = 0; k < ypoints; k++) { out.printf(locale, "%10.2f,", northOffset + n0 + k * spacing); for (int i = 0; i < xpoints - 1; i++) { out.printf(locale, "%10.2f,", heightOffset + resultsTable[k][i]); } out.printf(locale, "%10.2f%n", resultsTable[k][xpoints - 1]); doneSoFar++; reportProgress(toBeDone, doneSoFar); } out.close(); } else if (ext.equalsIgnoreCase("th")) { //Create a Therion data file //Generate comments containing info about the projection etc. out.printf(locale, "# Therion terrain data created by TerrainTool version %s from %s data%n", VERSIONID, reader.datasetName()); out.println("#"); for (String line : reader.copyright()) { out.printf(locale, "# %s%n", line); } out.println("#"); out.printf(locale, "# Used coordinate system %s with ellipsoid \"%s\"%n# and datum \"%s\"%n", usedType, usedEllipsoid.toString(), usedDatum.toString()); Position pt = usedCentre.getPosition(); Spherical sp = new Spherical(pt, Ellipsoid.GRS80, Datum.WGS_1984); out.printf(locale, "# Grid centred at %s%n", usedCentre); LatLong latlon = sp.toLatLong(); double lat = latlon.lat(); double lon = latlon.lon(); out.printf(locale, "# Lat/Long of centre = %s (%s %s) relative to WGS84/GRS80 datum%n", sp.toLatLongString(), LatLong.toDMS(lon, "EW"), LatLong.toDMS(lat, "NS")); if (usedCentre instanceof TransverseMercator) { double gc = Math.toDegrees(((TransverseMercator) usedCentre).gridConvergence()); out.printf(locale, "# Grid convergence at centre = %7.3f degrees (%s)%n", gc, LatLong.toDMS(gc, "EW")); out.printf(locale, "# Point scale factor at centre = %10.7f%n", ((TransverseMercator) usedCentre).pointScaleFactor()); } else if (usedCentre instanceof Orthomorphic) { double gc = Math.toDegrees(((Orthomorphic) usedCentre).gridConvergence()); out.printf(locale, "# Grid convergence at centre = %7.3f degrees (%s)%n", gc, LatLong.toDMS(gc, "EW")); } if (eastOffset != 0.0 || northOffset != 0 || heightOffset != 0) { out.printf(locale, "# Output offset by adding (%10.2f,%10.2f,%8.2f) to calculated results", eastOffset, northOffset, heightOffset); } out.printf(";%n; TerrainTool (c) 2008 - %4d Mike McCombe %n", BUILDYEAR); out.printf("#%n"); out.println("surface"); out.printf(locale, "cs %s%n", coordSystemString); out.println("grid-units meter"); out.printf(locale, "grid %10.2f %10.2f %.2f %.2f %d %d%n", e0 + eastOffset, n0 + northOffset, spacing, spacing, xpoints, ypoints); long toBeDone = ypoints; long doneSoFar = 0; for (int k = ypoints - 1; k >= 0; k--) { for (int i = 0; i < xpoints - 1; i++) { out.printf(locale, "%10.2f ", heightOffset + resultsTable[k][i]); } out.printf(locale, "%10.2f%n", resultsTable[k][xpoints - 1]); doneSoFar++; reportProgress(toBeDone, doneSoFar); } out.println("endsurface"); out.close(); } else { InfoMessage msg = new InfoMessage("Error", "Unsupported file type \"" + ext + "\"", Severity.ERROR); return msg; } setMessage("Done"); return new InfoMessage("Operation complete", "File saved", Severity.SUCCESS); } catch (IOException e) { InfoMessage msg = new InfoMessage("I/O Exception", e.toString(), Severity.ERROR); return msg; } } private void setMessage(String msg) { firePropertyChange("message", lastMessage, msg); lastMessage = msg; } private void reportProgress(long toDo, long doneSoFar) { int percent = (int) (doneSoFar * 100 / toDo); setProgress(percent); } @Override protected void done() { progressBar.setVisible(false); progressBar.setValue(0); try { InfoMessage message = get(); if (message.getSeverity() != Severity.SUCCESS) { message.display(mainFrame); } unsaved = false; } catch (InterruptedException ex) { /// Logger.getLogger(SRTMView.class.getName()).log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { /// Logger.getLogger(SRTMView.class.getName()).log(Level.SEVERE, null, ex); } } private String extDefault; } private class MainFrameListener extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { doExit(); } } /** * @param args the command line arguments */ public static void main(String args[]) { System.setProperty("https.protocols", "SSLv3,TLSv1,TLSv1.1,TLSv1.2"); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { try { TerrainFrame frame = new TerrainFrame(); frame.setVisible(true); } catch (Exception e) { if (!TerrainFrame.properties.isValid()) { System.out.println("Fatal - failed to create properties file"); } else { System.out.println("Unexpected fatal exception during initialisation"); System.out.println(e); System.exit(0); } } } }); } public void doExit() { try { if (unsaved) { String[] message = {"The terrain data has not been saved", "Do you still wish to exit?"}; int retValue = javax.swing.JOptionPane.showConfirmDialog(mainFrame, message, "Warning", javax.swing.JOptionPane.OK_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE); okToExit = (retValue == javax.swing.JOptionPane.OK_OPTION); if (!okToExit) { return; } } properties.save(); } catch (IOException ex) { //if the properties file can't be updated, too bad! } System.exit(0); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem aboutMenuItem; private javax.swing.JMenuItem coordMenuItem; private javax.swing.JMenuItem createMenuItem; private javax.swing.ButtonGroup datasourceGroup; private javax.swing.JMenuItem exitMenuItem; private javax.swing.JMenu fileMenu; private javax.swing.JMenu helpMenu; private javax.swing.JPanel jPanel1; private javax.swing.JSeparator jSeparator1; private javax.swing.JSeparator jSeparator2; private javax.swing.JMenuItem latLongMenuItem; private javax.swing.JMenuBar menuBar; private javax.swing.JMenuItem offsetMenuItem; private javax.swing.JMenu optionsMenu; private javax.swing.JProgressBar progressBar; private javax.swing.JMenuItem saveAsMenuItem; private javax.swing.JLabel statusAnimationLabel; private javax.swing.JLabel statusMessageLabel; private javax.swing.JPanel statusPanel; private javax.swing.JMenuItem therionMenuItem; // End of variables declaration//GEN-END:variables /* * Pathnames object to point to properties file and data cache * */ protected static Pathnames paths = new DefaultPathnames(); //Change this class to achieve os-specific behaviour private final Timer messageTimer; private final Timer busyIconTimer; private Icon idleIcon = null; private final Icon[] busyIcons = new Icon[15]; private int busyIconIndex = 0; private DEMReader reader = null; ///// private float[][] resultsTable = null; private double e0, n0; private double wid = Double.parseDouble(properties.get(TerrainProperties.EW)); private double hgt = Double.parseDouble(properties.get(TerrainProperties.NS)); private double spacing = Double.parseDouble(properties.get(TerrainProperties.SPACING)); private Projection usedCentre; private String usedType = ""; private Ellipsoid usedEllipsoid; private Datum usedDatum; private javax.swing.JFileChooser chooser = new javax.swing.JFileChooser(); private MappingToolkit toolkit = new MappingToolkit(); private CoordinateDialog coordset = new CoordinateDialog(this, true, toolkit); private String lastMessage = ""; private boolean unsaved = false; private boolean okToExit = true; private JFrame mainFrame = this; private MosaicPanel mosaic = new MosaicPanel(); /** * Initialise variables from the properties file using defaults as needed */ private static final DefaultProperties defaults = new DefaultProperties(); public static PropertySet properties = new PropertySet(paths.propertiesPath(), defaults); private String currentGridRef = properties.get(TerrainProperties.GRIDREF); private double currentLatitude = Double.parseDouble(properties.get(TerrainProperties.LAT)); private double currentLongitude = Double.parseDouble(properties.get(TerrainProperties.LON)); private Spherical currentLocation = new Spherical(new LatLong(currentLatitude, currentLongitude), Ellipsoid.GRS80, Datum.WGS_1984); private Ellipsoid currentEllipsoid = toolkit.getEllipsoid(properties.get(TerrainProperties.ELLIPSOID)); private Datum currentDatum = toolkit.getDatum(properties.get(TerrainProperties.DATUM)); private String currentCoordType = properties.get(TerrainProperties.COORD); private double eastOffset = Double.parseDouble(properties.get(TerrainProperties.EASTOFFSET)); private double northOffset = Double.parseDouble(properties.get(TerrainProperties.NORTHOFFSET)); private double heightOffset = Double.parseDouble(properties.get(TerrainProperties.HEIGHTOFFSET)); private LocationDialog dlg = new LocationDialog(this, true, toolkit, properties); private int alignment = Integer.parseInt(properties.get(TerrainProperties.ALIGNMENT)); PropertyChangeSupport pcs = new PropertyChangeSupport(this); private boolean gridrefdisplayed = false; private boolean processing = false; private int northSouthAlignment = SwingConstants.BOTTOM; private int eastWestAlignment = SwingConstants.LEFT; private LatLongDialog latlondialog = new LatLongDialog(this, true, toolkit, currentLocation); private String coordSystemString = properties.get(TerrainProperties.THERIONCS); protected String demURL = properties.get(TerrainProperties.DEMURL); /** * Version information */ private static final String VERSIONID = "1.19"; // Version 1.19 9th Mar 2024 private static final int BUILDYEAR = 2024; private static final int BUILDMONTH = 3; private static final int BUILDDAY = 9; /** * Adds a PropertyChangeListener to the listener list. * * @param l The listener to add. */ @Override public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } /** * Removes a PropertyChangeListener from the listener list. * * @param l The listener to remove. */ @Override public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.removePropertyChangeListener(l); } } src/uk/co/mccombe/terrain/TerrainProperties.java0000777000000000000000000000531314573074222017167 0ustar package uk.co.mccombe.terrain; /** * * @author Mike McCombe */ public class TerrainProperties { private TerrainProperties(String value) { stringvalue = value; } @Override public String toString() { return stringvalue; } public static final TerrainProperties COORD = new TerrainProperties("coordinatesystem"); //Current CoordinateSystem public static final TerrainProperties REGION = new TerrainProperties("region"); //Region -- needed for obsolete SRTM2 dataset public static final TerrainProperties FTP = new TerrainProperties("ftpsite"); //URL for data download public static final TerrainProperties AUTO = new TerrainProperties("autodownload"); //Flag to say whether to download missing tiles public static final TerrainProperties GRIDREF = new TerrainProperties("currentgridref"); //Current gridref in current Projection public static final TerrainProperties EXAMPLE = new TerrainProperties("example"); //Example to show gridref format public static final TerrainProperties ELLIPSOID = new TerrainProperties("ellipsoid"); //Current Ellipsoid public static final TerrainProperties DATUM = new TerrainProperties("datum"); //Current Datum public static final TerrainProperties EW = new TerrainProperties("e-w_range"); //E-W range of mesh to generate public static final TerrainProperties NS = new TerrainProperties("n-s_range"); //N-S range of mesh to generate public static final TerrainProperties SPACING = new TerrainProperties("spacing"); //Mesh spacing public static final TerrainProperties EASTOFFSET = new TerrainProperties("eastoffset"); //Offset to apply to Easting values public static final TerrainProperties NORTHOFFSET = new TerrainProperties("northoffset"); //Offset to apply to Northing values public static final TerrainProperties HEIGHTOFFSET = new TerrainProperties("heightoffset"); //Offset to apply to height values public static final TerrainProperties ALIGNMENT = new TerrainProperties("alignment"); //Integer value to signify where gridref lies in the mesh public static final TerrainProperties LAT = new TerrainProperties("latitude"); //Current latitude value public static final TerrainProperties LON = new TerrainProperties("longitude"); //Current longitude value public static final TerrainProperties LOCALE = new TerrainProperties("locale"); //Current locale public static final TerrainProperties ASTER = new TerrainProperties("useASTER"); //Obsolete public static final TerrainProperties THERIONCS = new TerrainProperties("therionCoordinateSet"); //EPSG String for Therion public static final TerrainProperties DEMURL = new TerrainProperties("demURL"); //Same as FTP private final String stringvalue; } src/uk/co/mccombe/terrain/TherionCSDialog.form0000777000000000000000000001271214163163576016515 0ustar
src/uk/co/mccombe/terrain/TherionCSDialog.java0000777000000000000000000001712014573074222016463 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ /* * TherionCSDialog.java * * Created on 12-Jun-2010, 16:37:01 */ package uk.co.mccombe.terrain; /** * * @author Mike */ public class TherionCSDialog extends javax.swing.JDialog { /** A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; /** Creates new form TherionCSDialog */ public TherionCSDialog(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); } /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */ public int getReturnStatus() { return returnStatus; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); jPanel1 = new javax.swing.JPanel(); jLabel1 = new javax.swing.JLabel(); csName = new javax.swing.JTextField(); setTitle("Set Therion Coordinate Set Name"); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); okButton.setText("OK"); okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); jLabel1.setText("Therion cs name"); csName.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { csNameActionPerformed(evt); } }); javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 27, Short.MAX_VALUE) .addComponent(csName, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) .addComponent(csName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton))) .addContainerGap()) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 14, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)) .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed doClose(RET_OK); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed doClose(RET_CANCEL); }//GEN-LAST:event_cancelButtonActionPerformed /** Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog doClose(RET_CANCEL); }//GEN-LAST:event_closeDialog private void csNameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_csNameActionPerformed // TODO add your handling code here: }//GEN-LAST:event_csNameActionPerformed private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } public void setCSName(String s){ csName.setText(s); } public String getCSName() { return csName.getText(); } /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { TherionCSDialog dialog = new TherionCSDialog(new javax.swing.JFrame(), true); dialog.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JTextField csName; private javax.swing.JLabel jLabel1; private javax.swing.JPanel jPanel1; private javax.swing.JButton okButton; // End of variables declaration//GEN-END:variables private int returnStatus = RET_CANCEL; } src/uk/co/mccombe/terrain/UnixPathnames.java0000777000000000000000000000201714573074222016270 0ustar /* * Provide sensible pathnames for data files and properties for use with Unix platforms */ package uk.co.mccombe.terrain; /** * * @author Mike McCombe */ public class UnixPathnames implements Pathnames{ @Override public String propertiesPath() { if(confighome!=null){ return confighome + sep + "terraintool" + sep; } else { return home + sep + ".config" + sep + "terraintool" + sep; } } @Override public String dataPath() { if(datahome!=null){ return datahome + sep + "terraintool" + sep; } else { return home + sep + ".local" + sep + "share" + sep + "terraintool" + sep; } } private static String confighome = System.getenv("$XDG_CONFIG_HOME"); private static String datahome = System.getenv("$XDG_DATA_HOME"); private static String sep = System.getProperty("file.separator"); private static String home = System.getProperty("user.home"); } src/uk/co/mccombe/test/0000777000000000000000000000000014573074317012156 5ustar src/uk/co/mccombe/test/Tester.java0000777000000000000000000000367414573075134014302 0ustar /** * * @author Mike * * Skeleton code for testing coordinate conversions. * NOT part of TerrainTool!! Could, for example, be adapted to check conversions * against test data provided by mapping agencies. */ package uk.co.mccombe.test; import uk.co.mccombe.mapping.*; public class Tester { /** * @param args the command line arguments */ public static void main(String[] args) { double lat = 58.0 ; double lon = -3.0 ; // Convert Lat/Lon to OSGB Grid Ref and grid convergence. LatLong ll = new LatLong(lat, lon); System.out.printf("Starting at = %s%n", ll.toString()); Position pos = new Position(ll, 0.0, Ellipsoid.GRS80, Datum.WGS_1984); OSGB zz = new OSGB(pos,Ellipsoid.AIRY, Datum.OSGB_1936); double c1 = zz.gridConvergence(); System.out.printf("Calc Grid Convergence = %9.6f%n", c1); ENPair pr = zz.toEN(); System.out.printf("Example 1 E %9.1f N %9.1f%n",pr.east(), pr.north()); System.out.printf("OSGB Grid Ref = %s%n", zz.toString()); // Convert a Lat/Lon position to NZMG and back again to show that it doesn't change (much) lat = -34.444066 ; lon = 172.739194 ; ll = new LatLong(lat, lon); System.out.printf("Starting at = %s%n", ll.toString()); pos = new Position(ll, 0.0, Ellipsoid.INTERNATIONAL, Datum.NZGD_1949); NZMG z = new NZMG(pos,Ellipsoid.INTERNATIONAL, Datum.NZGD_1949); System.out.printf("Grid convergence = %9.6f%n", Math.toDegrees(z.gridConvergence())); pr = z.toEN(); System.out.printf("Example 2 E %9.1f N %9.1f%n",pr.east(), pr.north()); NZMG z2 = new NZMG(pr, Ellipsoid.INTERNATIONAL, Datum.NZGD_1949); LatLong latlon = z2.getPosition().toLatLong(Ellipsoid.INTERNATIONAL, Datum.NZGD_1949); System.out.printf("Lat Long = %s%n", latlon.toString()); System.out.printf("NZMG Grid Ref = %s%n", z2.toString()); } } src/uk/co/mccombe/util/0000777000000000000000000000000014573074320012146 5ustar src/uk/co/mccombe/util/ErrorMessage.java0000777000000000000000000000145314573074222015416 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.mccombe.util; /** * * @author Mike */ /** * * @author Mike */ public class ErrorMessage { /** Creates a new instance of ErrorMessage */ public ErrorMessage() { this("",Severity.ERROR); } public ErrorMessage(String error){ this(error,Severity.ERROR); } public ErrorMessage(String errorText, Severity level){ seriousness = level ; text = errorText; } public String errorText() { return text ; }; public Severity severityLevel() { return seriousness ; }; public String toString() { return severityLevel() + " - " + errorText();}; private String text = "" ; private Severity seriousness = Severity.ERROR ; } src/uk/co/mccombe/util/Severity.java0000777000000000000000000000057014573074222014631 0ustar /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.mccombe.util; /** * * @author Mike */ public enum Severity { SUCCESS(0), WARNING(1), ERROR(2), SERIOUS(3), FATAL(4) ; Severity(int value){ val = value ; } public int value() { return val ; }; private int val ; }