diff options
| -rw-r--r-- | altosui/AltosFlightUI.java | 4 | ||||
| -rw-r--r-- | altosui/AltosGraphUI.java | 4 | ||||
| -rw-r--r-- | altosui/AltosUI.java | 4 | ||||
| -rw-r--r-- | altosuilib/AltosUILatLon.java | 44 | ||||
| -rw-r--r-- | altosuilib/AltosUIMap.java | 246 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapCache.java | 112 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapImage.java | 113 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapLine.java | 116 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapMark.java | 59 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapPath.java | 96 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapPreload.java | 608 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapRectangle.java | 45 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapStore.java | 203 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapStoreListener.java | 22 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapTile.java | 190 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapTileListener.java | 22 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapTransform.java | 106 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapView.java | 462 | ||||
| -rw-r--r-- | altosuilib/AltosUIMapZoomListener.java | 22 | ||||
| -rw-r--r-- | altosuilib/Makefile.am | 24 | ||||
| -rw-r--r-- | telegps/TeleGPS.java | 12 | ||||
| -rw-r--r-- | telegps/TeleGPSGraphUI.java | 4 | 
22 files changed, 2497 insertions, 21 deletions
diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java index baa18686..f2bd70a0 100644 --- a/altosui/AltosFlightUI.java +++ b/altosui/AltosFlightUI.java @@ -37,7 +37,7 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay {  	AltosDescent	descent;  	AltosLanded	landed;  	AltosCompanionInfo	companion; -	AltosSiteMap    sitemap; +	AltosUIMap      sitemap;  	boolean		has_map;  	boolean		has_companion;  	boolean		has_state; @@ -310,7 +310,7 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay {  		has_companion = false;  		has_state = false; -		sitemap = new AltosSiteMap(); +		sitemap = new AltosUIMap();  		has_map = false;  		/* Make the tabbed pane use the rest of the window space */ diff --git a/altosui/AltosGraphUI.java b/altosui/AltosGraphUI.java index 9e8a1939..0df92eae 100644 --- a/altosui/AltosGraphUI.java +++ b/altosui/AltosGraphUI.java @@ -34,7 +34,7 @@ public class AltosGraphUI extends AltosUIFrame  	JTabbedPane		pane;  	AltosGraph		graph;  	AltosUIEnable		enable; -	AltosSiteMap		map; +	AltosUIMap		map;  	AltosState		state;  	AltosGraphDataSet	graphDataSet;  	AltosFlightStats	stats; @@ -46,7 +46,7 @@ public class AltosGraphUI extends AltosUIFrame  		for (AltosState state : states) {  			if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) {  				if (map == null) -					map = new AltosSiteMap(); +					map = new AltosUIMap();  				map.show(state, null);  				has_gps = true;  			} diff --git a/altosui/AltosUI.java b/altosui/AltosUI.java index 302f623f..6137487c 100644 --- a/altosui/AltosUI.java +++ b/altosui/AltosUI.java @@ -280,7 +280,7 @@ public class AltosUI extends AltosUIFrame {  	}  	void LoadMaps() { -		new AltosSiteMapPreload(AltosUI.this); +		new AltosUIMapPreload(AltosUI.this);  	}  	void LaunchController() { @@ -578,7 +578,7 @@ public class AltosUI extends AltosUIFrame {  					} else {  						double lat = Double.parseDouble(args[i+1]);  						double lon = Double.parseDouble(args[i+2]); -						AltosSiteMap.prefetchMaps(lat, lon); +//						AltosSiteMap.prefetchMaps(lat, lon);  						i += 2;  					}  				} else if (args[i].equals("--replay")) diff --git a/altosuilib/AltosUILatLon.java b/altosuilib/AltosUILatLon.java new file mode 100644 index 00000000..688dd58b --- /dev/null +++ b/altosuilib/AltosUILatLon.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUILatLon { +	public double	lat; +	public double	lon; + +	public boolean equals(AltosUILatLon other) { +		if (other == null) +			return false; +		return lat == other.lat && lon == other.lon; +	} + +	public AltosUILatLon(double lat, double lon) { +		this.lat = lat; +		this.lon = lon; +	} +} diff --git a/altosuilib/AltosUIMap.java b/altosuilib/AltosUIMap.java new file mode 100644 index 00000000..fa974d36 --- /dev/null +++ b/altosuilib/AltosUIMap.java @@ -0,0 +1,246 @@ +/* + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMap extends JComponent implements AltosFlightDisplay, AltosUIMapZoomListener { + +	static final int px_size = 512; + +	static final int maptype_hybrid = 0; +	static final int maptype_roadmap = 1; +	static final int maptype_satellite = 2; +	static final int maptype_terrain = 3; +	static final int maptype_default = maptype_hybrid; + +	static final String[] maptype_names = { +		"hybrid", +		"roadmap", +		"satellite", +		"terrain" +	}; + +	public static final String[] maptype_labels = { +		"Hybrid", +		"Roadmap", +		"Satellite", +		"Terrain" +	}; + +	public static final 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 +		Color.BLACK,  // invalid +		Color.CYAN,   // stateless +	}; + +	public void reset() { +		// nothing +	} + +	public void font_size_changed(int font_size) { +		view.set_font(); +	} + +	public void units_changed(boolean imperial_units) { +		repaint(); +	} + +	JLabel	zoom_label; + +	private void set_zoom_label() { +		zoom_label.setText(String.format("Zoom %d", view.zoom() - view.default_zoom)); +	} + +	public void zoom_changed(int zoom) { +		set_zoom_label(); +	} + +	public void set_zoom(int zoom) { +		view.set_zoom(zoom); +	} + +	public int get_zoom() { +		return view.zoom(); +	} + +	public void set_maptype(int type) { +		view.set_maptype(type); +		maptype_combo.setSelectedIndex(type); +	} + +	public void show(AltosState state, AltosListenerState listener_state) { +		view.show(state, listener_state); +	} + +	public void centre(double lat, double lon) { +		view.centre(lat, lon); +	} + +	public void centre(AltosState state) { +		if (!state.gps.locked && state.gps.nsat < 4) +			return; +		centre(state.gps.lat, state.gps.lon); +	} + +	public void add_mark(double lat, double lon, int state) { +		view.add_mark(lat, lon, state); +	} + +	public void clear_marks() { +		view.clear_marks(); +	} + +	AltosUIMapView	view; + +	private GridBagLayout layout = new GridBagLayout(); + +	JComboBox<String>	maptype_combo; + +	public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) { +		view.set_load_params(lat, lon, radius, listener); +	} + +	public boolean all_fetched() { +		return view.all_fetched(); +	} + +	public static void prefetch_maps(double lat, double lon) { +	} + +	public AltosUIMap() { + +		view = new AltosUIMapView(); + +		view.setPreferredSize(new Dimension(500,500)); +		view.setVisible(true); +		view.setEnabled(true); +		view.add_zoom_listener(this); + +		GridBagLayout	my_layout = new GridBagLayout(); + +		setLayout(my_layout); + +		GridBagConstraints c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.BOTH; +		c.gridx = 0; +		c.gridy = 0; +		c.gridwidth = 1; +		c.gridheight = 10; +		c.weightx = 1; +		c.weighty = 1; +		add(view, c); + +		int	y = 0; + +		zoom_label = new JLabel("", JLabel.CENTER); +		set_zoom_label(); + +		c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.gridx = 1; +		c.gridy = y++; +		c.weightx = 0; +		c.weighty = 0; +		add(zoom_label, c); + +		JButton zoom_reset = new JButton("0"); +		zoom_reset.addActionListener(new ActionListener() { +				public void actionPerformed(ActionEvent e) { +					set_zoom(view.default_zoom); +				} +			}); + +		c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.gridx = 1; +		c.gridy = y++; +		c.weightx = 0; +		c.weighty = 0; +		add(zoom_reset, c); + +		JButton zoom_in = new JButton("+"); +		zoom_in.addActionListener(new ActionListener() { +				public void actionPerformed(ActionEvent e) { +					set_zoom(get_zoom() + 1); +				} +			}); + +		c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.gridx = 1; +		c.gridy = y++; +		c.weightx = 0; +		c.weighty = 0; +		add(zoom_in, c); + +		JButton zoom_out = new JButton("-"); +		zoom_out.addActionListener(new ActionListener() { +				public void actionPerformed(ActionEvent e) { +					set_zoom(get_zoom() - 1); +				} +			}); +		c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.gridx = 1; +		c.gridy = y++; +		c.weightx = 0; +		c.weighty = 0; +		add(zoom_out, c); + +		maptype_combo = new JComboBox<String>(maptype_labels); + +		maptype_combo.setEditable(false); +		maptype_combo.setMaximumRowCount(maptype_combo.getItemCount()); +		maptype_combo.addItemListener(new ItemListener() { +				public void itemStateChanged(ItemEvent e) { +					view.set_maptype(maptype_combo.getSelectedIndex()); +				} +			}); + +		c = new GridBagConstraints(); +		c.anchor = GridBagConstraints.CENTER; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.gridx = 1; +		c.gridy = y++; +		c.weightx = 0; +		c.weighty = 0; +		add(maptype_combo, c); +	} +} diff --git a/altosuilib/AltosUIMapCache.java b/altosuilib/AltosUIMapCache.java new file mode 100644 index 00000000..e849da79 --- /dev/null +++ b/altosuilib/AltosUIMapCache.java @@ -0,0 +1,112 @@ +/* + * 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 org.altusmetrum.altosuilib_2; + +import javax.swing.*; +import javax.imageio.ImageIO; +import java.awt.image.*; +import java.awt.*; +import java.io.*; +import java.net.*; + +public class AltosUIMapCache { +	static final int	success = 0; +	static final int	loading = 1; +	static final int	failed = 2; +	static final int	bad_request = 3; +	static final int	forbidden = 4; + +	static private Object fetch_lock = new Object(); + +	static final int		min_cache_size = 9; +	static final int		max_cache_size = 24; + +	static int			cache_size = min_cache_size; + +	static AltosUIMapImage[]	images = new AltosUIMapImage[cache_size]; + +	static Object cache_lock = new Object(); + +	public  static void set_cache_size(int new_size) { +		if (new_size < min_cache_size) +			new_size = min_cache_size; +		if (new_size > max_cache_size) +			new_size = max_cache_size; +		if (new_size == cache_size) +			return; + +		synchronized(cache_lock) { +			AltosUIMapImage[]	new_images = new AltosUIMapImage[new_size]; + +			for (int i = 0; i < cache_size; i++) { +				if (i < new_size) +					new_images[i] = images[i]; +				else if (images[i] != null) +					images[i].flush(); +			} +			images = new_images; +			cache_size = new_size; +		} +	} + +	static long			used; + +	public static Image get(AltosUIMapTile tile, AltosUIMapStore store, int width, int height) { +		int		oldest = -1; +		long		age = used; + +		synchronized(cache_lock) { +			AltosUIMapImage	image = null; +			for (int i = 0; i < cache_size; i++) { +				image = images[i]; + +				if (image == null) { +					oldest = i; +					break; +				} +				if (store.equals(image.store)) { +					image.used = used++; +					return image.image; +				} +				if (image.used < age) { +					oldest = i; +					age = image.used; +				} +			} + +			try { +				image = new AltosUIMapImage(tile, store); +				image.used = used++; +				if (images[oldest] != null) +					images[oldest].flush(); + +				images[oldest] = image; + +				if (image.image == null) +					tile.set_status(loading); +				else +					tile.set_status(success); + +				return image.image; +			} catch (IOException e) { +				tile.set_status(failed); +				return null; +			} +		} +	} +} diff --git a/altosuilib/AltosUIMapImage.java b/altosuilib/AltosUIMapImage.java new file mode 100644 index 00000000..3819d079 --- /dev/null +++ b/altosuilib/AltosUIMapImage.java @@ -0,0 +1,113 @@ +/* + * 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 org.altusmetrum.altosuilib_2; + +import javax.swing.*; +import javax.imageio.ImageIO; +import java.awt.image.*; +import java.awt.*; +import java.io.*; +import java.net.*; + +public class AltosUIMapImage implements AltosUIMapStoreListener { +	static final long google_maps_ratelimit_ms = 1200; +	// Google limits static map queries to 50 per minute per IP, so +	// each query should take at least 1.2 seconds. + +	static final int	success = 0; +	static final int	loading = 1; +	static final int	failed = 2; +	static final int	bad_request = 3; +	static final int	forbidden = 4; + +	static long		forbidden_time; +	static boolean		forbidden_set = false; +	static final long	forbidden_interval = 60l * 1000l * 1000l * 1000l; + +	AltosUIMapTile		tile;		/* Notify when image has been loaded */ +	Image			image; +	AltosUIMapStore		store; +	long			used; + +	class loader implements Runnable { +		public void run() { +			if (image != null) +				tile.notify_image(image); +			try { +				image = ImageIO.read(store.file); +			} catch (Exception ex) { +			} +			if (image == null) +				tile.set_status(failed); +			else +				tile.set_status(success); +			tile.notify_image(image); +		} +	} + +	private void load() { +		loader	l = new loader(); +		Thread	lt = new Thread(l); +		lt.start(); +	} + +	public void flush() { +		if (image != null) { +			image.flush(); +			image = null; +		} +	} + +	public boolean has_map() { +		return store.status() == AltosUIMapStore.success; +	} + +	public synchronized void notify_store(AltosUIMapStore store, int status) { +		switch (status) { +		case AltosUIMapStore.loading: +			break; +		case AltosUIMapStore.success: +			load(); +			break; +		default: +			tile.set_status(status); +			tile.notify_image(null); +		} +	} + +	public AltosUIMapImage(AltosUIMapTile tile, AltosUIMapStore store) throws IOException { +		this.tile = tile; +		this.image = null; +		this.store = store; +		this.used = 0; + +		int status = store.status(); +		switch (status) { +		case AltosUIMapStore.loading: +			store.add_listener(this); +			break; +		case AltosUIMapStore.success: +			load(); +			break; +		default: +			tile.set_status(status); +			tile.notify_image(null); +			break; +		} +	} +} diff --git a/altosuilib/AltosUIMapLine.java b/altosuilib/AltosUIMapLine.java new file mode 100644 index 00000000..e09a2d9f --- /dev/null +++ b/altosuilib/AltosUIMapLine.java @@ -0,0 +1,116 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMapLine { +	AltosUILatLon	start, end; + +	private Font	font = null; + +	public void set_font(Font font) { +		this.font = font; +	} + +	private AltosUILatLon lat_lon(MouseEvent e, AltosUIMapTransform t) { +		return t.screen_lat_lon(e.getPoint()); +	} + +	public void dragged(MouseEvent e, AltosUIMapTransform t) { +		end = lat_lon(e, t); +	} + +	public void pressed(MouseEvent e, AltosUIMapTransform t) { +		start = lat_lon(e, t); +		end = null; +	} + +	private String line_dist() { +		String	format; +		AltosGreatCircle	g = new AltosGreatCircle(start.lat, start.lon, +								 end.lat, end.lon); +		double	distance = g.distance; + +		if (AltosConvert.imperial_units) { +			distance = AltosConvert.meters_to_feet(distance); +			if (distance < 10000) { +				format = "%4.0fft"; +			} else { +				distance /= 5280; +				if (distance < 10) +					format = "%5.3fmi"; +				else if (distance < 100) +					format = "%5.2fmi"; +				else if (distance < 1000) +					format = "%5.1fmi"; +				else +					format = "%5.0fmi"; +			} +		} else { +			if (distance < 10000) { +				format = "%4.0fm"; +			} else { +				distance /= 1000; +				if (distance < 100) +					format = "%5.2fkm"; +				else if (distance < 1000) +					format = "%5.1fkm"; +				else +					format = "%5.0fkm"; +			} +		} +		return String.format(format, distance); +	} + +	public void paint(Graphics2D g, AltosUIMapTransform t) { +		g.setColor(Color.BLUE); + +		if (start == null || end == null) +			return; + +		Line2D.Double line = new Line2D.Double(t.screen(start), +						       t.screen(end)); + +		g.draw(line); + +		String	message = line_dist(); +		g.setFont(font); +		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); +		Rectangle2D	bounds; +		bounds = font.getStringBounds(message, g.getFontRenderContext()); + +		float x = (float) line.x1; +		float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f; + +		if (line.x1 < line.x2) { +			x -= (float) bounds.getWidth() + 2.0f; +		} else { +			x += 2.0f; +		} +		g.drawString(message, x, y); +	} +} diff --git a/altosuilib/AltosUIMapMark.java b/altosuilib/AltosUIMapMark.java new file mode 100644 index 00000000..8c640e5f --- /dev/null +++ b/altosuilib/AltosUIMapMark.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMapMark { + +	AltosUILatLon	lat_lon; +	int		state; + +	static public int stroke_width = 6; + +	public void paint(Graphics2D g, AltosUIMapTransform t) { + +		Point2D.Double pt = t.screen(lat_lon); + +		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, +				   RenderingHints.VALUE_ANTIALIAS_ON); +		g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + +		if (0 <= state && state < AltosUIMap.stateColors.length) +			g.setColor(AltosUIMap.stateColors[state]); +		else +			g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]); + +		g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); +		g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); +		g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); +	} + +	public AltosUIMapMark (double lat, double lon, int state) { +		lat_lon = new AltosUILatLon(lat, lon); +		this.state = state; +	} +} diff --git a/altosuilib/AltosUIMapPath.java b/altosuilib/AltosUIMapPath.java new file mode 100644 index 00000000..ff17be67 --- /dev/null +++ b/altosuilib/AltosUIMapPath.java @@ -0,0 +1,96 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +class PathPoint { +	AltosUILatLon	lat_lon; +	int		state; + +	public PathPoint(AltosUILatLon lat_lon, int state) { +		this.lat_lon = lat_lon; +		this.state = state; +	} + +	public boolean equals(PathPoint other) { +		if (other == null) +			return false; + +		return lat_lon.equals(other.lat_lon) && state == other.state; +	} +} + +public class AltosUIMapPath { + +	LinkedList<PathPoint>	points = new LinkedList<PathPoint>(); +	PathPoint		last_point = null; + +	static public int stroke_width = 6; + +	public void paint(Graphics2D g, AltosUIMapTransform t) { +		Point2D.Double	prev = null; + +		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, +				   RenderingHints.VALUE_ANTIALIAS_ON); +		g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + +		for (PathPoint point : points) { +			Point2D.Double	cur = t.screen(point.lat_lon); +			if (prev != null) { +				Line2D.Double	line = new Line2D.Double (prev, cur); +				Rectangle	bounds = line.getBounds(); + +				if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) { +					if (0 <= point.state && point.state < AltosUIMap.stateColors.length) +						g.setColor(AltosUIMap.stateColors[point.state]); +					else +						g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]); + +					g.draw(line); +				} +			} +			prev = cur; +		} +	} + +	public AltosUIMapRectangle add(double lat, double lon, int state) { +		PathPoint		point = new PathPoint(new AltosUILatLon (lat, lon), state); +		AltosUIMapRectangle	rect = null; + +		if (!point.equals(last_point)) { +			if (last_point != null) +				rect = new AltosUIMapRectangle(last_point.lat_lon, point.lat_lon); +			points.add (point); +			last_point = point; +		} +		return rect; +	} + +	public void clear () { +		points = new LinkedList<PathPoint>(); +	} +} diff --git a/altosuilib/AltosUIMapPreload.java b/altosuilib/AltosUIMapPreload.java new file mode 100644 index 00000000..d702dddf --- /dev/null +++ b/altosuilib/AltosUIMapPreload.java @@ -0,0 +1,608 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.lang.Math; +import java.net.URL; +import java.net.URLConnection; +import org.altusmetrum.altoslib_4.*; + +class AltosUIMapPos extends Box { +	AltosUIFrame	owner; +	JLabel		label; +	JComboBox	hemi; +	JTextField	deg; +	JLabel		deg_label; +	JTextField	min; +	JLabel		min_label; + +	public void set_value(double new_value) { +		double	d, m; +		int	h; + +		h = 0; +		if (new_value < 0) { +			h = 1; +			new_value = -new_value; +		} +		d = Math.floor(new_value); +		deg.setText(String.format("%3.0f", d)); +		m = (new_value - d) * 60.0; +		min.setText(String.format("%7.4f", m)); +		hemi.setSelectedIndex(h); +	} + +	public double get_value() throws NumberFormatException { +		int	h = hemi.getSelectedIndex(); +		String	d_t = deg.getText(); +		String	m_t = min.getText(); +		double 	d, m, v; +		try { +			d = Double.parseDouble(d_t); +		} catch (NumberFormatException ne) { +			JOptionPane.showMessageDialog(owner, +						      String.format("Invalid degrees \"%s\"", +								    d_t), +						      "Invalid number", +						      JOptionPane.ERROR_MESSAGE); +			throw ne; +		} +		try { +			if (m_t.equals("")) +				m = 0; +			else +				m = Double.parseDouble(m_t); +		} catch (NumberFormatException ne) { +			JOptionPane.showMessageDialog(owner, +						      String.format("Invalid minutes \"%s\"", +								    m_t), +						      "Invalid number", +						      JOptionPane.ERROR_MESSAGE); +			throw ne; +		} +		v = d + m/60.0; +		if (h == 1) +			v = -v; +		return v; +	} + +	public AltosUIMapPos(AltosUIFrame in_owner, +			   String label_value, +			   String[] hemi_names, +			   double default_value) { +		super(BoxLayout.X_AXIS); +		owner = in_owner; +		label = new JLabel(label_value); +		hemi = new JComboBox<String>(hemi_names); +		hemi.setEditable(false); +		deg = new JTextField(5); +		deg.setMinimumSize(deg.getPreferredSize()); +		deg.setHorizontalAlignment(JTextField.RIGHT); +		deg_label = new JLabel("°"); +		min = new JTextField(9); +		min.setMinimumSize(min.getPreferredSize()); +		min_label = new JLabel("'"); +		set_value(default_value); +		add(label); +		add(Box.createRigidArea(new Dimension(5, 0))); +		add(hemi); +		add(Box.createRigidArea(new Dimension(5, 0))); +		add(deg); +		add(Box.createRigidArea(new Dimension(5, 0))); +		add(deg_label); +		add(Box.createRigidArea(new Dimension(5, 0))); +		add(min); +		add(Box.createRigidArea(new Dimension(5, 0))); +		add(min_label); +	} +} + +class AltosUISite { +	String	name; +	double	latitude; +	double	longitude; + +	public String toString() { +		return name; +	} + +	public AltosUISite(String in_name, double in_latitude, double in_longitude) { +		name = in_name; +		latitude = in_latitude; +		longitude = in_longitude; +	} + +	public AltosUISite(String line) throws ParseException { +		String[]	elements = line.split(":"); + +		if (elements.length < 3) +			throw new ParseException(String.format("Invalid site line %s", line), 0); + +		name = elements[0]; + +		try { +			latitude = Double.parseDouble(elements[1]); +			longitude = Double.parseDouble(elements[2]); +		} catch (NumberFormatException ne) { +			throw new ParseException(String.format("Invalid site line %s", line), 0); +		} +	} +} + +class AltosUISites extends Thread { +	AltosUIMapPreload	preload; +	URL			url; +	LinkedList<AltosUISite>	sites; + +	void notify_complete() { +		SwingUtilities.invokeLater(new Runnable() { +				public void run() { +					preload.set_sites(); +				} +			}); +	} + +	void add(AltosUISite site) { +		sites.add(site); +	} + +	void add(String line) { +		try { +			add(new AltosUISite(line)); +		} catch (ParseException pe) { +		} +	} + +	public void run() { +		try { +			URLConnection uc = url.openConnection(); +			//int length = uc.getContentLength(); + +			InputStreamReader in_stream = new InputStreamReader(uc.getInputStream(), AltosLib.unicode_set); +			BufferedReader in = new BufferedReader(in_stream); + +			for (;;) { +				String line = in.readLine(); +				if (line == null) +					break; +				add(line); +			} +		} catch (IOException e) { +		} finally { +			notify_complete(); +		} +	} + +	public AltosUISites(AltosUIMapPreload in_preload) { +		sites = new LinkedList<AltosUISite>(); +		preload = in_preload; +		try { +			url = new URL(AltosLib.launch_sites_url); +		} catch (java.net.MalformedURLException e) { +			notify_complete(); +		} +		start(); +	} +} + +public class AltosUIMapPreload extends AltosUIFrame implements ActionListener, ItemListener, AltosUIMapTileListener { +	AltosUIFrame	owner; +	AltosUIMap	map; + +	AltosUIMapPos	lat; +	AltosUIMapPos	lon; + +	JProgressBar	pbar; +	int		pbar_max; +	int		pbar_cur; + +	AltosUISites	sites; +	JLabel		site_list_label; +	JComboBox<AltosUISite>	site_list; + +	JToggleButton	load_button; +	boolean		loading; +	JButton		close_button; + +	JCheckBox[]	maptypes = new JCheckBox[AltosUIMap.maptype_terrain - AltosUIMap.maptype_hybrid + 1]; + +	JComboBox<Integer>	min_zoom; +	JComboBox<Integer>	max_zoom; +	JComboBox<Integer>	radius; + +	Integer[]		zooms = { -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6 }; +	Integer[]		radii = { 1, 2, 3, 4, 5 }; + +	static final String[]	lat_hemi_names = { "N", "S" }; +	static final String[]	lon_hemi_names = { "E", "W" }; + +	class updatePbar implements Runnable { +		String		s; + +		public updatePbar(String in_s) { +			s = in_s; +		} + +		public void run() { +			int 	n = ++pbar_cur; + +			pbar.setMaximum(pbar_max); +			pbar.setValue(n); +			pbar.setString(s); +		} +	} + +	double	latitude, longitude; +	int	min_z; +	int	max_z; +	int	cur_z; +	int	all_types; +	int	cur_type; +	int	r; + +	int	tiles_per_layer; +	int	tiles_loaded; +	int	layers_total; +	int	layers_loaded; + + +	private void do_load() { +		tiles_loaded = 0; +		map.set_zoom(cur_z + AltosUIMapView.default_zoom); +		map.set_maptype(cur_type); +		map.set_load_params(latitude, longitude, r, this); +	} + +	private int next_type(int start) { +		int next_type; +		for (next_type = start; +		     next_type <= AltosUIMap.maptype_terrain && (all_types & (1 << next_type)) == 0; +		     next_type++) +			; +		return next_type; +	} + +	private void next_load() { +		int next_type = next_type(cur_type + 1); + +		if (next_type > AltosUIMap.maptype_terrain) { +			if (cur_z == max_z) { +				return; +			} else { +				cur_z++; +			} +			next_type = next_type(0); +		} +		cur_type = next_type; +		do_load(); +	} + +	private void start_load() { +		cur_z = min_z; +		int ntype = 0; +		all_types = 0; +		for (int t = AltosUIMap.maptype_hybrid; t <= AltosUIMap.maptype_terrain; t++) +			if (maptypes[t].isSelected()) { +				all_types |= (1 << t); +				ntype++; +			} +		if (ntype == 0) { +			all_types |= (1 << AltosUIMap.maptype_hybrid); +			ntype = 1; +		} + +		cur_type = next_type(0); +		tiles_per_layer = (r * 2 + 1) * (r * 2 + 1); +		layers_total = (max_z - min_z + 1) * ntype; +		layers_loaded = 0; +		pbar_max = layers_total * tiles_per_layer; +		pbar_cur = 0; + +		map.clear_marks(); +		map.add_mark(latitude,longitude, AltosLib.ao_flight_boost); +		do_load(); +	} + +	/* AltosUIMapTileListener methods */ + +	public void notify_tile(AltosUIMapTile tile, int status) { +		if (status == AltosUIMapStore.loading) +			return; + +		SwingUtilities.invokeLater(new updatePbar(tile.store.file.toString())); +		++tiles_loaded; +		if (tiles_loaded == tiles_per_layer) { +			++layers_loaded; +			if (layers_loaded == layers_total) { +				SwingUtilities.invokeLater(new Runnable() { +						public void run() { +							pbar.setValue(0); +							pbar.setString(""); +							load_button.setSelected(false); +							loading = false; +						} +					}); +			} else { +				SwingUtilities.invokeLater(new Runnable() { +						public void run() { +							next_load(); +						} +					}); +			} +		} +	} + +	public void set_sites() { +		int	i = 1; +		for (AltosUISite site : sites.sites) { +			site_list.insertItemAt(site, i); +			i++; +		} +	} + +	public void itemStateChanged(ItemEvent e) { +		int		state = e.getStateChange(); + +		if (state == ItemEvent.SELECTED) { +			Object	o = e.getItem(); +			if (o instanceof AltosUISite) { +				AltosUISite	site = (AltosUISite) o; +				lat.set_value(site.latitude); +				lon.set_value(site.longitude); +			} +		} +	} + +	public void actionPerformed(ActionEvent e) { +		String	cmd = e.getActionCommand(); + +		if (cmd.equals("close")) +			setVisible(false); + +		if (cmd.equals("load")) { +			if (!loading) { +				try { +					latitude = lat.get_value(); +					longitude = lon.get_value(); +					min_z = (Integer) min_zoom.getSelectedItem(); +					max_z = (Integer) max_zoom.getSelectedItem(); +					if (max_z < min_z) +						max_z = min_z; +					r = (Integer) radius.getSelectedItem(); +					loading = true; +				} catch (NumberFormatException ne) { +					load_button.setSelected(false); +				} +				start_load(); +			} +		} +	} + +	public AltosUIMapPreload(AltosUIFrame in_owner) { +		System.out.printf("start creating preload ui\n"); + +		owner = in_owner; + +		Container		pane = getContentPane(); +		GridBagConstraints	c = new GridBagConstraints(); +		Insets			i = new Insets(4,4,4,4); + +		setTitle("AltOS Load Maps"); + +		pane.setLayout(new GridBagLayout()); + +		map = new AltosUIMap(); + +		c.fill = GridBagConstraints.BOTH; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 1; + +		c.gridx = 0; +		c.gridy = 0; +		c.gridwidth = 10; +		c.anchor = GridBagConstraints.CENTER; + +		pane.add(map, c); + +		pbar = new JProgressBar(); +		pbar.setMinimum(0); +		pbar.setMaximum(1); +		pbar.setValue(0); +		pbar.setString(""); +		pbar.setStringPainted(true); + +		c.fill = GridBagConstraints.HORIZONTAL; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 0; + +		c.gridx = 0; +		c.gridy = 1; +		c.gridwidth = 10; + +		pane.add(pbar, c); + +		site_list_label = new JLabel ("Known Launch Sites:"); + +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 0; + +		c.gridx = 0; +		c.gridy = 2; +		c.gridwidth = 1; + +		pane.add(site_list_label, c); + +		site_list = new JComboBox<AltosUISite>(new AltosUISite[] { new AltosUISite("Site List", 0, 0) }); +		site_list.addItemListener(this); + +		sites = new AltosUISites(this); + +		c.fill = GridBagConstraints.HORIZONTAL; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 0; + +		c.gridx = 1; +		c.gridy = 2; +		c.gridwidth = 1; + +		pane.add(site_list, c); + +		lat = new AltosUIMapPos(owner, +					"Latitude:", +					lat_hemi_names, +					37.167833333); +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 0; +		c.weighty = 0; + +		c.gridx = 0; +		c.gridy = 3; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; + +		pane.add(lat, c); + +		lon = new AltosUIMapPos(owner, +					"Longitude:", +					lon_hemi_names, +					-97.73975); + +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 0; +		c.weighty = 0; + +		c.gridx = 1; +		c.gridy = 3; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; + +		pane.add(lon, c); + +		load_button = new JToggleButton("Load Map"); +		load_button.addActionListener(this); +		load_button.setActionCommand("load"); + +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 0; + +		c.gridx = 0; +		c.gridy = 4; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; + +		pane.add(load_button, c); + +		close_button = new JButton("Close"); +		close_button.addActionListener(this); +		close_button.setActionCommand("close"); + +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 0; + +		c.gridx = 1; +		c.gridy = 4; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; + +		pane.add(close_button, c); + +		JLabel	types_label = new JLabel("Map Types"); +		c.gridx = 2; +		c.gridwidth = 2; +		c.gridy = 2; +		pane.add(types_label, c); + +		c.gridwidth = 1; + +		for (int type = AltosUIMap.maptype_hybrid; type <= AltosUIMap.maptype_terrain; type++) { +			maptypes[type] = new JCheckBox(AltosUIMap.maptype_labels[type], +						       type == AltosUIMap.maptype_hybrid); +			c.gridx = 2 + (type >> 1); +			c.fill = GridBagConstraints.HORIZONTAL; +			c.gridy = (type & 1) + 3; +			pane.add(maptypes[type], c); +		} + +		JLabel	min_zoom_label = new JLabel("Minimum Zoom"); +		c.gridx = 4; +		c.gridy = 2; +		pane.add(min_zoom_label, c); + +		min_zoom = new JComboBox<Integer>(zooms); +		min_zoom.setSelectedItem(zooms[10]); +		min_zoom.setEditable(false); +		c.gridx = 5; +		c.gridy = 2; +		pane.add(min_zoom, c); + +		JLabel	max_zoom_label = new JLabel("Maximum Zoom"); +		c.gridx = 4; +		c.gridy = 3; +		pane.add(max_zoom_label, c); + +		max_zoom = new JComboBox<Integer>(zooms); +		max_zoom.setSelectedItem(zooms[14]); +		max_zoom.setEditable(false); +		c.gridx = 5; +		c.gridy = 3; +		pane.add(max_zoom, c); + +		JLabel radius_label = new JLabel("Tile Radius"); +		c.gridx = 4; +		c.gridy = 4; +		pane.add(radius_label, c); + +		radius = new JComboBox<Integer>(radii); +		radius.setSelectedItem(radii[4]); +		radius.setEditable(true); +		c.gridx = 5; +		c.gridy = 4; +		pane.add(radius, c); + +		pack(); +		setLocationRelativeTo(owner); +		setVisible(true); + +		System.out.printf("done creating preload ui\n"); +	} +} diff --git a/altosuilib/AltosUIMapRectangle.java b/altosuilib/AltosUIMapRectangle.java new file mode 100644 index 00000000..8a5b16e1 --- /dev/null +++ b/altosuilib/AltosUIMapRectangle.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +public class AltosUIMapRectangle { +	AltosUILatLon	ul, lr; + +	public AltosUIMapRectangle(AltosUILatLon a, AltosUILatLon b) { +		double	ul_lat, ul_lon; +		double	lr_lat, lr_lon; + +		if (a.lat > b.lat) { +			ul_lat = a.lat; +			lr_lat = b.lat; +		} else { +			ul_lat = b.lat; +			lr_lat = a.lat; +		} +		if (a.lon < b.lon) { +			ul_lon = a.lon; +			lr_lon = b.lon; +		} else { +			ul_lon = b.lon; +			lr_lon = a.lon; +		} + +		ul = new AltosUILatLon(ul_lat, ul_lon); +		lr = new AltosUILatLon(lr_lat, lr_lon); +	} +} diff --git a/altosuilib/AltosUIMapStore.java b/altosuilib/AltosUIMapStore.java new file mode 100644 index 00000000..4cecb54f --- /dev/null +++ b/altosuilib/AltosUIMapStore.java @@ -0,0 +1,203 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.io.*; +import java.net.*; +import java.util.*; + +public class AltosUIMapStore { +	String					url; +	File					file; +	LinkedList<AltosUIMapStoreListener>	listeners = new LinkedList<AltosUIMapStoreListener>(); + +	static final int			success = 0; +	static final int			loading = 1; +	static final int			failed = 2; +	static final int			bad_request = 3; +	static final int			forbidden = 4; + +	int					status; + +	public int status() { +		return status; +	} + +	public synchronized void add_listener(AltosUIMapStoreListener listener) { +		if (!listeners.contains(listener)) +			listeners.add(listener); +	} + +	public synchronized void remove_listener(AltosUIMapStoreListener listener) { +		listeners.remove(listener); +	} + +	private synchronized void notify_listeners(int status) { +		this.status = status; +		for (AltosUIMapStoreListener listener : listeners) +			listener.notify_store(this, status); +	} + +	static Object	forbidden_lock = new Object(); +	static long	forbidden_time; +	static boolean	forbidden_set; + +	private int fetch_url() { +		URL u; + +		try { +			u = new URL(url); +		} catch (java.net.MalformedURLException e) { +			return bad_request; +		} + +		byte[] data; +		URLConnection uc = null; +		try { +			uc = u.openConnection(); +			String type = uc.getContentType(); +			int contentLength = uc.getContentLength(); +			if (uc instanceof HttpURLConnection) { +				int response = ((HttpURLConnection) uc).getResponseCode(); +				switch (response) { +				case HttpURLConnection.HTTP_FORBIDDEN: +				case HttpURLConnection.HTTP_PAYMENT_REQUIRED: +				case HttpURLConnection.HTTP_UNAUTHORIZED: +					synchronized (forbidden_lock) { +						forbidden_time = System.nanoTime(); +						forbidden_set = true; +						return forbidden; +					} +				} +			} +			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 failed; + +		} catch (IOException e) { +			return failed; +		} + +		try { +			FileOutputStream out = new FileOutputStream(file); +			out.write(data); +			out.flush(); +			out.close(); +		} catch (FileNotFoundException e) { +			return bad_request; +		} catch (IOException e) { +			if (file.exists()) +				file.delete(); +			return bad_request; +		} +		return success; +	} + +	static Object	fetch_lock = new Object(); + +	static final long	forbidden_interval = 60l * 1000l * 1000l * 1000l; +	static final long 	google_maps_ratelimit_ms = 1200; + +	class loader implements Runnable { + +		public void run() { +			if (file.exists()) { +				notify_listeners(success); +				return; +			} + +			synchronized(forbidden_lock) { +				if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) { +					notify_listeners(forbidden); +					return; +				} +			} + +			int new_status; + +			if (!AltosUIVersion.has_google_maps_api_key()) { +				synchronized (fetch_lock) { +					long startTime = System.nanoTime(); +					new_status = fetch_url(); +					if (new_status == success) { +						long duration_ms = (System.nanoTime() - startTime) / 1000000; +						if (duration_ms < google_maps_ratelimit_ms) { +							try { +								Thread.sleep(google_maps_ratelimit_ms - duration_ms); +							} catch (InterruptedException e) { +								Thread.currentThread().interrupt(); +							} +						} +					} +				} +			} else { +				new_status = fetch_url(); +			} +			notify_listeners(new_status); +		} +	} + +	private void load() { +		loader	l = new loader(); +		Thread	lt = new Thread(l); +		lt.start(); +	} + +	private AltosUIMapStore (String url, File file) { +		this.url = url; +		this.file = file; + +		if (file.exists()) +			status = success; +		else { +			status = loading; +			load(); +		} +	} + +	public boolean equals(AltosUIMapStore other) { +		return url.equals(other.url); +	} + +	static HashMap<String,AltosUIMapStore> stores = new HashMap<String,AltosUIMapStore>(); + +	public static AltosUIMapStore get(String url, File file) { +		AltosUIMapStore	store; +		synchronized(stores) { +			if (stores.containsKey(url)) { +				store = stores.get(url); +			} else { +				store = new AltosUIMapStore(url, file); +				stores.put(url, store); +			} +		} +		return store; +	} + +} diff --git a/altosuilib/AltosUIMapStoreListener.java b/altosuilib/AltosUIMapStoreListener.java new file mode 100644 index 00000000..91aff00c --- /dev/null +++ b/altosuilib/AltosUIMapStoreListener.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +public interface AltosUIMapStoreListener { +	abstract void notify_store(AltosUIMapStore store, int status); +} diff --git a/altosuilib/AltosUIMapTile.java b/altosuilib/AltosUIMapTile.java new file mode 100644 index 00000000..6fbcdb4b --- /dev/null +++ b/altosuilib/AltosUIMapTile.java @@ -0,0 +1,190 @@ +/* + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.image.*; +import javax.swing.*; +import javax.imageio.*; +import java.awt.geom.*; +import java.io.*; +import java.util.*; +import java.awt.RenderingHints.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMapTile { +	AltosUIMapTileListener	listener; +	AltosUILatLon	upper_left, center; +	int		px_size; +	int		zoom; +	int		maptype; +	AltosUIMapStore	store; +	int		status; + +	private File map_file() { +		double lat = center.lat; +		double lon = center.lon; +		char chlat = lat < 0 ? 'S' : 'N'; +		char chlon = lon < 0 ? 'W' : 'E'; + +		if (lat < 0) lat = -lat; +		if (lon < 0) lon = -lon; +		String maptype_string = String.format("%s-", AltosUIMap.maptype_names[maptype]); +		String format_string; +		if (maptype == AltosUIMap.maptype_hybrid || maptype == AltosUIMap.maptype_satellite || maptype == AltosUIMap.maptype_terrain) +			format_string = "jpg"; +		else +			format_string = "png"; +		return new File(AltosUIPreferences.mapdir(), +				String.format("map-%c%.6f,%c%.6f-%s%d.%s", +					      chlat, lat, chlon, lon, maptype_string, zoom, format_string)); +	} + +	private String map_url() { +		String format_string; +		if (maptype == AltosUIMap.maptype_hybrid || maptype == AltosUIMap.maptype_satellite || maptype == AltosUIMap.maptype_terrain) +			format_string = "jpg"; +		else +			format_string = "png32"; + +		if (AltosUIVersion.has_google_maps_api_key()) +			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s&key=%s", +					     center.lat, center.lon, zoom, px_size, px_size, AltosUIMap.maptype_names[maptype], format_string, AltosUIVersion.google_maps_api_key); +		else +			return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s", +					     center.lat, center.lon, zoom, px_size, px_size, AltosUIMap.maptype_names[maptype], format_string); +	} +	private Font	font = null; + +	public void set_font(Font font) { +		this.font = font; +	} + +	int	painting_serial; +	int	painted_serial; + +	Image	image; + +	public void paint_graphics(Graphics2D g2d, AltosUIMapTransform t, int serial) { +		if (serial < painted_serial) +			return; + +		Point2D.Double	point_double = t.screen(upper_left); +		Point		point = new Point((int) (point_double.x + 0.5), +						  (int) (point_double.y + 0.5)); + +		painted_serial = serial; + +		if (!g2d.hitClip(point.x, point.y, px_size, px_size)) +			return; + +		if (image != null) { +			g2d.drawImage(image, point.x, point.y, null); +			image = null; +		} else { +			g2d.setColor(Color.GRAY); +			g2d.fillRect(point.x, point.y, px_size, px_size); + +			if (t.has_location()) { +				String	message = null; +				switch (status) { +				case AltosUIMapCache.loading: +					message = "Loading..."; +					break; +				case AltosUIMapCache.bad_request: +					message = "Internal error"; +					break; +				case AltosUIMapCache.failed: +					message = "Network error, check connection"; +					break; +				case AltosUIMapCache.forbidden: +					message = "Too many requests, try later"; +					break; +				} +				if (message != null && font != null) { +					g2d.setFont(font); +					g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); +					Rectangle2D bounds = font.getStringBounds(message, g2d.getFontRenderContext()); + +					float x = px_size / 2.0f; +					float y = px_size / 2.0f; +					x = x - (float) bounds.getWidth() / 2.0f; +					y = y + (float) bounds.getHeight() / 2.0f; +					g2d.setColor(Color.BLACK); +					g2d.drawString(message, (float) point_double.x + x, (float) point_double.y + y); +				} +			} +		} +	} + +	public void set_status(int status) { +		this.status = status; +		listener.notify_tile(this, status); +	} + +	public void notify_image(Image image) { +		listener.notify_tile(this, status); +	} + +	public void paint(Graphics g, AltosUIMapTransform t) { +		Graphics2D		g2d = (Graphics2D) g; +		boolean			queued = false; + +		Point2D.Double	point = t.screen(upper_left); + +		if (!g.hitClip((int) (point.x + 0.5), (int) (point.y + 0.5), px_size, px_size)) +			return; + +		++painting_serial; + +		if (image == null && t.has_location()) +			image = AltosUIMapCache.get(this, store, px_size, px_size); + +		paint_graphics(g2d, t, painting_serial); +	} + +	public int store_status() { +		return store.status(); +	} + +	public void add_store_listener(AltosUIMapStoreListener listener) { +		store.add_listener(listener); +	} + +	public void remove_store_listener(AltosUIMapStoreListener listener) { +		store.remove_listener(listener); +	} + +	public AltosUIMapTile(AltosUIMapTileListener listener, AltosUILatLon upper_left, AltosUILatLon center, int zoom, int maptype, int px_size, Font font) { +		this.listener = listener; +		this.upper_left = upper_left; + +		while (center.lon < -180.0) +			center.lon += 360.0; +		while (center.lon > 180.0) +			center.lon -= 360.0; + +		this.center = center; +		this.zoom = zoom; +		this.maptype = maptype; +		this.px_size = px_size; +		this.font = font; +		status = AltosUIMapCache.loading; +		store = AltosUIMapStore.get(map_url(), map_file()); +	} +} diff --git a/altosuilib/AltosUIMapTileListener.java b/altosuilib/AltosUIMapTileListener.java new file mode 100644 index 00000000..4cc3ff2f --- /dev/null +++ b/altosuilib/AltosUIMapTileListener.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +public interface AltosUIMapTileListener { +	abstract public void notify_tile(AltosUIMapTile tile, int status); +} diff --git a/altosuilib/AltosUIMapTransform.java b/altosuilib/AltosUIMapTransform.java new file mode 100644 index 00000000..e6f1ffe3 --- /dev/null +++ b/altosuilib/AltosUIMapTransform.java @@ -0,0 +1,106 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMapTransform { + +	double	scale_x, scale_y; + +	double	offset_x, offset_y; + +	public AltosUILatLon lat_lon (Point2D.Double point) { +		double lat, lon; +		double rads; + +		lon = point.x/scale_x; +		rads = 2 * Math.atan(Math.exp(-point.y/scale_y)); +		lat = Math.toDegrees(rads - Math.PI/2); + +		return new AltosUILatLon(lat,lon); +	} + +	public Point2D.Double screen_point(Point screen) { +		return new Point2D.Double(screen.x + offset_x, screen.y + offset_y); +	} + +	public AltosUILatLon screen_lat_lon(Point screen) { +		return lat_lon(screen_point(screen)); +	} + +	public Point2D.Double point(AltosUILatLon lat_lon) { +		double x, y; +		double e; + +		x = lat_lon.lon * scale_x; + +		e = Math.sin(Math.toRadians(lat_lon.lat)); +		e = Math.max(e,-(1-1.0E-15)); +		e = Math.min(e,  1-1.0E-15 ); + +		y = 0.5*Math.log((1+e)/(1-e))*-scale_y; + +		return new Point2D.Double(x, y); +	} + +	public Point2D.Double screen(Point2D.Double point) { +		return new Point2D.Double(point.x - offset_x, point.y - offset_y); +	} + +	public Point screen(Point point) { +		return new Point((int) (point.x - offset_x + 0.5), +				 (int) (point.y - offset_y + 0.5)); +	} + +	public Rectangle screen(AltosUIMapRectangle map_rect) { +		Point2D.Double	ul = screen(map_rect.ul); +		Point2D.Double	lr = screen(map_rect.lr); + +		return new Rectangle((int) ul.x, (int) ul.y, (int) (lr.x - ul.x), (int) (lr.y - ul.y)); +	} + +	public Point2D.Double screen(AltosUILatLon lat_lon) { +		return screen(point(lat_lon)); +	} + +	private boolean has_location; + +	public boolean has_location() { +		return has_location; +	} + +	public AltosUIMapTransform(int width, int height, int zoom, AltosUILatLon centre_lat_lon) { +		scale_x = 256/360.0 * Math.pow(2, zoom); +		scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); + +		Point2D.Double centre_pt = point(centre_lat_lon); + +		has_location = (centre_lat_lon.lat != 0 || centre_lat_lon.lon != 0); +		offset_x = centre_pt.x - width / 2.0; +		offset_y = centre_pt.y - height / 2.0; +	} +} diff --git a/altosuilib/AltosUIMapView.java b/altosuilib/AltosUIMapView.java new file mode 100644 index 00000000..c558118b --- /dev/null +++ b/altosuilib/AltosUIMapView.java @@ -0,0 +1,462 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import javax.swing.*; +import java.io.*; +import java.lang.*; +import java.awt.geom.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosUIMapView extends Canvas implements MouseMotionListener, MouseListener, MouseWheelListener, ComponentListener, AltosUIMapTileListener, AltosUIMapStoreListener { + +	AltosUIMapPath	path = new AltosUIMapPath(); + +	AltosUIMapLine	line = new AltosUIMapLine(); + +	LinkedList<AltosUIMapMark> marks = new LinkedList<AltosUIMapMark>(); + +	LinkedList<AltosUIMapZoomListener> zoom_listeners = new LinkedList<AltosUIMapZoomListener>(); + +	boolean		have_boost = false; +	boolean		have_landed = false; + +	ConcurrentHashMap<Point,AltosUIMapTile> tiles = new ConcurrentHashMap<Point,AltosUIMapTile>(); + +	static final int default_zoom = 15; +	static final int min_zoom = 3; +	static final int max_zoom = 21; +	static final int px_size = 512; + +	int		load_radius; +	AltosUILatLon	load_centre = null; +	AltosUIMapTileListener	load_listener; + +	int 		zoom = default_zoom; +	int		maptype = AltosUIMap.maptype_default; + +	long		user_input_time; + +	/* Milliseconds to wait after user action before auto-scrolling +	 */ +	static final long auto_scroll_delay = 20 * 1000; + +	AltosUIMapTransform	transform; +	AltosUILatLon		centre; + +	public void set_font() { +		line.set_font(AltosUILib.value_font); +		for (AltosUIMapTile tile : tiles.values()) +			tile.set_font(AltosUILib.value_font); +	} + +	private boolean is_drag_event(MouseEvent e) { +		return e.getModifiers() == InputEvent.BUTTON1_MASK; +	} + +	Point	drag_start; + +	private void drag(MouseEvent e) { +		if (drag_start == null) +			return; + +		int dx = e.getPoint().x - drag_start.x; +		int dy = e.getPoint().y - drag_start.y; + +		AltosUILatLon	new_centre = transform.screen_lat_lon(new Point(getWidth() / 2 - dx, getHeight() / 2 - dy)); +		centre (new_centre.lat, new_centre.lon); +		drag_start = e.getPoint(); +	} + +	private void drag_start(MouseEvent e) { +		drag_start = e.getPoint(); +	} + +	private void notice_user_input() { +		user_input_time = System.currentTimeMillis(); +	} + +	private boolean recent_user_input() { +		return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay; +	} + +	/* MouseMotionListener methods */ + +	public void mouseDragged(MouseEvent e) { +		notice_user_input(); +		if (is_drag_event(e)) +			drag(e); +		else { +			line.dragged(e, transform); +			repaint(); +		} +	} + +	public void mouseMoved(MouseEvent e) { +	} + +	/* MouseListener methods */ +	public void mouseClicked(MouseEvent e) { +	} + +	public void mouseEntered(MouseEvent e) { +	} + +	public void mouseExited(MouseEvent e) { +	} + +	public void mousePressed(MouseEvent e) { +		notice_user_input(); +		if (is_drag_event(e)) +			drag_start(e); +		else +			line.pressed(e, transform); +	} + +	public void mouseReleased(MouseEvent e) { +	} + +	/* MouseWheelListener methods */ + +	public void mouseWheelMoved(MouseWheelEvent e) { +		int	zoom_change = e.getWheelRotation(); + +		notice_user_input(); +		AltosUILatLon	mouse_lat_lon = transform.screen_lat_lon(e.getPoint()); +		set_zoom(zoom() - zoom_change); + +		Point2D.Double	new_mouse = transform.screen(mouse_lat_lon); + +		int	dx = getWidth()/2 - e.getPoint().x; +		int	dy = getHeight()/2 - e.getPoint().y; + +		AltosUILatLon	new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy)); + +		centre(new_centre.lat, new_centre.lon); +	} + +	/* ComponentListener methods */ + +	public void componentHidden(ComponentEvent e) { +	} + +	public void componentMoved(ComponentEvent e) { +	} + +	public void componentResized(ComponentEvent e) { +		set_transform(); +	} + +	public void componentShown(ComponentEvent e) { +		set_transform(); +	} + +	public void repaint(Rectangle r, int pad) { +		repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2); +	} + +	public void repaint(AltosUIMapRectangle rect, int pad) { +		repaint (transform.screen(rect), pad); +	} + +	private boolean far_from_centre(AltosUILatLon lat_lon) { + +		if (centre == null || transform == null) +			return true; + +		Point2D.Double	screen = transform.screen(lat_lon); + +		int		width = getWidth(); +		int		dx = Math.abs ((int) screen.x - width/2); + +		if (dx > width / 4) +			return true; + +		int		height = getHeight(); +		int		dy = Math.abs ((int) screen.y - height/2); + +		if (dy > height / 4) +			return true; + +		return false; +	} + +	public void show(AltosState state, AltosListenerState listener_state) { + +		/* If insufficient gps data, nothing to update +		 */ +		AltosGPS	gps = state.gps; + +		if (gps == null) +			return; + +		if (!gps.locked && gps.nsat < 4) +			return; + +		AltosUIMapRectangle	damage = path.add(gps.lat, gps.lon, state.state); + +		switch (state.state) { +		case AltosLib.ao_flight_boost: +			if (!have_boost) { +				add_mark(gps.lat, gps.lon, state.state); +				have_boost = true; +			} +			break; +		case AltosLib.ao_flight_landed: +			if (!have_landed) { +				add_mark(gps.lat, gps.lon, state.state); +				have_landed = true; +			} +			break; +		} + +		if (damage != null) +			repaint(damage, AltosUIMapPath.stroke_width); +		maybe_centre(gps.lat, gps.lon); +	} + +	private void set_transform() { +		Rectangle	bounds = getBounds(); + +		transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre); +		repaint(); +	} + +	public boolean set_zoom(int zoom) { +		if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) { +			this.zoom = zoom; +			tiles.clear(); +			set_transform(); + +			for (AltosUIMapZoomListener listener : zoom_listeners) +				listener.zoom_changed(this.zoom); + +			return true; +		} +		return false; +	} + +	public void add_zoom_listener(AltosUIMapZoomListener listener) { +		if (!zoom_listeners.contains(listener)) +			zoom_listeners.add(listener); +	} + +	public void remove_zoom_listener(AltosUIMapZoomListener listener) { +		zoom_listeners.remove(listener); +	} + +	public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) { +		load_centre = new AltosUILatLon(lat, lon); +		load_radius = radius; +		load_listener = listener; +		centre(lat, lon); +		make_tiles(); +		for (AltosUIMapTile tile : tiles.values()) { +			tile.add_store_listener(this); +			if (tile.store_status() != AltosUIMapStore.loading) +				listener.notify_tile(tile, tile.store_status()); +		} +		repaint(); +	} + +	public boolean all_fetched() { +		for (AltosUIMapTile tile : tiles.values()) { +			if (tile.store_status() == AltosUIMapStore.loading) +				return false; +		} +		return true; +	} + +	public boolean set_maptype(int maptype) { +		if (maptype != this.maptype) { +			this.maptype = maptype; +			tiles.clear(); +			repaint(); +			return true; +		} +		return false; +	} + +	public int get_maptype() { +		return maptype; +	} + +	public int zoom() { +		return zoom; +	} + +	public void centre(AltosUILatLon lat_lon) { +		centre = lat_lon; +		set_transform(); +	} + +	public void centre(double lat, double lon) { +		centre(new AltosUILatLon(lat, lon)); +	} + +	public void maybe_centre(double lat, double lon) { +		AltosUILatLon	lat_lon = new AltosUILatLon(lat, lon); +		if (centre == null || (!recent_user_input() && far_from_centre(lat_lon))) +			centre(lat_lon); +	} + +	private VolatileImage create_back_buffer() { +		return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight()); +	} + +	private Point floor(Point2D.Double point) { +		return new Point ((int) Math.floor(point.x / px_size) * px_size, +				  (int) Math.floor(point.y / px_size) * px_size); +	} + +	private Point ceil(Point2D.Double point) { +		return new Point ((int) Math.ceil(point.x / px_size) * px_size, +				  (int) Math.ceil(point.y / px_size) * px_size); +	} + +	private void make_tiles() { +		Point	upper_left; +		Point	lower_right; + +		if (load_centre != null) { +			Point centre = floor(transform.point(load_centre)); + +			upper_left = new Point(centre.x - load_radius * px_size, +					       centre.y - load_radius * px_size); +			lower_right = new Point(centre.x + load_radius * px_size, +					       centre.y + load_radius * px_size); +		} else { +			upper_left = floor(transform.screen_point(new Point(0, 0))); +			lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight()))); +		} +		LinkedList<Point> to_remove = new LinkedList<Point>(); + +		for (Point point : tiles.keySet()) { +			if (point.x < upper_left.x || lower_right.x < point.x || +			    point.y < upper_left.y || lower_right.y < point.y) { +				to_remove.add(point); +			} +		} + +		for (Point point : to_remove) +			tiles.remove(point); + +		AltosUIMapCache.set_cache_size(((lower_right.y - upper_left.y) / px_size + 1) * ((lower_right.x - upper_left.x) / px_size + 1)); +		for (int y = upper_left.y; y <= lower_right.y; y += px_size) { +			for (int x = upper_left.x; x <= lower_right.x; x += px_size) { +				Point point = new Point(x, y); + +				if (!tiles.containsKey(point)) { +					AltosUILatLon	ul = transform.lat_lon(new Point2D.Double(x, y)); +					AltosUILatLon	center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2)); +					AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype, +										 px_size, AltosUILib.value_font); +					tiles.put(point, tile); +				} +			} +		} +	} + +	/* AltosUIMapTileListener methods */ +	public void notify_tile(AltosUIMapTile tile, int status) { +		for (Point point : tiles.keySet()) { +			if (tile == tiles.get(point)) { +				Point	screen = transform.screen(point); +				repaint(screen.x, screen.y, px_size, px_size); +			} +		} +	} + +	/* AltosUIMapStoreListener methods */ +	public void notify_store(AltosUIMapStore store, int status) { +		if (load_listener != null) { +			for (AltosUIMapTile tile : tiles.values()) +				if (store.equals(tile.store)) +					load_listener.notify_tile(tile, status); +		} +	} + +	private void do_paint(Graphics g) { +		Graphics2D	g2d = (Graphics2D) g; + +		make_tiles(); + +		for (AltosUIMapTile tile : tiles.values()) +			tile.paint(g2d, transform); + +		synchronized(marks) { +			for (AltosUIMapMark mark : marks) +				mark.paint(g2d, transform); +		} + +		path.paint(g2d, transform); + +		line.paint(g2d, transform); +	} + +	public void paint(Graphics g) { + +		VolatileImage	back_buffer = create_back_buffer(); +		do { +			GraphicsConfiguration gc = getGraphicsConfiguration(); +			int code = back_buffer.validate(gc); +			if (code == VolatileImage.IMAGE_INCOMPATIBLE) +				back_buffer = create_back_buffer(); + +			Graphics g_back = back_buffer.getGraphics(); +			g_back.setClip(g.getClip()); +			do_paint(g_back); +			g_back.dispose(); + +			g.drawImage(back_buffer, 0, 0, this); +		} while (back_buffer.contentsLost()); +		back_buffer.flush(); +	} + +	public void update(Graphics g) { +		paint(g); +	} + +	public void add_mark(double lat, double lon, int state) { +		synchronized(marks) { +			marks.add(new AltosUIMapMark(lat, lon, state)); +		} +		repaint(); +	} + +	public void clear_marks() { +		synchronized(marks) { +			marks.clear(); +		} +	} + +	public AltosUIMapView() { +		centre(0, 0); + +		addComponentListener(this); +		addMouseMotionListener(this); +		addMouseListener(this); +		addMouseWheelListener(this); +		set_font(); +	} +} diff --git a/altosuilib/AltosUIMapZoomListener.java b/altosuilib/AltosUIMapZoomListener.java new file mode 100644 index 00000000..02e8bb51 --- /dev/null +++ b/altosuilib/AltosUIMapZoomListener.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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 org.altusmetrum.altosuilib_2; + +public interface AltosUIMapZoomListener { +	abstract public void zoom_changed(int zoom); +} diff --git a/altosuilib/Makefile.am b/altosuilib/Makefile.am index b90669ce..466cfd99 100644 --- a/altosuilib/Makefile.am +++ b/altosuilib/Makefile.am @@ -33,11 +33,6 @@ altosuilib_JAVA = \  	AltosUISeries.java \  	AltosUIVersion.java \  	AltosUSBDevice.java \ -	AltosSiteMap.java \ -	AltosSiteMapCache.java \ -	AltosSiteMapPreload.java \ -	AltosSiteMapTile.java \ -	AltosSiteMapImage.java \  	AltosVoice.java \  	AltosDisplayThread.java \  	AltosDeviceUIDialog.java \ @@ -65,8 +60,23 @@ altosuilib_JAVA = \  	AltosBTDevice.java \  	AltosBTDeviceIterator.java \  	AltosBTManage.java \ -	AltosBTKnown.java - +	AltosBTKnown.java \ +	AltosUIMap.java \ +	AltosUIMapView.java \ +	AltosUIMapLine.java \ +	AltosUIMapMark.java \ +	AltosUIMapPath.java \ +	AltosUIMapTile.java \ +	AltosUIMapCache.java \ +	AltosUIMapImage.java \ +	AltosUIMapTransform.java \ +	AltosUIMapRectangle.java \ +	AltosUIMapZoomListener.java \ +	AltosUIMapTileListener.java \ +	AltosUIMapPreload.java \ +	AltosUIMapStore.java \ +	AltosUIMapStoreListener.java \ +	AltosUILatLon.java  JAR=altosuilib_$(ALTOSUILIB_VERSION).jar diff --git a/telegps/TeleGPS.java b/telegps/TeleGPS.java index c61b245e..2503d53e 100644 --- a/telegps/TeleGPS.java +++ b/telegps/TeleGPS.java @@ -67,7 +67,7 @@ public class TeleGPS  	JTabbedPane		pane; -	AltosSiteMap   		sitemap; +	AltosUIMap   		map;  	TeleGPSInfo		gps_info;  	AltosInfoTable		info_table; @@ -167,7 +167,7 @@ public class TeleGPS  	}  	void load_maps() { -		new AltosSiteMapPreload(this); +		new AltosUIMapPreload(this);  	}  	void disconnect() { @@ -438,9 +438,9 @@ public class TeleGPS  		c.gridwidth = 2;  		bag.add(pane, c); -		sitemap = new AltosSiteMap(); -		pane.add("Site Map", sitemap); -		displays.add(sitemap); +		map = new AltosUIMap(); +		pane.add("Map", map); +		displays.add(map);  		gps_info = new TeleGPSInfo();  		pane.add("Info", gps_info); @@ -578,7 +578,7 @@ public class TeleGPS  				} else {  					double lat = Double.parseDouble(args[i+1]);  					double lon = Double.parseDouble(args[i+2]); -					AltosSiteMap.prefetchMaps(lat, lon); +					AltosUIMap.prefetch_maps(lat, lon);  					i += 2;  				}  			} else if (args[i].equals("--replay")) diff --git a/telegps/TeleGPSGraphUI.java b/telegps/TeleGPSGraphUI.java index b7fc4caa..fbc9657e 100644 --- a/telegps/TeleGPSGraphUI.java +++ b/telegps/TeleGPSGraphUI.java @@ -38,7 +38,7 @@ public class TeleGPSGraphUI extends AltosUIFrame  	JTabbedPane		pane;  	AltosGraph		graph;  	AltosUIEnable		enable; -	AltosSiteMap		map; +	AltosUIMap		map;  	AltosState		state;  	AltosFlightStats	stats;  	AltosGraphDataSet	graphDataSet; @@ -69,7 +69,7 @@ public class TeleGPSGraphUI extends AltosUIFrame  		graph = new AltosGraph(enable, stats, graphDataSet);  		statsTable = new AltosFlightStatsTable(stats); -		map = new AltosSiteMap(); +		map = new AltosUIMap();  		pane.add("Flight Graph", graph.panel);  		pane.add("Configure Graph", enable);  | 
