./0000775000175000017500000000000013206563236010637 5ustar wookeywookey./doc/0000775000175000017500000000000013206563171011402 5ustar wookeywookey./doc/README.html0000775000175000017500000005543613206556557013257 0ustar wookeywookey TerrainTool - create surface topographic data for Survex and Therion

TerrainTool

TerrainTool is used to create surface topographic data for the cave survey packages Survex and Therion. This uses the results of the Shuttle Radar Topography Mission (SRTM) in which the shuttle Endeavour mapped the height of the Earth's surface between the latitudes 60 degrees North and 56 degrees South - about 80% of the Earth's land mass. Resolution was 1 arc-second for the US and its territories and 3 arc-seconds elsewhere. The latter corresponds to about 90m at the equator. The resulting data is royalty-free and, for many countries, may be the only data publicly available.

More recently, the Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) project has published data at 1 arc-second resolution for the land masses between 83 degrees North and 83 degrees South and is also royalty-free. Whilst the SRTM data contains numerous "voids" caused by shadowing in steep or mountainous areas, the ASTER data was built from stereo images taken over a much longer period of time and as a result is much more complete. It does, however, suffer from "artefacts" - spurious features which are by-products of the imaging process.

SRTM data is available to all on the Internet from a NASA server and TerrainTool automatically fetches anything it needs. The mechanism for accessing ASTER data is slightly more complicated in that users need to register first on the US or Japanese website and then "order" (at no cost) the files that they need. A few minutes later, the system sends the user an e-mail containing a link to a zip file containing the relevant files. This can be downloaded via FTP or using a standard web browser. A zip file containing the tiles for the UK and Ireland, for example, was a little over 500MB. Unfortunately, it's not possible for a tool like TerrainTool to take care of the downloading of ASTER data automatically. More instructions on how to do this manually can be found below.

TerrainTool does the following:-

The programme, written in Java, provides a conventional GUI-style interface and will run under Windows, Solaris and Linux operating systems. The latest Java Runtime (JRE 6, JDK 6 or later) is required and can be downloaded free of charge from Oracle, for example.

"TerrainTool" was written by Mike McCombe who is very grateful to UBSS for giving it a home. Please feel free to contact Mike with feedback or requests for help at mikemccombe <at> btinternet.com or via the Survex list.

Installation

Sorry, there's no fancy package installer but as there's only one file it should be pretty straightforward.

  1. If you don't already have the Java Standard Edition (SE) version 6 installed, download it from the Oracle website now. Unless you plan to develop your own Java software, the Java Runtime Environment (JRE) will suffice.
  2. Download the TerrainTool "jar". Internet Explorer users may find that the file that arrives is called TerrainTool.zip. If this happens, just rename it to be TerrainTool.jar. Do not try unpacking the ZIP file! I suggest you save it in its own directory. When you first run the programme, it will create "terraintool.properties" (in the same directory) and a subdirectory called "data".
  3. Double-clicking the jar file should start the programme. If you need to start it from a command line interface, try "java -jar TerrainTool.jar".

Thanks to Wookey, Terraintool is available in Debian 7 ('Wheezy') or Ubuntu 12.04 ('Precise Pangolin') onwards. To install it select 'terraintool' in your favourite package manager (Upgrade Center, Synaptic, Aptitude) and install it. On the command line do

sudo apt-get install terraintool

If your system has a firewall, you may need to tell it to allow java to have outgoing ftp access. Otherwise, it won't be able to download the data files from the NASA site. This version of TerrainTool doesn't support Internet access via a proxy. I can easily add this if anyone wants it - but I have no means of testing it.

Driving Instructions

TerrainTool is a conventional GUI-based application with a menu bar and dialog boxes to gather user-information. To get started, do the following:

  1. Select the required region and coordinate system using the Options menu
  2. Go to Create on the File menu to specify the size, location and resolution of the mesh.
  3. Save the results as in Survex (.svx) or Therion (.th) format
  4. Use Survex to process the file and Aven to view the results in 3D. Remember to enable viewing of surface legs in Aven!
  5. Use the Offset command in the Options menu to fully align the terrain data with the coordinates used in your survey.
  6. When generating surface data in Therion format, Therion needs to be told the coordinate system used for the surface data in a form that it recognises (e.g. EPSG:27700). See the description of the surface command in the Therion Book for more details.
  7. Incorporate the terrain data into your survey project.

File Menu

Create... is used to calculate the terrain mesh. A dialog box is used to gather the following:-

Item Meaning
Grid Reference The grid reference for the mesh, expressed in the current coordinate type. This point can be at the centre of the mesh, any of the corners or the mid-point of ant of the sides (see below). An example in the correct format is shown below the text field.
E-W Range The distance (in metres) between the East and West edges of the mesh.
N-S Range The distance (in metres) between the North and South edges of the mesh.
Spacing The distance (in metres) between adjacent cells in the mesh.
Grid ref is at Specifies where current point (see "Grid Reference" above) lies in relation to the boundaries of the mesh.

Pressing OK starts the calculation of the mesh points. If "Auto-download" is enabled, data files will be downloaded as needed from the Nasa ftp site. These are stored in the "data" subdirectory for later re-use if necessary, avoiding the need to download the same file again.

At the end of the calculation, results are displayed as a simple coloured relief map. The mouse position is displayed (in current coordinates) in the message bar on the bottom edge of the frame.

Lat/Long... provides a means of defining the current point in terms of latitude and longitude, rather than as a grid reference. If the point can be represented as a valid grid reference in the current coordinate system, it is used to initialise the "Grid Reference" field of the "Create..." dialog box. Likewise, the current grid reference is used to initialise the Lat and Long fields with the latitude and longitude of the current point.

Latitude and longitude values can be expressed as either

Latitude and Longitude are usually based on the WGS-84/GRS-80 datum and ellipsoid. The user may select alternatives, which will cause the lat and long values to be re-calculated.

Save as...

Once a mesh has been calculated, the "Save as..." command can be used to save the terrain data. Normally, this will be in Survex (.svx) or Therion (.th) format. Occasionally, there may be missing values (known as "voids") in the SRTM data - particularly in mountainous areas where steep faces may have been hidden from the Shuttle's line of sight. Generally, TerrainTool will "repair" individual voids by interpolating from the surrounding cells. However, if this isn't possible, gaps are left in the mesh where no data is available.

Otherwise, height values are defined for each point in the mesh. Easting and Northing values are those of the current coordinate system.

Options Menu

Coordinates...

This is used to select the type of coordinates to use. The following are currently supported:

Coordinate System
Austrian The Austrian (BMN) coordinate system, in three zones
Irish Grid The Irish grid system, used in both Northern Ireland and the Republic of Ireland.
Lambert 93 The Lambert 93 coordinate system. A conformal conical projection occasionally used in France.
Lambert (5 zones) The coordinate system most commonly used in France. Three zones (I, II and III) cover North, Central and Southern France. Zone IV is used in Corsica. A fifth zone (II-extended) covers the whole of France, at the expense of greater distortion.
NZMG New Zealand Map Grid - New Zealand’s coordinate system superseded in 2010. Maps based on this are no longer available, but still widely used. Based on a conformal orthomorphic projection.
NZTM2000 New Zealand Transverse Mercator, successor to NZMG.
OSGB Ordnance Survey of Great Britain - the normal British grid system.
UTM Universal Transverse Mercator, devised by the US Department of Defense to cover the globe (except polar regions) in 60 zones. Also used by many national mapping agencies, often with a national or regional datum instead of WGS84.

One of the design objectives of this software is to be able to add further coordinate systems with minimal difficulty.

Selecting a coordinate system from the drop-down list results in automatic selection of sensible defaults for the datum and ellipsoid. The user is free to override this selection using the other two drop-down lists. Whilst, for example, OSGB invariably uses its own datum and the "Airy Sphere", other systems are frequently used with a variety of datums. UTM, for example, is used in Spain with the European (1950) Datum and Australia with their own (MGI) datum.

Auto download enables/disables the automatic downloading of data from the NASA SRTM site.

Region

The SRTM data site is organised into six regions - Africa, Australia, Eurasia, North_America, South_America and Islands (New Zealand and islands of the Pacific). As I don't have a simple method of determining the region automatically from lat/lon, you will need to manually select the right region.

Offset

This provides a simple way of adding a fixed 3-D offset to mesh point in the mesh as it is saved. You might want to do this because

SRTM Only

Creates terrain using only SRTM data. Any "voids" which cannot be filled by interpolation will result in gaps in the output data. If auto-download is selected and there is an Internet connction, missing tiles will be automatically downloaded from the NASA server.

ASTER Only

Creates terrain using only ASTER data. This has higher resolution and greater coverage than SRTM data but must be manually downloaded and installed (see below). Its greater resolution causes TerrainTool to run more slowly than with SRTM data. The end-result may show signs of "artefacts" - spurious features produced by the imaging process.

SRTM plus ASTER

Uses ASTER to fill any "voids" in the SRTM data. This option minimises the processing time and "artefact" penalties of using ASTER data whilst leaving the least number of voids in the finished product.

Legacy ASTER Data

By default, version 1.11 onwards of TerrainTool assumes ASTER data to be from the ASTER 2 dataset. This contains fewer artefacts than the original ASTER dataset. However, if you need to use the original data files, select this option.

Installing ASTER data files

Obtaining ASTER data is free and quite straightforward. The first step is that you will need to register and, when you've logged in you can use the tool on the WIST web site to select the "granules" you need - most easily by just dragging an area on a map of the world. Having ticked various boxes to confirm agreement to their terms and conditions, the selection is bundled up into an "order". A few minutes later, the system sends an e-mail to the address you gave at registration and this contains a link to a zip file containing the tiles you selected and instructions on how to download it using either a browser or a command-line ftp client.

Each "granule" contains the data for a 1-degree by 1-degree tile of the earth's surface and is in two files - an xml descriptor (e.g. ASTGTM_N50E002.zip.xml) and a zip file (e.g. ASTGTM_N50E002.zip) containing the data itself. The first time you run TerrainTool it will create a sub-directory called "data" (in the directory containing TerrainTool.jar). This is used to store both SRTM tiles and ASTER granules. Use a standard "zip" utility (e.g. WinZip) to extract the data files (e.g. ASTGTM_N50E002.zip) and place them in this directory. Do NOT unpack the zip files themselves - TerrainTool decompresses the contents as it reads them.

A similar procedure can be used to install SRTM data files manually. Simply copy data tiles (e.g. N51W003.hgt.zip) into the "data" sub-directory.

./doc/ReleaseNotes.html0000775000175000017500000001167513206556072014700 0ustar wookeywookey TerrainTool Release Notes

Version 1.13

HTTPS Support

Sometime prior to July 2017, changes were made to the USGS webserver that force the use of an encrypted HTTPS connection in place of HTTP to download the SRTM tiles. Version 1.13 adds support for HTTPS using Transport Layer Security (TLS) version 1.2. The USGS server does not seem to accept earlier versions of TLS.

Java Version

Previous versions of TerrainTool were built using Java 6 because this was the highest version supported on Mac OS X at the time (2010). However, Java 6 does not support TLS1.2, forcing TerrainTool 1.13 to be built on Java 7. Oracle provide downloads for both Java 7 and Java 8 for OS X but be aware that there may be compatibility issues with legacy Mac systems.I'm keen to hear from Mac users about whether their systems support Java 7 or Java 8 because it would clearly be better for everyone if I could upgrade TerrainTool to be build on the latest and most secure version of Java.

Version 1.12a

ASTER 2

This version bring support for the ASTER-2 dataset that has recently become available. This has the same resolution as the original ASTER data but the processing has resulted in fewer artefacts. Internally, the data format is the same as before but the filename e.g. ASTGTM2_N51W003.zip now includes a "2" to indicate that it is from the ASTER-2 dataset. Note: you can't just read the file and read it as though it came from the original ASTER dataset because the files inside the outer zip "wrapper" also have a "2" added to their names.

File Locations

Previous versions of TerrainTool running under Windows placed the terraintool.properties and data directory in the same directory as terraintool.jar. Under unix-family operating systems, (e.g. Ubuntu, Debian) these files were created in the current working directory, wherever that happened to be. If the current directory already contained a sub-directory called "data", terrain data files were just added to it. This sometimes resulted in terrain data being added to unrelated data directories and, potentially, duplication of data.

The sections below explain where the data files and properties file will be located, according to whether you have the cross-platform version (default) or used an application installer under Ubuntu or Debian. If you already have data that has been downloaded from the SRTM or ASTER sites, you may want to copy these into the new data directory to avoid having to download them again.

Cross-platform version

If you downloaded TerrainTool as a "jar" file from the UBSS web site, you have the cross-platform version which is built to run under Windows, Unix family (Ubuntu, Debian, Solaris etc.) and OS X. By default, this version of TerrainTool creates the terraintool.properties and the data store in the .terraintool sub-directory of the user's home directory. For recent Windows versions, this would be something like c:\users\Mike\.terraintool or /home/mike/.terraintool on Ubuntu. Note that normally, Unix-family operating systems treat directories starting with "." as hidden - hence the sub-directory may not be visible.

Unix-specific Version

Apart from downloading terraintool.jar from the UBSS web site, TerrainTool can also be installed as a pre-packaged application for Ubuntu and Debian (thanks to Wookey!). This is identical to the cross-platform version, except that the properties file and data directory are placed in a more unix-friendly location, as follows:-

Customisation

If building TerrainTool from source-code, the file locations are easily customised by changing a single line of TerrainFrame.java and writing a class to implement the Pathnames interface. See Pathnames.java.

./src/0000775000175000017500000000000013206557751011433 5ustar wookeywookey./src/mccombe/0000775000175000017500000000000013206557731013036 5ustar wookeywookey./src/mccombe/terrain/0000775000175000017500000000000013206563171014475 5ustar wookeywookey./src/mccombe/terrain/LocationDialog.form0000775000175000017500000002540011731674166020266 0ustar wookeywookey
./src/mccombe/terrain/TerrainProperties.java0000775000175000017500000000413711731674171021035 0ustar wookeywookeypackage 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"); public static final TerrainProperties REGION = new TerrainProperties("region"); public static final TerrainProperties FTP = new TerrainProperties("ftpsite"); public static final TerrainProperties AUTO = new TerrainProperties("autodownload"); public static final TerrainProperties GRIDREF = new TerrainProperties("currentgridref"); public static final TerrainProperties EXAMPLE = new TerrainProperties("example"); public static final TerrainProperties ELLIPSOID = new TerrainProperties("ellipsoid"); public static final TerrainProperties DATUM = new TerrainProperties("datum"); public static final TerrainProperties EW = new TerrainProperties("e-w_range"); public static final TerrainProperties NS = new TerrainProperties("n-s_range"); public static final TerrainProperties SPACING = new TerrainProperties("spacing"); public static final TerrainProperties EASTOFFSET = new TerrainProperties("eastoffset"); public static final TerrainProperties NORTHOFFSET = new TerrainProperties("northoffset"); public static final TerrainProperties HEIGHTOFFSET = new TerrainProperties("heightoffset"); public static final TerrainProperties ALIGNMENT = new TerrainProperties("alignment"); public static final TerrainProperties LAT = new TerrainProperties("latitude"); public static final TerrainProperties LON = new TerrainProperties("longitude"); public static final TerrainProperties LOCALE = new TerrainProperties("locale"); public static final TerrainProperties ASTER = new TerrainProperties("useASTER"); public static final TerrainProperties THERIONCS = new TerrainProperties("therionCoordinateSet"); public static final TerrainProperties LEGACYASTER = new TerrainProperties("legacyASTER"); private String stringvalue; } ./src/mccombe/terrain/TerrainFrame.java0000775000175000017500000026355413206556557017753 0ustar wookeywookey/* * 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 mccombe.terrain; 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.JDialog; import javax.swing.JFrame; import javax.swing.SwingConstants; import javax.swing.Timer; import javax.swing.UnsupportedLookAndFeelException; import mccombe.mapping.*; import mccombe.util.*; /** * * @author Mike */ public class TerrainFrame extends javax.swing.JFrame { /** * Creates new form TerrainFrame */ public TerrainFrame() { initComponents(); try { javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } catch (IllegalAccessException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (InstantiationException ex) { ex.printStackTrace(); } // 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 { if (useASTER.equalsIgnoreCase("aster")) { reader = new ASTERReader(statusPanel); asterMenuItem.getModel().setSelected(true); legacyMenuItem.setEnabled(true); } else if (useASTER.equalsIgnoreCase("both")) { reader = new CompositeReader(statusPanel); bothMenuItem.getModel().setSelected(true); legacyMenuItem.setEnabled(true); } else if (useASTER.equalsIgnoreCase("srtm")) { reader = new SRTM2Reader(statusPanel); ///// srtmMenuItem.getModel().setSelected(true); legacyMenuItem.setEnabled(false); } properties.set(TerrainProperties.ASTER, useASTER); legacyMenuItem.getModel().setSelected(useLegacyASTER.equalsIgnoreCase("true")); } catch (MissingDataFileException miss) { InfoMessage error = new InfoMessage("Initialisation error", 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"); autoDownloadMenuItem.setState(auto); 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(); 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(); jPanel1 = new javax.swing.JPanel(); 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(); autoDownloadMenuItem = new javax.swing.JCheckBoxMenuItem(); regionMenuItem = new javax.swing.JMenuItem(); offsetMenuItem = new javax.swing.JMenuItem(); therionMenuItem = new javax.swing.JMenuItem(); jSeparator3 = new javax.swing.JSeparator(); srtmMenuItem = new javax.swing.JRadioButtonMenuItem(); asterMenuItem = new javax.swing.JRadioButtonMenuItem(); bothMenuItem = new javax.swing.JRadioButtonMenuItem(); jSeparator2 = new javax.swing.JPopupMenu.Separator(); legacyMenuItem = new javax.swing.JRadioButtonMenuItem(); helpMenu = new javax.swing.JMenu(); aboutMenuItem = new javax.swing.JMenuItem(); setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel); statusPanel.setLayout(statusPanelLayout); statusPanelLayout.setHorizontalGroup( statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup() .addContainerGap(220, 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) .addContainerGap()) .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) .addGroup(statusPanelLayout.createSequentialGroup() .addContainerGap() .addComponent(statusMessageLabel) .addContainerGap(390, Short.MAX_VALUE)) ); 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, 14, Short.MAX_VALUE) .addComponent(statusMessageLabel)) .addContainerGap()) ); jPanel1.setLayout(new java.awt.BorderLayout()); 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.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.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.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.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.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { coordMenuItemActionPerformed(evt); } }); optionsMenu.add(coordMenuItem); autoDownloadMenuItem.setFont(autoDownloadMenuItem.getFont().deriveFont(autoDownloadMenuItem.getFont().getSize()-1f)); autoDownloadMenuItem.setSelected(true); autoDownloadMenuItem.setText("Auto Download "); autoDownloadMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { autoDownloadMenuItemActionPerformed(evt); } }); optionsMenu.add(autoDownloadMenuItem); regionMenuItem.setText("Region..."); regionMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { regionMenuItemActionPerformed(evt); } }); optionsMenu.add(regionMenuItem); offsetMenuItem.setText("Offset..."); offsetMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { offsetMenuItemActionPerformed(evt); } }); optionsMenu.add(offsetMenuItem); therionMenuItem.setText("Therion..."); therionMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { therionMenuItemActionPerformed(evt); } }); optionsMenu.add(therionMenuItem); optionsMenu.add(jSeparator3); datasourceGroup.add(srtmMenuItem); srtmMenuItem.setSelected(true); srtmMenuItem.setText("SRTM Only"); srtmMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { srtmMenuItemActionPerformed(evt); } }); optionsMenu.add(srtmMenuItem); datasourceGroup.add(asterMenuItem); asterMenuItem.setText("ASTER Only"); asterMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { asterMenuItemActionPerformed(evt); } }); optionsMenu.add(asterMenuItem); datasourceGroup.add(bothMenuItem); bothMenuItem.setText("SRTM plus ASTER"); bothMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bothMenuItemActionPerformed(evt); } }); optionsMenu.add(bothMenuItem); optionsMenu.add(jSeparator2); legacyMenuItem.setText("Legacy ASTER Data"); legacyMenuItem.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { legacyMenuItemActionPerformed(evt); } }); optionsMenu.add(legacyMenuItem); 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.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, 400, 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, 237, 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)); properties.set(TerrainProperties.REGION, region); if (properties.get(TerrainProperties.ASTER).equalsIgnoreCase("aster")) { reader = new ASTERReader(statusPanel); containsASTER = true; } else if (properties.get(TerrainProperties.ASTER).equalsIgnoreCase("both")) { reader = new CompositeReader(statusPanel); } else if (properties.get(TerrainProperties.ASTER).equalsIgnoreCase("srtm")) { reader = new SRTM2Reader(statusPanel); ///// containsASTER = false; } String leg = properties.get(TerrainProperties.LEGACYASTER); boolean v1 = leg.equalsIgnoreCase("true"); reader.setLegacy(v1); 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 (MissingDataFileException miss) { InfoMessage error = new InfoMessage("Initialisation error", miss.getMessage(), Severity.FATAL); error.display(this); System.exit(0); } }//GEN-LAST:event_createMenuItemActionPerformed 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 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 autoDownloadMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoDownloadMenuItemActionPerformed String newValue = "false"; if (autoDownloadMenuItem.getState()) { newValue = "true"; } properties.set(TerrainProperties.AUTO, newValue); setDownload(newValue); downloadSetting = newValue; }//GEN-LAST:event_autoDownloadMenuItemActionPerformed private void regionMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_regionMenuItemActionPerformed // String region = properties.get(TerrainProperties.REGION); RegionSelect reg = new RegionSelect(this, true); reg.select(region); reg.setVisible(true); if (reg.getReturnStatus() == RegionSelect.RET_OK) { String newRegion = reg.getSelection(); if (!region.equalsIgnoreCase(newRegion)) { properties.set(TerrainProperties.REGION, newRegion); region = newRegion; } } }//GEN-LAST:event_regionMenuItemActionPerformed 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); box.setDate(dateString); box.setVisible(true); }//GEN-LAST:event_aboutMenuItemActionPerformed 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 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 srtmMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_srtmMenuItemActionPerformed if (srtmMenuItem.getModel().isSelected()) { properties.set(TerrainProperties.ASTER, "srtm"); autoDownloadMenuItem.setEnabled(true); legacyMenuItem.setEnabled(false); } }//GEN-LAST:event_srtmMenuItemActionPerformed private void asterMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_asterMenuItemActionPerformed if (asterMenuItem.getModel().isSelected()) { properties.set(TerrainProperties.ASTER, "aster"); autoDownloadMenuItem.setEnabled(false); legacyMenuItem.setEnabled(true); } }//GEN-LAST:event_asterMenuItemActionPerformed private void bothMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bothMenuItemActionPerformed if (bothMenuItem.getModel().isSelected()) { properties.set(TerrainProperties.ASTER, "both"); autoDownloadMenuItem.setEnabled(true); legacyMenuItem.setEnabled(true); } }//GEN-LAST:event_bothMenuItemActionPerformed 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 legacyMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_legacyMenuItemActionPerformed String res = "false"; if (legacyMenuItem.getModel().isSelected()) { res = "true"; } properties.set(TerrainProperties.LEGACYASTER, res); }//GEN-LAST:event_legacyMenuItemActionPerformed 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); } public String getAutodownload() { return downloadSetting; } public void setAutodownload(String flag) { this.pcs.firePropertyChange("autodownload", downloadSetting, flag); downloadSetting = flag; } 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 missng data points", reader.missing()), String.format("Cache hit-rate = %6.2f%%", hitrate * 100.0) }; return new InfoMessage("Calculation complete", message, Severity.SUCCESS); } catch (Exception ex) { String[] msg = {"Unable to retrieve height data", ex.getClass().getName(), ex.getMessage()}; 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); } mosaic.repaint(); message.display(mainFrame); } catch (InterruptedException ex) { } catch (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.printf(locale, "; %s%n", reader.copyright()); out.printf(locale, "; Used coordinate system %s with ellipsoid \"%s\" 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 - 2012 Mike McCombe %n"); out.printf(";%n"); 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")) { 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")) { //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.printf(locale, "# %s%n", reader.copyright()); out.printf(locale, "# Used coordinate system %s with ellipsoid \"%s\" 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 - 2012 Mike McCombe %n"); 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.JRadioButtonMenuItem asterMenuItem; private javax.swing.JCheckBoxMenuItem autoDownloadMenuItem; private javax.swing.JRadioButtonMenuItem bothMenuItem; 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.JPopupMenu.Separator jSeparator2; private javax.swing.JSeparator jSeparator3; private javax.swing.JMenuItem latLongMenuItem; private javax.swing.JRadioButtonMenuItem legacyMenuItem; private javax.swing.JMenuBar menuBar; private javax.swing.JMenuItem offsetMenuItem; private javax.swing.JMenu optionsMenu; private javax.swing.JProgressBar progressBar; private javax.swing.JMenuItem regionMenuItem; private javax.swing.JMenuItem saveAsMenuItem; private javax.swing.JRadioButtonMenuItem srtmMenuItem; 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 JDialog aboutBox; /// private SRTM2Reader reader = null; ///// 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 static final DefaultProperties defaults = new DefaultProperties(); public static PropertySet properties = new PropertySet(paths.propertiesPath(), defaults); private String downloadSetting = properties.get(TerrainProperties.AUTO); private String region = properties.get(TerrainProperties.REGION); private String currentGridRef = properties.get(TerrainProperties.GRIDREF); private String useLegacyASTER = properties.get(TerrainProperties.LEGACYASTER); private MosaicPanel mosaic = new MosaicPanel(); 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 Position currentPosition = currentLocation.getPosition(); 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 LocationDialog dlg = new LocationDialog(this, true, toolkit, properties); 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)); PropertyChangeSupport pcs = new PropertyChangeSupport(this); private boolean gridrefdisplayed = false; private boolean processing = false; private int northSouthAlignment = SwingConstants.BOTTOM; private int eastWestAlignment = SwingConstants.LEFT; private int alignment = Integer.parseInt(properties.get(TerrainProperties.ALIGNMENT)); private LatLongDialog latlondialog = new LatLongDialog(this, true, toolkit, currentLocation); private boolean allowDataChoice = true; private String useASTER = properties.get(TerrainProperties.ASTER); private String coordSystemString = properties.get(TerrainProperties.THERIONCS); private boolean containsASTER = false; /* * Version information */ private static final String versionID = "1.13"; // Version 1.13 24th November 2017 private static final int BUILDYEAR = 2017; private static final int BUILDMONTH = 11; private static final int BUILDDAY = 24; /** * Holds value of property download. */ private String download; /** * Adds a PropertyChangeListener to the listener list. * * @param l The listener to add. */ public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } /** * Removes a PropertyChangeListener from the listener list. * * @param l The listener to remove. */ public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { pcs.removePropertyChangeListener(l); } /** * Getter for property download. * * @return Value of property download. */ public String getDownload() { return this.download; } /** * Setter for property download. * * @param download New value of property download. */ public void setDownload(String download) { String oldDownload = this.download; this.download = download; pcs.firePropertyChange("download", oldDownload, download); } } ./src/mccombe/terrain/DefaultPathnames.java0000775000175000017500000000141611731674171020576 0ustar wookeywookey/* * 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 mccombe.terrain; /** * * @author Mike */ public class DefaultPathnames implements Pathnames { @Override public String propertiesPath() { return home + sep + subdir + sep ; } @Override public String dataPath() { return home + sep + subdir + sep ; } private static String home = System.getProperty("user.home"); private static String sep = System.getProperty("file.separator"); private static String subdir = ".terraintool"; } ./src/mccombe/terrain/LatLongDialog.java0000775000175000017500000003454311731674171020040 0ustar wookeywookey/* * LatLongDialog.java * * Created on 25 February 2008, 16:46 */ package mccombe.terrain; 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; import mccombe.mapping.*; /** * * @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/mccombe/terrain/AboutDialog.java0000775000175000017500000002177413206047750017550 0ustar wookeywookey/* * AboutDialog.java * * Created on 20 January 2008, 21:51 */ package mccombe.terrain; /** * * @author Mike */ public class AboutDialog extends javax.swing.JDialog { /** Creates new form AboutDialog */ 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(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/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(jLabel2.getFont().deriveFont(jLabel2.getFont().getStyle() | java.awt.Font.BOLD, jLabel2.getFont().getSize()+4)); jLabel2.setText("Terrain Tool"); jLabel3.setFont(jLabel3.getFont().deriveFont(jLabel3.getFont().getStyle() | java.awt.Font.BOLD, jLabel3.getFont().getSize()+2)); jLabel3.setText("Version:"); versionLabel.setFont(versionLabel.getFont()); versionLabel.setText("1.0"); jLabel5.setFont(jLabel5.getFont().deriveFont(jLabel5.getFont().getStyle() | java.awt.Font.BOLD, jLabel5.getFont().getSize()+2)); jLabel5.setText("Date:"); buildDate.setText("13 Jul 2009"); jLabel7.setFont(jLabel7.getFont().deriveFont(jLabel7.getFont().getStyle() | java.awt.Font.BOLD, jLabel7.getFont().getSize()+2)); jLabel7.setText("Author:"); jLabel8.setText("Mike McCombe"); jLabel4.setFont(new java.awt.Font("Tahoma", 1, 13)); // NOI18N jLabel4.setText("E-mail:"); jLabel9.setText("mikemccombe@btinternet.com"); jLabel10.setText("This software is distributed under the terms of the \n"); jLabel11.setText("GNU General Public Licence v3"); jLabel12.setText("Copyright 2008 - 2017 Mike McCombe"); 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) .addComponent(jLabel2) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel3) .addComponent(jLabel5) .addComponent(jLabel7) .addComponent(jLabel4)) .addGap(86, 86, 86) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(versionLabel) .addComponent(jLabel8) .addComponent(buildDate) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(jButton1) .addComponent(jLabel9)))) .addComponent(jLabel10) .addComponent(jLabel11) .addComponent(jLabel12)) .addGap(33, 33, 33)) ); 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, 215, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(jLabel2) .addGap(18, 18, 18) .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(jLabel4) .addComponent(jLabel9)) .addGap(18, 18, 18) .addComponent(jLabel10) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel11) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 21, Short.MAX_VALUE) .addComponent(jButton1)) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel12))))) .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() { public void run() { AboutDialog dialog = new AboutDialog(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); } }); } /** * 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); } // 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 jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel7; private javax.swing.JLabel jLabel8; private javax.swing.JLabel jLabel9; private javax.swing.JLabel versionLabel; // End of variables declaration//GEN-END:variables } ./src/mccombe/terrain/DefaultProperties.java0000775000175000017500000000265413173103352021005 0ustar wookeywookey/* * DefaultsProperties.java * * Created on 12 May 2006, 10:59 * */ package 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("ftpsite","https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/"); this.setProperty("autodownload", "true"); this.setProperty("coordinatesystem", "OSGB"); this.setProperty("currentgridref", "ST 700600"); 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", "srtm"); this.setProperty("therionCoordinateSet","OSGB"); this.setProperty("legacyASTER","false"); } } ./src/mccombe/terrain/SRTM2Reader.java0000775000175000017500000000265511731674177017357 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package mccombe.terrain; import javax.swing.JComponent; /** * * @author Mike McCombe */ public class SRTM2Reader extends DEMReader { public SRTM2Reader(JComponent item) throws MissingDataFileException { 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"; } public String copyright() { return copyright ; } private static final boolean downloadable = true; private static final int recordlength = 1201; private static final String filenameformat = "%1s%02d%1s%03d"; private static final String name = "Shuttle Radar Topography Mission"; private static final String extn = ".hgt.zip"; private static final boolean littleendian = false; private static final int missingValue = -32768; private static final String copyright = "SRTM DEM data is public-domain."; } ./src/mccombe/terrain/InfoMessage.java0000775000175000017500000000216411731674156017555 0ustar wookeywookeypackage mccombe.terrain; import 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/mccombe/terrain/DEMReader.java0000775000175000017500000003571713206036547017112 0ustar wookeywookeypackage mccombe.terrain; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import mccombe.mapping.*; import java.io.*; import java.util.zip.*; import javax.swing.JComponent; import javax.net.ssl.HttpsURLConnection; /** * * @author Mike */ public abstract class DEMReader extends PropertyChangeSupport { protected DEMReader(JComponent item) throws MissingDataFileException { super(item); PropertyChangeListener[] listeners = item.getPropertyChangeListeners(); for (PropertyChangeListener ear : listeners) { addPropertyChangeListener(ear); } addPropertyChangeListener(listener); item.addPropertyChangeListener(listener); String slash = System.getProperty("file.separator"); DIRECTORY = TerrainFrame.paths.dataPath(); File dir = new File(DIRECTORY); if (!dir.isDirectory()) { boolean madeDirectory = dir.mkdir(); if (!madeDirectory) { throw new MissingDataFileException(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); } public double getHeight(LatLong place) throws MissingDataFileException { double lat = Math.floor(place.lat()); double lon = Math.floor(place.lon()); String ew = "E"; String ns = "N"; if (lat < 0) { ns = "S"; } if (lon < 0) { ew = "W"; } lat = Math.abs(lat); lon = Math.abs(lon); double x0 = tile(place.lon()); double y0 = (double) (recordlength() - 1) - tile(place.lat()); int xtile = (int) x0; int ytile = (int) y0; String pagename = makename(ns, (int) lat, ew, (int) lon); double[] h = new double[3]; double[] x = new double[3]; double[] w = new double[3]; double[] y = new double[3]; int k = Math.max(ytile - 1, 0); int m = 0; while (m < 3 && k < recordlength() && k < ytile + 3) { CacheEntry page = getRow(pagename, k); if (page == null) { return MISSING; } int i = 0; int j = xtile; while (i < 3 && j < recordlength() && j < xtile + 4) { int v = page.getValue(j); if (v != missingValue()) { x[i] = (double) j; h[i] = (double) v; i++; } else { missing++; } j++; } j = xtile - 1; while (i < 3 && j >= 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 { 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 e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } catch (java.security.KeyManagementException e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } catch (java.security.NoSuchAlgorithmException e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } } in = new java.util.zip.ZipInputStream(new FileInputStream(infile)); String entryname = ""; do { ZipEntry entry = in.getNextEntry(); if(entry==null) throw new MissingDataFileException(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(); int n = heights.length; 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()); } } public int[] readRecord() throws EOFException, 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) { throw new EOFException(); } sofar += res; } for (int i = 0; i < recordlength(); i++) { short temp; 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; } return outbuffer; } public double tile(double x) { double q = Math.floor(x); double r = (recordlength() - 1) * (x - q); return r; } public double frac(int tile) { double x = (double) tile / (double) (recordlength() - 1); return x; } protected void downloadFile(String filename) throws java.security.KeyManagementException, java.security.NoSuchAlgorithmException, IOException, MissingDataFileException { if (!download || !downloadable()) { String msg = String.format("Needs data file %s - DEM not downloadable or auto-download is disabled", filename); throw new MissingDataFileException(msg); } String urlString = getProperty(TerrainProperties.FTP) + getProperty(TerrainProperties.REGION) + "/" + filename; java.net.URL url = new java.net.URL(urlString); setMessage(String.format("Downloading data: %s", filename)); HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); InputStream ins = con.getInputStream(); // java.io.DataInputStream instream = new java.io.DataInputStream(ins); String outfilename = DIRECTORY + filename; File outfile = new java.io.File(outfilename); DataOutputStream outstream = new DataOutputStream(new FileOutputStream(outfile)); long available = ins.available(); int bytecount = 0; long sofar = 0; long guess = 900000; while (bytecount != -1) { byte[] buffer = new byte[BUFFERLENGTH]; bytecount = ins.read(buffer); if (bytecount > 0) { String str = new String(buffer); 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(""); return; } protected class CacheEntry { public CacheEntry(String name, int[] buffer) { page_name = name; last_used = cycle; data = buffer; } public void setLastUsed() { last_used = cycle; } public String getName() { return page_name; } public int getValue(int i) { return data[i]; } public long lastUsed() { return last_used; } private long last_used; private String page_name; private int[] data; } 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; } private void setMessage(String msg) { if (!msg.equals(lastMessage)) { firePropertyChange("message", lastMessage, msg); } lastMessage = msg; } private void setProgress(int val) { int v = Math.min(100, val); v = Math.max(0, v); if (lastValue != v) { firePropertyChange("progress", lastValue, v); } lastValue = v; } private String getProperty(TerrainProperties propertyName) { return TerrainFrame.properties.get(propertyName); } public void setDownload(boolean flag) { download = flag; } public PropertyChangeListener[] getPropertyChangeListeners() { PropertyChangeListener[] res = new PropertyChangeListener[1]; res[0] = listener; return res; } private PropertyChangeListener listener = new java.beans.PropertyChangeListener() { 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 void setLegacy(boolean t) { useLegacy = t ; } 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(); public abstract String zipEntryName(String name); protected static java.util.zip.ZipInputStream in; protected String DIRECTORY = ""; // private static final int DEMSettings.recordlength() = 1201; 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; public static final double MISSING = -32768.0; protected static final int BUFFERLENGTH = 1024; protected String lastMessage = ""; protected boolean download = true; 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 DEMProfile DEMSettings = DEMProfile.SRTM; } ./src/mccombe/terrain/PropertySet.java0000775000175000017500000000450311731674156017654 0ustar wookeywookey/* * PropertySet.java * * Created on 12 May 2006, 10:08 * */ package 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); } private String getProperty(String name){ return properties.getProperty(name); } private void setProperty(String key, String value){ properties.setProperty(key, 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/mccombe/terrain/MosaicPanel.form0000775000175000017500000000674311754242474017600 0ustar wookeywookey
./src/mccombe/terrain/AboutDialog.form0000775000175000017500000002612413206047750017564 0ustar wookeywookey
./src/mccombe/terrain/images/0000775000175000017500000000000013206557741015750 5ustar wookeywookey./src/mccombe/terrain/images/busy-icon5.png0000775000175000017500000000677411731674074020474 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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É#„c6†lé{şŞµÎ–Ëww˙11ů~Eq Ć?ńăkă×wřJĹj5—ٶ íXWÜśÍLF‘&#?Źvn}îŮŠ6Z„aD˘(*Ő%ścŹuv|Äçć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.JMťş„/ĄÔ¶”:ź/<µ’`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/mccombe/terrain/images/idle-icon.png0000775000175000017500000000644011731674076020332 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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ŰÓ{WvF<ß_‘Ać#bj*˝5a˙i‚ ĚKĆXýɸĐ0¤¦k8?ď,MrŹk4]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/mccombe/terrain/images/About.png0000775000175000017500000007224011731674076017542 0ustar wookeywookey‰PNG  IHDRú§"±&gAMAŻČ7ŠétEXtSoftwareAdobe ImageReadyqÉe<PLTEw¨ăwcF‰´ĺ:sĹx¤Ű8Ec¦šŽLTm…f7ŮŰä‰tV•xG—™¦yc8TŚÖu[6¨‘niU5Z’Ú‹rH)4MvfĄ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˘áYBŤp<®â˘¦˝a’Ńŕâë<>4i|Ą¤Ş´`]b±¶ĚAuÄ»±«‹mK/,!ОŢv–Ăź}J\`Woc-]µŃĘĚŽg\O)PMR©¦±…ťĂ¶žwF}ËťxdKoK&gžáŕßć}q“lEŹl5»¶Á$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“'˘±"ú‚SZŚO&tíÇ˙NĽî_?‘ýK_đ“xBĄŔ€U`˛*ů, a @1 | öřC¸9ˇ8*ŁŔĚ„ĂHFâFˇC¦ĆD!3ŕ¬Hęg$ŚŞ`Y  {D?yD€Üúě92A˛ć( )Ŕ`ěHP°  –ŕ„„^$Du #%řŠÂqý §ÁäaA"¦+ś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 FSmj‚1°@ @á6٢ňŕ|[lÚAFC€éŻ06ÔćŻČX@Đ„ć¤đ \ţúč/ †MżU@ X ‚€0¨­ Â!ěŔ–€(Ě˙¶p_7ˇ V X@RËW€\QA ęŮťýźLťEnuH\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<ŔÄŁ­OKvŻ áňě,(9 B AB`ŚD€@.• $sA „™; ’¶‚(T€  ˇ ‡$,Ů»8<‹ćÜ ‰ň'’wľÍň„ř&ü „ssżĺzÂ!}P ô-÷ŃepÔן!Ä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(‰tCBn °˝~ĐňϞŠ_Rëuúű’wAq ń)ÚÁäůŽäx0 „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ĽÜé «€©»XŤu&ěÎěÔřŐ ô;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'¤µfdeŰ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{öěcŤ6ě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 ‚‰`¶1IřŔ¤_ö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ň:ŘÓ`oy@‰íŰE éŘ„Ž«DŤôE 2®îţ\›™Ŕ–LČ}v›pőVó7$eĄWLť*sŞ5MY^ń‘sŢ ˛łźî |fý,mĹb …´Ĺśś·§]ÚĆ©#![ö|íZ‰9Ó$¦éó:°Ë})âGŽáî_±ŔŚ>§ űwné/ßŇo@ ńVçť»“yâ:óÇł€ń.ľ}â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ě¬ŃŔţh1°őĆęËsďŹďĽĐÖŔöPďrßęićNi ­i < Î+ćěM“QźćşŘuaa6۶]z®ł{÷nĂiłýćţO;vűď1‰cÇnŻÝkanáĹS{í«ćüčÍěü!ÓđäÎťŽűÄ0yJU˘ Ş 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Ö@›†NťgN‡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.Ć…!S0rg§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Áâ˙‘Ő­ŐGXĄ…=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ČlŹ7›u#mÂ1˝sÎzޢÍî I ń“aŰăóŇ­¬Tť÷î•q¶ř“vcâýh…˝ÎS˝kŹL6;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›Ń}Ńý¬]»Ţĺ2Źnk°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ŻďŔ{˘™WKUPđ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ť1ŤTž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_NwYŃ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Šö…,[8w·Éqđxm °ĺ•’rB5Ĺ,]UňHµ¨ľčşó’éoßN~›b,ŐS€­ăeËf„‡€ęwŔ®ŢFÓ˅Ľ ůM7Y˝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ĘÂý ľú]uťdú+.®—&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ŔjŤkßf`Ť›,µ…÷…„\]Ŕô é77S·Ň  .y+=ýĆ[`«ÉÔtÓ»e ĆĐÂ…ű€y쥠ÇÂ/^,tŰčćf¦úÖËą5ŤmŻ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ůţ4ŹcőÖě„©ii©©é‰ÍŞżAË•<Ţ˱ ţU`mzś1űż Çd`MgjĘk _ĚŔ$âĆř’˙üy`—ëż©‡)qm-[-›~­óŢ´“wŚýćj>{¦°j'çÚXٽł^V ·ŠŹ_b!Łž?ł|ÎZ˝ÚyóRw¦6N7»±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Ôťó-daٲ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ŔĚ}B0ťQđă;`{ŔŔ¶•˝°‚óŘčÁ đ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ú?áZŹDBRRZë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ć±NHÜÜŔ‰ĺ„Đó $-”Ŕ~.(ÂťŠľ—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&Lť7ŻśgęNuő]šd-ždŕfđta`nß´ Ř•’?ôű!1 8sF@LX¬"íbJŔLÂ+hęÁ,$M=@%$˙2^UPVá:§ĎŘĽÝ ±ąYUÇĽ@…dž„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%Ě‘HPp­üÔ„´%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ĘĘ&çyŹWzPtÜXŤęńMŔ‚ čE±CŕT//)ö«>y‡ą§,Xl×rzrŕ¶‘‡?‡)ďŹ>÷`±¬:Ů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ßÄ„Ť­VOAŹMˇ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[YŹK^ ű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ĽxJRQ±Ň9ýâó˘GŠ•˛Őćć˘çí9Áâă ÍŚé'ÖqmlN74w€‘ţ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žÓŰ|Ż< ő%ů^ůNKA´ËĚŕýÝ—ç_Ą]$ňňŐ˛!/•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ł9Nqvb\')šŻş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=ç»—qťWĹ=€‹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/mccombe/terrain/images/busy-icon7.png0000775000175000017500000000701611731674075020465 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/busy-icon8.png0000775000175000017500000000701211731674075020462 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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ŮÜř»ÓÓ32ľČüčťµ-í› 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„üŐŰ»}ľZť7ëőĂÜŘŇĚË• E‡ż_}摾‡?^ °¸¸”zîŮţ !„áîîdٲ,M©­Oś<őüŹ*Ó3ýA Ą˛9ŤźîrRŞ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/mccombe/terrain/images/pos2.png0000775000175000017500000000170111731674077017346 0ustar wookeywookey‰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@Ä ç¬óťłĎŔ –H3Q5€ ©BŕÇÄĆáä.@ $płd!sý#ř~<<+"ŔľxÓ ŔM›Ŕ0‡˙ęB™\€„Ŕt‘8K€@zŽB¦@F€ť&S `ËcbăP-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/pos9.png0000775000175000017500000000170611731674106017353 0ustar wookeywookey‰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/mccombe/terrain/images/busy-icon1.png0000775000175000017500000000700111731674105020443 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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)ď¶Ű6BAâńŹ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/mccombe/terrain/images/busy-icon11.png0000775000175000017500000000677511731674076020554 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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.ŕ=ďľ3ŤD„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/mccombe/terrain/images/pos8.png0000775000175000017500000000171011731674105017344 0ustar wookeywookey‰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Éżđ6Nľ}<IEND®B`‚./src/mccombe/terrain/images/busy-icon9.png0000775000175000017500000000677511731674075020502 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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!§¦Ż~ţ^ĺ~ŐŮý‡ç8vť9lËqlaŰŁ45M“Bt4i çŇaŚŻ=“)ĄĽ|ë».,Vă”ReYT9ŽX–Ą,JĄT™&Ń ×­ öφq$Ú3g/–+Őł ůĄ#ĺJ5Za˘* ‹D˘§vřPj=Ť| 7Ŕřw A5GÁ?IEND®B`‚./src/mccombe/terrain/images/busy-icon6.png0000775000175000017500000000677511731674074020476 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/busy-icon3.png0000775000175000017500000000676411731674106020465 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/pos1.png0000775000175000017500000000170211731674076017345 0ustar wookeywookey‰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@Ä ç¬óťłĎŔ –H3Q5€ ©BŕÇÄĆáä.@ $płd!sý#ř~<<+"ŔľxÓ ŔM›Ŕ0‡˙ęB™\€„Ŕt‘8K€@zŽB¦@F€ť&S `ËcbăP-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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+›IŹo»˛ąŹî  :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/mccombe/terrain/images/busy-icon4.png0000775000175000017500000000677011731674074020467 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/busy-icon14.png0000775000175000017500000000700211731674076020537 0ustar wookeywookey‰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-`'ćÓ€ťř™{[”! ‘ eDh;¬Ď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ĺe2AUŁšRݨˇT5ŹZB­ˇ¶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—á„‘ąŃ<ŁŐFŤFŚ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ă¶Ë)ÄiťS›Ó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†?ŽpXŃ1—5wŃÜCsßDúD–DŢ›g1O9Ż-J5*>Ş.j<Ú7ş4ş?Ć.fYĚŐXťXIlK9.*®6nlľßüíó‡âťâ ă{/Č]pyˇÎÂô…§©.,:–@LN8”đ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/mccombe/terrain/images/pos7.png0000775000175000017500000000170711731674105017351 0ustar wookeywookey‰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/mccombe/terrain/LocationDialog.java0000775000175000017500000004250111731674172020242 0ustar wookeywookey/* * LocationDialog.java * * Created on 19 January 2008, 16:24 */ package mccombe.terrain; 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 mccombe.mapping.*; 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/mccombe/terrain/TerrainFrame.form0000775000175000017500000004040313206047750017745 0ustar wookeywookey
./src/mccombe/terrain/CoordinateDialog.java0000775000175000017500000002063011731674166020563 0ustar wookeywookey/* * CoordinateDialog.java * * Created on 19 January 2008, 16:46 */ package mccombe.terrain; import java.awt.event.ActionEvent; import mccombe.mapping.*; 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/mccombe/terrain/CoordinateDialog.form0000775000175000017500000001430411731674176020607 0ustar wookeywookey
./src/mccombe/terrain/UnixPathnames.java0000775000175000017500000000201111731674171020125 0ustar wookeywookey/* * Provide sensible pathnames for data files and properties for use with Unix platforms */ package 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/mccombe/terrain/CompositeReader.java0000775000175000017500000002067013205315511020425 0ustar wookeywookeypackage mccombe.terrain; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import mccombe.mapping.*; import java.io.*; import java.util.zip.*; import javax.swing.JComponent; /** * * @author Mike */ public class CompositeReader extends SRTM2Reader { public CompositeReader(JComponent item) throws MissingDataFileException { super(item); subReader = new ASTERReader(item); } /* @Override public double getHeight(LatLong place) throws MissingDataFileException { double ht = mainReader.getHeight(place); if (ht == MISSING) ht = subReader.getHeight(place); return ht ; } */ private DEMReader subReader; @Override public double getHeight(LatLong place) throws MissingDataFileException { double lat = Math.floor(place.lat()); double lon = Math.floor(place.lon()); String ew = "E"; String ns = "N"; if (lat < 0) { ns = "S"; } if (lon < 0) { ew = "W"; } lat = Math.abs(lat); lon = Math.abs(lon); double x0 = tile(place.lon()); double y0 = (double) (recordlength() - 1) - tile(place.lat()); int xtile = (int) x0; int ytile = (int) y0; String pagename = makename(ns, (int) lat, ew, (int) lon); double[] h = new double[3]; double[] x = new double[3]; double[] w = new double[3]; double[] y = new double[3]; int k = Math.max(ytile - 1, 0); int m = 0; while (m < 3 && k < recordlength() && k < ytile + 3) { CacheEntry page = getRow(pagename, k, place); if (page == null) { return MISSING; } int i = 0; int j = xtile; while (i < 3 && j < recordlength() && j < xtile + 4) { int v = page.getValue(j); if (v != missingValue()) { x[i] = (double) j; h[i] = (double) v; i++; } else { missing++; } j++; } j = xtile - 1; while (i < 3 && j >= 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, LatLong place) throws MissingDataFileException { 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 e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } catch (java.security.KeyManagementException e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } catch (java.security.NoSuchAlgorithmException e) { throw new MissingDataFileException(String.format("Unable to dowload missing file %s%n%s%n", tempname, e.toString())); } } in = new java.util.zip.ZipInputStream(new FileInputStream(infile)); String entryname = ""; do { ZipEntry entry = in.getNextEntry(); if(entry==null) throw new MissingDataFileException(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(); int n = heights.length; if (recordno == ytile) { break; } recordno++; } for (int q = 0; q < heights.length; q++) { if (heights[q] == missingValue()) { double lat = Math.floor(place.lat()) + (1.0-frac(ytile)); double lon = Math.floor(place.lon()) + frac(q); LatLong here = new LatLong(lat, lon); double ht = subReader.getHeight(here); int height = (int) Math.round(ht); if (height == subReader.missing()) { height = (int) MISSING; } heights[q] = height; } } 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()); } } public String datasetName() { if(useLegacy) return legacyDatasetName; 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 copyright() { return copyright; } private static final boolean downloadable = true; private static final int recordlength = 1201; private static final String filenameformat = "%1s%02d%1s%03d"; private static final String legacyDatasetName = "Shuttle Radar Topography Mission plus ASTER"; private static final String name = "Shuttle Radar Topography Mission plus ASTER V2"; private static final String extn = ".hgt.zip"; private static final boolean littleendian = false; private static final int missingValue = -32768; private static final String copyright = "ASTER GDEM is a product of METI and NASA"; } ./src/mccombe/terrain/RegionSelect.form0000775000175000017500000001050111731674165017754 0ustar wookeywookey
./src/mccombe/terrain/Pathnames.java0000775000175000017500000000071011731674171017265 0ustar wookeywookeypackage 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/mccombe/terrain/OffsetDialog.form0000775000175000017500000001624311731674175017751 0ustar wookeywookey
./src/mccombe/terrain/OffsetDialog.java0000775000175000017500000002554411731674165017732 0ustar wookeywookey/* * OffsetDialog.java * * Created on 03 February 2008, 12:54 */ package 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/mccombe/terrain/TherionCSDialog.java0000775000175000017500000001711211731674167020334 0ustar wookeywookey/* * 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 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/mccombe/terrain/LatLongDialog.form0000775000175000017500000001642311731674164020061 0ustar wookeywookey
./src/mccombe/terrain/MissingDataFileException.java0000775000175000017500000000114211731674171022227 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package mccombe.terrain; /** * * @author Mike */ public class MissingDataFileException extends Exception { /** * Creates a new instance of MissingDataFileException without detail message. */ public MissingDataFileException() { } /** * Constructs an instance of MissingDataFileException with the specified detail message. * @param msg the detail message. */ public MissingDataFileException(String msg) { super(msg); } } ./src/mccombe/terrain/TherionCSDialog.form0000775000175000017500000001271211731674156020355 0ustar wookeywookey
./src/mccombe/terrain/MosaicPanel.java0000775000175000017500000004273611754242474017560 0ustar wookeywookey/* * MosaicPanel.java * * Created on 21 January 2008, 10:40 */ package 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 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) { dataTable = buffer; minval = 1.0E99; maxval = -1.0E99; for (float[] row : dataTable) { for (float x : row) { if (x != SRTM2Reader.MISSING) { 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/mccombe/terrain/ASTERReader.java0000775000175000017500000000352411731674164017356 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package mccombe.terrain; import java.io.EOFException; import java.io.IOException; import javax.swing.JComponent; /** * * @author Mike */ public class ASTERReader extends DEMReader { public ASTERReader(JComponent item) throws MissingDataFileException { super(item); } public String datasetName() { if(useLegacy) return legacyDatasetName; return name; } public boolean downloadable() { return downloadable; } public int recordlength() { return recordlength; } public String formatstring() { if(useLegacy) return legacyfilenameformat; return filenameformat; } public String extn() { return extn; } public boolean littleendian() { return littleendian; } public int missingValue() { return missingValue; } public String zipEntryName(String name){ return name + "_dem.tif"; } public String copyright() { return copyright ; } private static final boolean downloadable = false; private static final int recordlength = 3601; private static final String legacyfilenameformat = "ASTGTM_%1s%02d%1s%03d"; private static final String filenameformat = "ASTGTM2_%1s%02d%1s%03d"; private static final String name = "Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) Version 2"; private static final String legacyDatasetName = "Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER)"; private static final String copyright = "ASTER GDEM is a product of METI and NASA"; private static final String extn = ".zip"; private static final boolean littleendian = true; private static final int missingValue = -9999; } ./src/mccombe/terrain/RegionSelect.java0000775000175000017500000001330711731674171017736 0ustar wookeywookey/* * RegionSelect.java * * Created on 20 January 2008, 17:07 */ package 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/mccombe/mapping/0000775000175000017500000000000013206557733014473 5ustar wookeywookey./src/mccombe/mapping/Ellipsoid.java0000775000175000017500000000712011731674243017262 0ustar wookeywookey/* * Ellipsoid.java * * Created on 05 July 2005, 18:07 * */ package 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/mccombe/mapping/TransverseMercator.java0000775000175000017500000002444111731674244021175 0ustar wookeywookey/* * TransverseMercator.java * * Created on 05 July 2005, 16:14 * */ package 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/mccombe/mapping/LambertIIExtended.java0000775000175000017500000000542011731674244020631 0ustar wookeywookey/* * LambertIIExtended.java * * Created on 15 July 2005, 18:50 * */ package 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 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/mccombe/mapping/GridFormatException.java0000775000175000017500000000102411731674243021250 0ustar wookeywookeypackage 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/mccombe/mapping/NZMG.java0000775000175000017500000001543011731674243016114 0ustar wookeywookey/* * 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 mccombe.mapping; /** * * @author Mike McCombe */ public class NZMG extends 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 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 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/mccombe/mapping/LambertIII.java0000775000175000017500000000617211731674243017265 0ustar wookeywookey/* * LambertIII.java * * Created on 19 July 2005, 09:44 * */ package 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 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/mccombe/mapping/LambertI.java0000775000175000017500000000615011731674242017036 0ustar wookeywookey/* * LambertI.java * * Created on 19 July 2005, 09:30 * */ package 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 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/mccombe/mapping/AustrianM31.java0000775000175000017500000000316711731674242017413 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package 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/mccombe/mapping/Spherical.java0000775000175000017500000000372611731674243017260 0ustar wookeywookey/* * Spherical.java * * Created on 16 July 2005, 13:07 * * This is a simple spherical coordinate system using latitude and longitude to define position * */ package 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 & 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/mccombe/mapping/IrishGrid.java0000775000175000017500000001462311731674243017230 0ustar wookeywookey/* * IrishGrid.java * * * Created on 24 August 2005, 18:12 * */ package 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 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/mccombe/mapping/BMN.java0000775000175000017500000000417312774157611015762 0ustar wookeywookey/* */ package 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/mccombe/mapping/CoordinateSystem.java0000775000175000017500000001063511731674244020640 0ustar wookeywookey/* * CoordinateSystem.java * * Created on 05 July 2005, 17:17 * */ package 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 & 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 & Longitude.referred to the current Ellipsoid & Datum * @return String containing Latitude & 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/mccombe/mapping/Position.java0000775000175000017500000001177511731674242017154 0ustar wookeywookey/* * Position.java * * Created on 24 October 2007, 22:06 * */ package mccombe.mapping; /** * A Position represents the absolute location of a point in 3D space, independent of * any particular Datum, Ellipsoid or type of coordinates. The nature of the internal * representation of this point is deliberately encapsulated and irrelevant. This class * does not override the toString() 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 & Lon are defined * @param datum The Datum used to measure the Lat & 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/mccombe/mapping/NZTM2000.java0000775000175000017500000001033011731674244016426 0ustar wookeywookey/* * New Zealand Transverse Mercator 2000 Coordinate System */ package mccombe.mapping; /** * * @author Mike McCombe */ public class NZTM2000 extends 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 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 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/mccombe/mapping/Lambert93.java0000775000175000017500000000635411731674242017107 0ustar wookeywookey/* * Lambert93.java * * Created on 24 October 2006, 22:49 * */ package 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 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 mccombe.mapping.Datum defaultDatum() { return Datum.WGS_1984 ; } /** * Define the default Ellipsoid for this system * @return the default Ellipsoid (Ellipsoid.GRS80) */ public 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/mccombe/mapping/AustrianM28.java0000775000175000017500000000316711731674243017422 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package mccombe.mapping; /** * * @author Mike */ public class AustrianM28 extends BMN { public AustrianM28(Position p, Ellipsoid e, Datum d){ super(p, e, d); zonename = "M31"; } 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/mccombe/mapping/Datum.java0000775000175000017500000001772011731674243016417 0ustar wookeywookey/* * Datum.java * * Created on 13 July 2005, 13:13 * */ package 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 /** * 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/mccombe/mapping/MappingToolkit.java0000775000175000017500000002655211731674242020310 0ustar wookeywookeypackage 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); } /** * 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 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) { String name = f.getName(); Class type = f.getType(); String typeName = type.getName(); if (typeName.equals("mccombe.mapping.Datum")) { try { Datum datum = (Datum) f.get(null); datumList.add(datum); } catch (IllegalArgumentException ex) { } catch (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) { String name = f.getName(); Class type = f.getType(); String typeName = type.getName(); if (typeName.equals("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/mccombe/mapping/XYZ.java0000775000175000017500000000375411731674244016042 0ustar wookeywookey/* * XYZ.java * * Created on 13 July 2005, 13:05 * */ package 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/mccombe/mapping/LambertII.java0000775000175000017500000000617211731674242017153 0ustar wookeywookey/* * LambertII.java * * Created on 19 July 2005, 09:23 * */ package 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 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/mccombe/mapping/Projection.java0000775000175000017500000000270111731674243017452 0ustar wookeywookey/* * Projection.java * * Created on 05 July 2005, 17:30 * */ package 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/mccombe/mapping/Orthomorphic.java0000775000175000017500000001352612774154131020017 0ustar wookeywookey/* * 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 mccombe.mapping; /** * * @author Mike McCombe */ public abstract class Orthomorphic extends 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/mccombe/mapping/ENPair.java0000775000175000017500000000214511731674243016456 0ustar wookeywookey/* * ENPair.java * * Created on 05 July 2005, 16:56 * */ package 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/mccombe/mapping/OSGB.java0000775000175000017500000002311611731674244016074 0ustar wookeywookeypackage 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 & tidied 21-Oct-2007 */ public class OSGB extends 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 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 mccombe.mapping.Datum defaultDatum() { return Datum.OSGB_1936; } /** * Define the default Ellipsoid for this CoordinateSystem * @return Ellipsoid.AIRY */ public 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 && y",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 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 & 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/mccombe/mapping/Lambert.java0000775000175000017500000002530012772770660016732 0ustar wookeywookey/* * Lambert.java * * Created on 05 July 2005, 22:15 */ package 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 mccombe.mapping.LambertI LambertI}, * {@link mccombe.mapping.LambertII LambertII}, * {@link mccombe.mapping.LambertIII LambertIII}, * {@link 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 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 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 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 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 mccombe.mapping.Datum defaultDatum() { return Datum.NTF ; } /** * Define the default Ellipsoid for this coordinate system * @return default Ellipsoid */ public 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/mccombe/mapping/AustrianM34.java0000775000175000017500000000317011731674243017411 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package 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/mccombe/mapping/LambertIV.java0000775000175000017500000000610311731674243017163 0ustar wookeywookey/* * LambertIV.java * * Created on 19 July 2005, 09:50 * */ package 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 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/mccombe/mapping/LatLongFormatException.java0000775000175000017500000000142411731674244021730 0ustar wookeywookey/* * 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 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/mccombe/mapping/UTM.java0000775000175000017500000002037111731674242016005 0ustar wookeywookey/* * UTM.java * * Created on 13 July 2005, 11:50 * */ package 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 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 mccombe.mapping.Datum defaultDatum() { return Datum.WGS_1984; } /** * Get the default Ellipsoid for this CoordinateSystem * @return Default Ellipsoid (GRS80) */ public 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/mccombe/util/0000775000175000017500000000000013206557744014017 5ustar wookeywookey./src/mccombe/util/ErrorMessage.java0000775000175000017500000000144511731674246017265 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package 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/mccombe/util/Severity.java0000775000175000017500000000056211731674246016500 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package 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 ; } ./src/Tester.java0000775000175000017500000000300711731674247013550 0ustar wookeywookey/* * To change this template, choose Tools | Templates * and open the template in the editor. */ /** * * @author Mike */ import 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 ; Position pos = new Position(new LatLong(lat,lon), 0.0, Ellipsoid.GRS80, Datum.WGS_1984); // OSGB zz = new OSGB(pos,Ellipsoid.AIRY, Datum.OSGB_1936); // double c1 = zz.gridConvergence(); // double c2 = zz.gc(); // System.out.printf("Calc Grid Convergence = %9.6f Measured = %9.6f%n", c1,c2); // ENPair pr = z.toEN(); // System.out.printf("Example 1 E %9.1f N %9.1f%n",pr.east(), pr.north()); lat = -34.444066 ; lon = 172.739194 ; LatLong 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())); ENPair 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()); } }