From: Anthony Towns Date: Sat, 20 Nov 2010 22:18:39 +0000 (+1000) Subject: AltosSiteMap: major refactoring X-Git-Tag: debian/0.7.1+117+ge7954c8~8^2~4 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=72f5e05f9f0055f2cef8b840812f090556c94338 AltosSiteMap: major refactoring --- diff --git a/ao-tools/altosui/AltosSiteMap.java b/ao-tools/altosui/AltosSiteMap.java index 1db83959..a8b66dac 100644 --- a/ao-tools/altosui/AltosSiteMap.java +++ b/ao-tools/altosui/AltosSiteMap.java @@ -33,36 +33,206 @@ 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 = 1.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 } - public void show(AltosState state, int crc_errors) { - for (int x = 0; x < mapTiles.length; x++) { - mapTiles[x].show(state, crc_errors); + + 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); + } + } } } - - AltosSiteMapTile [] mapTiles = new AltosSiteMapTile[9]; - class GrabNDrag extends MouseInputAdapter { - private JComponent scroll; - private Point startPt = new Point(); + 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; - public GrabNDrag(JComponent parent) { - scroll = parent; + 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_ready) { + if (state.pad_lat == 0 && state.pad_lon == 0) + return; + if (state.ngps < 3) + return; + } + + if (!initialised) { + initMaps(state.pad_lat, state.pad_lon); + initialised = true; } - public void mousePressed(MouseEvent e) { - startPt.setLocation(e.getPoint()); + 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])); } - 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); + } + + private AltosSiteMap(boolean knowWhatYouAreDoing) { + if (!knowWhatYouAreDoing) { + throw new RuntimeException("Arggh."); } } @@ -83,9 +253,11 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { 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((x%3)-1, (x/3)-1); + mapTiles[x] = new AltosSiteMapTile(px_size); layout.setConstraints(mapTiles[x], c); comp.add(mapTiles[x]); } diff --git a/ao-tools/altosui/AltosSiteMapCache.java b/ao-tools/altosui/AltosSiteMapCache.java new file mode 100644 index 00000000..dbdcbf65 --- /dev/null +++ b/ao-tools/altosui/AltosSiteMapCache.java @@ -0,0 +1,103 @@ +/* + * Copyright © 2010 Anthony Towns + * + * 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/AltosSiteMapLabel.java b/ao-tools/altosui/AltosSiteMapLabel.java deleted file mode 100644 index 1a371c5b..00000000 --- a/ao-tools/altosui/AltosSiteMapLabel.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright © 2010 Anthony Towns - * - * 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 AltosSiteMapLabel 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 void fetchAndLoadMap(final File pngfile, final String url) { - System.out.printf("# Trying to fetch %s...\n", pngfile); - - Thread thread = new Thread() { - public void run() { - try { - if (fetchMap(pngfile, url)) { - setIcon(new ImageIcon(ImageIO.read(pngfile))); - } - } catch (Exception e) { - System.out.printf("# Failed to fetch file %s\n", pngfile); - System.out.printf(" wget -O '%s' ''\n", pngfile, url); - } - } - }; - thread.start(); - } - - public void fetchMap(double lat, double lng, int zoom, int px_size) { - File pngfile = MapFile(lat, lng, zoom); - String url = MapURL(lat, lng, zoom, px_size); - - if (!pngfile.exists()) { - fetchMap(pngfile, url); - } - } - - public void loadMap(double lat, double lng, int zoom, int px_size) { - File pngfile = MapFile(lat, lng, zoom); - String url = MapURL(lat, lng, zoom, px_size); - - if (!pngfile.exists()) { - fetchAndLoadMap(pngfile, url); - return; - } - - try { - setIcon(new ImageIcon(ImageIO.read(pngfile))); - return; - } catch (IOException e) { - System.out.printf("# IO error trying to load %s\n", pngfile); - return; - } - } - - private static File MapFile(double lat, double lng, int zoom) { - 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 static String MapURL(double lat, double lng, - int zoom, int px_size) - { - 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); - } -} - diff --git a/ao-tools/altosui/AltosSiteMapTile.java b/ao-tools/altosui/AltosSiteMapTile.java index 6035a794..de28fc8b 100644 --- a/ao-tools/altosui/AltosSiteMapTile.java +++ b/ao-tools/altosui/AltosSiteMapTile.java @@ -32,96 +32,15 @@ import java.awt.geom.Point2D; import java.awt.geom.Line2D; public class AltosSiteMapTile extends JLayeredPane { - int zoom; - double scale_x, scale_y; Point2D.Double coord_pt; Point2D.Double last_pt; - AltosSiteMapLabel mapLabel; + JLabel mapLabel; JLabel draw; Graphics2D g2d; - int off_x; - int off_y; - - static final int px_size = 512; - - private void loadMap() { - Point2D.Double map_latlng = latlng(px_size/2, px_size/2); - mapLabel.loadMap(map_latlng.x, map_latlng.y, zoom, px_size); - } - - private boolean setLocation(double lat, double lng) { - Point2D.Double north_step; - double step_nm = 0.5; - for (zoom = 3; zoom < 22; zoom++) { - coord_pt = pt(lat, lng, new Point2D.Double(0,0), zoom); - north_step = pt(lat+step_nm/60.0, lng, - new Point2D.Double(0,0), zoom); - if (coord_pt.y - north_step.y > px_size/2) - break; - } - coord_pt.x = -px_size * Math.floor(coord_pt.x/px_size + off_x); - coord_pt.y = -px_size * Math.floor(coord_pt.y/px_size + off_y); - - scale_x = 256/360.0 * Math.pow(2, zoom); - scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); - - last_pt = null; - - return true; - } - - private static double limit(double v, double lo, double hi) { - if (v < lo) - return lo; - if (hi < v) - return hi; - return v; - } - - // based on google js - // http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js - // search for fromLatLngToPoint and fromPointToLatLng - private Point2D.Double pt(double lat, double lng) { - return pt(lat, lng, coord_pt, scale_x, scale_y); - } - - private static Point2D.Double pt(double lat, double lng, - Point2D.Double centre, 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(lat, lng, centre, scale_x, scale_y); - } - - private static Point2D.Double pt(double lat, double lng, - Point2D.Double centre, double scale_x, double scale_y) - { - Point2D.Double res = new Point2D.Double(); - double e; - - res.x = centre.x + lng*scale_x; - e = limit(Math.sin(Math.toRadians(lat)),-(1-1.0E-15),1-1.0E-15); - res.y = centre.y + 0.5*Math.log((1+e)/(1-e))*-scale_y; - return res; - } - - private Point2D.Double latlng(double x, double y) { - return latlng(new Point2D.Double(x,y), coord_pt); - } - private Point2D.Double latlng(Point2D.Double pt) { - return latlng(pt, coord_pt); - } - private Point2D.Double latlng(Point2D.Double pt, Point2D.Double centre) { - double lat, lng; - double rads; - - lng = (pt.x - centre.x)/scale_x; - rads = 2 * Math.atan(Math.exp((pt.y-centre.y)/-scale_y)); - lat = Math.toDegrees(rads - Math.PI/2); - - return new Point2D.Double(lat,lng); + public void loadMap(ImageIcon icn) { + mapLabel.setIcon(icn); } static Color stateColors[] = { @@ -138,21 +57,13 @@ public class AltosSiteMapTile extends JLayeredPane { boolean drawn_landed_circle = false; boolean drawn_boost_circle = false; - public void show(AltosState state, int crc_errors) { - if (!state.gps_ready) { - if (state.pad_lat == 0 && state.pad_lon == 0) - return; - if (state.ngps < 3) - return; - } - + 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(state.pad_lat, state.pad_lon); + // setLocation(state.pad_lat, state.pad_lon); + // loadMap(); + last_pt = pt; } - Point2D.Double pt = pt(state.gps.lat, state.gps.lon); if (pt != last_pt) { if (0 <= state.state && state.state < stateColors.length) { g2d.setColor(stateColors[state.state]); @@ -160,6 +71,7 @@ public class AltosSiteMapTile extends JLayeredPane { 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; @@ -192,7 +104,7 @@ public class AltosSiteMapTile extends JLayeredPane { repaint(); } - public static Graphics2D fillLabel(JLabel l, Color c) { + 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(); @@ -202,24 +114,21 @@ public class AltosSiteMapTile extends JLayeredPane { return g; } - public AltosSiteMapTile(int x_tile_offset, int y_tile_offset) { + public AltosSiteMapTile(int px_size) { setPreferredSize(new Dimension(px_size, px_size)); - mapLabel = new AltosSiteMapLabel(); - fillLabel(mapLabel, Color.GRAY); + 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)); + 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)); - - off_x = x_tile_offset; - off_y = y_tile_offset; } } 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/Makefile.am b/ao-tools/altosui/Makefile.am index 334608f6..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 \ @@ -63,7 +64,7 @@ altosui_JAVA = \ AltosSerialInUseException.java \ AltosSerialMonitor.java \ AltosSiteMap.java \ - AltosSiteMapLabel.java \ + AltosSiteMapCache.java \ AltosSiteMapTile.java \ AltosState.java \ AltosTelemetry.java \