diff options
| -rw-r--r-- | ao-tools/altosui/AltosFlightUI.java | 18 | ||||
| -rw-r--r-- | ao-tools/altosui/AltosInfoTable.java | 4 | ||||
| -rw-r--r-- | ao-tools/altosui/AltosSiteMap.java | 269 | ||||
| -rw-r--r-- | ao-tools/altosui/AltosSiteMapCache.java | 103 | ||||
| -rw-r--r-- | ao-tools/altosui/AltosSiteMapTile.java | 134 | ||||
| -rw-r--r-- | ao-tools/altosui/AltosUI.java | 6 | ||||
| -rw-r--r-- | ao-tools/altosui/GrabNDrag.java | 51 | ||||
| -rw-r--r-- | ao-tools/altosui/Makefile.am | 4 | 
8 files changed, 580 insertions, 9 deletions
| diff --git a/ao-tools/altosui/AltosFlightUI.java b/ao-tools/altosui/AltosFlightUI.java index d5bcdb10..1107d527 100644 --- a/ao-tools/altosui/AltosFlightUI.java +++ b/ao-tools/altosui/AltosFlightUI.java @@ -42,6 +42,7 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {  	AltosAscent	ascent;  	AltosDescent	descent;  	AltosLanded	landed; +	AltosSiteMap    sitemap;  	private AltosFlightStatus flightStatus;  	private AltosInfoTable flightInfo; @@ -85,6 +86,7 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {  		descent.reset();  		landed.reset();  		flightInfo.clear(); +		sitemap.reset();  	}  	public void show(AltosState state, int crc_errors) { @@ -111,6 +113,7 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {  		}  		flightStatus.show(state, crc_errors);  		flightInfo.show(state, crc_errors); +		sitemap.show(state, crc_errors);  	}  	public void set_exit_on_close() { @@ -142,12 +145,12 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {  			// Channel menu  			channels = new AltosChannelMenu(AltosPreferences.channel(serial));  			channels.addActionListener(new ActionListener() { -					public void actionPerformed(ActionEvent e) { -						int channel = channels.getSelectedIndex(); -						reader.set_channel(channel); -						AltosPreferences.set_channel(serial, channel); -					} -				}); +				public void actionPerformed(ActionEvent e) { +					int channel = channels.getSelectedIndex(); +					reader.set_channel(channel); +					AltosPreferences.set_channel(serial, channel); +				} +			});  			c.gridx = 0;  			c.gridy = 0;  			c.anchor = GridBagConstraints.WEST; @@ -182,6 +185,9 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {  		flightInfo = new AltosInfoTable();  		pane.add("Table", new JScrollPane(flightInfo)); +		sitemap = new AltosSiteMap(); +		pane.add("Site Map", sitemap); +  		/* Make the tabbed pane use the rest of the window space */  		c.gridx = 0;  		c.gridy = 2; diff --git a/ao-tools/altosui/AltosInfoTable.java b/ao-tools/altosui/AltosInfoTable.java index c571d5c9..723f8301 100644 --- a/ao-tools/altosui/AltosInfoTable.java +++ b/ao-tools/altosui/AltosInfoTable.java @@ -31,8 +31,8 @@ import java.util.concurrent.LinkedBlockingQueue;  public class AltosInfoTable extends JTable {  	private AltosFlightInfoTableModel model; -	private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 12); -	private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 12); +	private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 14); +	private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 14);  	static final int info_columns = 3;  	static final int info_rows = 17; diff --git a/ao-tools/altosui/AltosSiteMap.java b/ao-tools/altosui/AltosSiteMap.java new file mode 100644 index 00000000..72a65b15 --- /dev/null +++ b/ao-tools/altosui/AltosSiteMap.java @@ -0,0 +1,269 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; + +public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { +	// max vertical step in a tile in naut. miles +	static final double tile_size_nmi = 2.0; + +	static final int px_size = 512; + +	private static Point2D.Double translatePoint(Point2D.Double p, +			Point2D.Double d) +	{ +		return new Point2D.Double(p.x + d.x, p.y + d.y); +	} + +	static class LatLng { +		public double lat, lng; +		public LatLng(double lat, double lng) { +			this.lat = lat; +			this.lng = lng; +		} +	} + +	// based on google js +	//  http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js +	// search for fromLatLngToPoint and fromPointToLatLng +	private static Point2D.Double pt(LatLng latlng, int zoom) { +		double scale_x = 256/360.0 * Math.pow(2, zoom); +		double scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); +		return pt(latlng, scale_x, scale_y); +	} + +	private static Point2D.Double pt(LatLng latlng, +					 double scale_x, double scale_y) +	{ +		Point2D.Double res = new Point2D.Double(); +		double e; + +		res.x = latlng.lng * scale_x; + +		e = Math.sin(Math.toRadians(latlng.lat)); +		e = Math.max(e,-(1-1.0E-15)); +		e = Math.min(e,  1-1.0E-15 ); + +		res.y = 0.5*Math.log((1+e)/(1-e))*-scale_y; +		return res; +	} + +	static private LatLng latlng(Point2D.Double pt, +				     double scale_x, double scale_y) +	{ +		double lat, lng; +		double rads; + +		lng = pt.x/scale_x; +		rads = 2 * Math.atan(Math.exp(-pt.y/scale_y)); +		lat = Math.toDegrees(rads - Math.PI/2); + +		return new LatLng(lat,lng); +	} + +	int zoom; +	double scale_x, scale_y; + +	private Point2D.Double pt(double lat, double lng) { +		return pt(new LatLng(lat, lng), scale_x, scale_y); +	} + +	private LatLng latlng(double x, double y) { +		return latlng(new Point2D.Double(x,y), scale_x, scale_y); +	} +	private LatLng latlng(Point2D.Double pt) { +		return latlng(pt, scale_x, scale_y); +	} + +	AltosSiteMapTile [] mapTiles = new AltosSiteMapTile[9]; +	Point2D.Double [] tileOffset = new Point2D.Double[9]; + +	private Point2D.Double getBaseLocation(double lat, double lng) { +		Point2D.Double locn, north_step; + +		zoom = 2; +		// stupid loop structure to please Java's control flow analysis +		do { +			zoom++; +			scale_x = 256/360.0 * Math.pow(2, zoom); +			scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); +			locn = pt(lat, lng); +			north_step = pt(lat+tile_size_nmi/60.0, lng); +			if (locn.y - north_step.y > px_size) +				break; +		} while (zoom < 22); +		locn.x = -px_size * Math.floor(locn.x/px_size); +		locn.y = -px_size * Math.floor(locn.y/px_size); +		return locn; +	} + +	public void reset() { +		// nothing +	} + +	private void bgLoadMap(final int i, +			       final File pngfile, final String pngurl) +	{ +		Thread thread = new Thread() { +			public void run() { +				ImageIcon res; +				res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); +				if (res != null) { +					mapTiles[i].loadMap(res); +				} else { +					System.out.printf("# Failed to fetch file %s\n", pngfile); +					System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); +				} +			} +		}; +		thread.start(); +	} + +	public static void prefetchMaps(double lat, double lng, int w, int h) { +		AltosPreferences.init(null); + +		AltosSiteMap asm = new AltosSiteMap(true); +		Point2D.Double c = asm.getBaseLocation(lat, lng); +		Point2D.Double p = new Point2D.Double(); +		Point2D.Double p2; +		int dx = -w/2, dy = -h/2; +		for (int y = dy; y < h+dy; y++) { +			for (int x = dx; x < w+dx; x++) { +				LatLng map_latlng = asm.latlng( +							    -c.x + x*px_size + px_size/2, +							    -c.y + y*px_size + px_size/2); +				File pngfile = asm.MapFile(map_latlng.lat, map_latlng.lng); +				String pngurl = asm.MapURL(map_latlng.lat, map_latlng.lng); +				if (pngfile.exists()) { +					System.out.printf("Already have %s\n", pngfile); +				} else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { +					System.out.printf("Fetched map %s\n", pngfile); +				} else { +					System.out.printf("# Failed to fetch file %s\n", pngfile); +					System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); +				} +			} +		} +	} + +	private void initMaps(double lat, double lng) { +		Point2D.Double c = getBaseLocation(lat, lng); +		Point2D.Double p = new Point2D.Double(); + +		for (int i = 0; i < 9; i++) { +			int x = i%3 - 1, y = i/3 - 1; + +			tileOffset[i] = new Point2D.Double( +				c.x - x*px_size, p.y = c.y - y*px_size); +			LatLng map_latlng = latlng( +						    -tileOffset[i].x+px_size/2, +						    -tileOffset[i].y+px_size/2); + +			File pngfile = MapFile(map_latlng.lat, map_latlng.lng); +			String pngurl = MapURL(map_latlng.lat, map_latlng.lng); +			bgLoadMap(i, pngfile, pngurl); +		} +	} + +	private File MapFile(double lat, double lng) { +		char chlat = lat < 0 ? 'S' : 'N'; +		char chlng = lng < 0 ? 'E' : 'W'; +		if (lat < 0) lat = -lat; +		if (lng < 0) lng = -lng; +		return new File(AltosPreferences.logdir(), +				String.format("map-%c%.6f,%c%.6f-%d.png", +					      chlat, lat, chlng, lng, zoom)); +	} + +	private String MapURL(double lat, double lng) { +		return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size); +	} + +	boolean initialised = false; +	public void show(AltosState state, int crc_errors) { +		// if insufficient gps data, nothing to update +		if (!state.gps.locked) { +			if (state.pad_lat == 0 && state.pad_lon == 0) +				return; +			if (state.gps.nsat < 4) +				return; +		} + +		if (!initialised) { +			initMaps(state.pad_lat, state.pad_lon); +			initialised = true; +		} + +		Point2D.Double pt = pt(state.gps.lat, state.gps.lon); +		for (int x = 0; x < mapTiles.length; x++) { +			mapTiles[x].show(state, crc_errors, +					 translatePoint(pt, tileOffset[x])); +		} +	} + +	private AltosSiteMap(boolean knowWhatYouAreDoing) { +		if (!knowWhatYouAreDoing) { +			throw new RuntimeException("Arggh."); +		} +	} + +	public AltosSiteMap() { +		JComponent comp = new JComponent() { +			GrabNDrag scroller = new GrabNDrag(this); +			{ +				addMouseMotionListener(scroller); +				addMouseListener(scroller); +				setAutoscrolls(true); +			} +		}; + +		GridBagLayout layout = new GridBagLayout(); +		comp.setLayout(layout); + +		GridBagConstraints c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.BOTH; + +		// put some space between the map tiles, debugging only +		// c.insets = new Insets(5, 5, 5, 5); +		for (int x = 0; x < 9; x++) { +			c.gridx = x % 3; +			c.gridy = x / 3; +			mapTiles[x] = new AltosSiteMapTile(px_size); +			layout.setConstraints(mapTiles[x], c); +			comp.add(mapTiles[x]); +		} +		setViewportView(comp); +		setPreferredSize(new Dimension(500,200)); +	} +} + diff --git a/ao-tools/altosui/AltosSiteMapCache.java b/ao-tools/altosui/AltosSiteMapCache.java new file mode 100644 index 00000000..e9dbf8e6 --- /dev/null +++ b/ao-tools/altosui/AltosSiteMapCache.java @@ -0,0 +1,103 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.net.URL; +import java.net.URLConnection; + +public class AltosSiteMapCache extends JLabel { +	public static boolean fetchMap(File file, String url) { +		URL u; +		try { +			u = new URL(url); +		} catch (java.net.MalformedURLException e) { +			return false; +		} + +		byte[] data; +		try { +			URLConnection uc = u.openConnection(); +			int contentLength = uc.getContentLength(); +			InputStream in = new BufferedInputStream(uc.getInputStream()); +			int bytesRead = 0; +			int offset = 0; +			data = new byte[contentLength]; +			while (offset < contentLength) { +				bytesRead = in.read(data, offset, data.length - offset); +				if (bytesRead == -1) +					break; +				offset += bytesRead; +			} +			in.close(); + +			if (offset != contentLength) { +				return false; +			} +		} catch (IOException e) { +			return false; +		} + +		try { +			FileOutputStream out = new FileOutputStream(file); +			out.write(data); +			out.flush(); +			out.close(); +		} catch (FileNotFoundException e) { +			return false; +		} catch (IOException e) { +			if (file.exists()) { +				file.delete(); +			} +			return false; +		} +		return true; +	} + +	public static ImageIcon fetchAndLoadMap(File pngfile, String url) { +		if (!pngfile.exists()) { +			if (!fetchMap(pngfile, url)) { +				return null; +			} +		} +		return loadMap(pngfile, url); +	} + +	public static ImageIcon loadMap(File pngfile, String url) { +		if (!pngfile.exists()) { +			return null; +		} + +		try { +			return new ImageIcon(ImageIO.read(pngfile)); +		} catch (IOException e) { +			System.out.printf("# IO error trying to load %s\n", pngfile); +			return null; +		} +	} +} + diff --git a/ao-tools/altosui/AltosSiteMapTile.java b/ao-tools/altosui/AltosSiteMapTile.java new file mode 100644 index 00000000..8aee86c1 --- /dev/null +++ b/ao-tools/altosui/AltosSiteMapTile.java @@ -0,0 +1,134 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; + +public class AltosSiteMapTile extends JLayeredPane { +	Point2D.Double coord_pt; +	Point2D.Double last_pt; + +	JLabel mapLabel; +	JLabel draw; +	Graphics2D g2d; + +	public void loadMap(ImageIcon icn) { +		mapLabel.setIcon(icn); +	} + +	static Color stateColors[] = { +		Color.WHITE,  // startup +		Color.WHITE,  // idle +		Color.WHITE,  // pad +		Color.RED,    // boost +		Color.PINK,   // fast +		Color.YELLOW, // coast +		Color.CYAN,   // drogue +		Color.BLUE,   // main +		Color.BLACK   // landed +	}; + +	boolean drawn_landed_circle = false; +	boolean drawn_boost_circle = false; +	public void show(AltosState state, int crc_errors, Point2D.Double pt) { +		if (last_pt == null) { +			// setLocation(state.pad_lat, state.pad_lon); +			// loadMap(); +			last_pt = pt; +		} + +		if (pt != last_pt) { +			if (0 <= state.state && state.state < stateColors.length) { +				g2d.setColor(stateColors[state.state]); +			} +			g2d.draw(new Line2D.Double(last_pt, pt)); +		} + +		int px_size = getWidth(); +		if (0 <= pt.x && pt.x < px_size) { +			if (0 <= pt.y && pt.y < px_size) { +				int dx = 500, dy = 250; +				if (state.state > 2) { +					dx = Math.min(200, 20 + (int) Math.abs(last_pt.x - pt.x)); +					dy = Math.min(100, 10 + (int) Math.abs(last_pt.y - pt.y)); +				} +				Rectangle r = new Rectangle((int)pt.x-dx, (int)pt.y-dy, +							    dx*2, dy*2); +				scrollRectToVisible(r); +			} +		} + +		if (state.state == 3 && !drawn_boost_circle) { +			drawn_boost_circle = true; +			g2d.setColor(Color.RED); +			g2d.drawOval((int)last_pt.x-5, (int)last_pt.y-5, 10, 10); +			g2d.drawOval((int)last_pt.x-20, (int)last_pt.y-20, 40, 40); +			g2d.drawOval((int)last_pt.x-35, (int)last_pt.y-35, 70, 70); +		} +		if (state.state == 8 && !drawn_landed_circle) { +			drawn_landed_circle = true; +			g2d.setColor(Color.BLACK); +			g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); +			g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); +			g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); +		} + +		last_pt = pt; +		repaint(); +	} + +	public static Graphics2D fillLabel(JLabel l, Color c, int px_size) { +		BufferedImage img = new BufferedImage(px_size, px_size, +						      BufferedImage.TYPE_INT_ARGB); +		Graphics2D g = img.createGraphics(); +		g.setColor(c); +		g.fillRect(0, 0, px_size, px_size); +		l.setIcon(new ImageIcon(img)); +		return g; +	} + +	public AltosSiteMapTile(int px_size) { +		setPreferredSize(new Dimension(px_size, px_size)); + +		mapLabel = new JLabel(); +		fillLabel(mapLabel, Color.GRAY, px_size); +		mapLabel.setOpaque(true); +		mapLabel.setBounds(0, 0, px_size, px_size); +		add(mapLabel, new Integer(0)); + +		draw = new JLabel(); +		g2d = fillLabel(draw, new Color(127, 127, 127, 0), px_size); +		draw.setBounds(0, 0, px_size, px_size); +		draw.setOpaque(false); + +		add(draw, new Integer(1)); +	} +} + diff --git a/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java index 6bfde014..93a5e0d8 100644 --- a/ao-tools/altosui/AltosUI.java +++ b/ao-tools/altosui/AltosUI.java @@ -353,7 +353,11 @@ public class AltosUI extends JFrame {  	public static void main(final String[] args) {  		int	process = 0;  		/* Handle batch-mode */ -		if (args.length == 2 && args[0].equals("--replay")) { +        if (args.length == 3 && args[0].equals("--fetchmaps")) { +            double lat = Double.parseDouble(args[1]); +            double lon = Double.parseDouble(args[2]); +            AltosSiteMap.prefetchMaps(lat, lon, 5, 5); +        } else if (args.length == 2 && args[0].equals("--replay")) {  			String filename = args[1];  			FileInputStream in;  			try { diff --git a/ao-tools/altosui/GrabNDrag.java b/ao-tools/altosui/GrabNDrag.java new file mode 100644 index 00000000..b44f3fe2 --- /dev/null +++ b/ao-tools/altosui/GrabNDrag.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; + +class GrabNDrag extends MouseInputAdapter { +    private JComponent scroll; +    private Point startPt = new Point(); + +    public GrabNDrag(JComponent parent) { +        scroll = parent; +    } + +    public void mousePressed(MouseEvent e) { +        startPt.setLocation(e.getPoint()); +    } +    public void mouseDragged(MouseEvent e) { +        int xd = e.getX() - startPt.x; +        int yd = e.getY() - startPt.y; + +        Rectangle r = scroll.getVisibleRect(); +        r.x -= xd; +        r.y -= yd; +        scroll.scrollRectToVisible(r); +    } +} diff --git a/ao-tools/altosui/Makefile.am b/ao-tools/altosui/Makefile.am index a603ca8a..93a43b12 100644 --- a/ao-tools/altosui/Makefile.am +++ b/ao-tools/altosui/Makefile.am @@ -10,6 +10,7 @@ CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:../libaltos:$(FREETTS)/  bin_SCRIPTS=altosui  altosui_JAVA = \ +	GrabNDrag.java \  	AltosAscent.java \  	AltosChannelMenu.java \  	AltosConfig.java \ @@ -62,6 +63,9 @@ altosui_JAVA = \  	AltosSerial.java \  	AltosSerialInUseException.java \  	AltosSerialMonitor.java \ +	AltosSiteMap.java \ +	AltosSiteMapCache.java \ +	AltosSiteMapTile.java \  	AltosState.java \  	AltosTelemetry.java \  	AltosTelemetryIterable.java \ | 
