}
}
-public class AltosSiteMap extends JComponent implements AltosFlightDisplay, MouseMotionListener, MouseListener {
+public class AltosSiteMap extends JComponent implements AltosFlightDisplay, MouseMotionListener, MouseListener, HierarchyBoundsListener {
// preferred vertical step in a tile in naut. miles
// will actually choose a step size between x and 2x, where this
// is 1.5x
tile.set_status(AltosSiteMapCache.loading);
int status = AltosSiteMapCache.fetch_map(pngfile, pngurl);
if (status == AltosSiteMapCache.success) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- tile.load_map(pngfile);
- }
- });
+ if (SwingUtilities.isEventDispatchThread())
+ tile.load_map(pngfile);
+ else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ tile.load_map(pngfile);
+ }
+ });
+ }
} else {
tile.set_status(status);
System.out.printf("# Failed to fetch file %s (status %d)\n", pngfile, status);
Thread thread = new Thread() {
public void run() {
init_map(offset, load_mode_cached|load_mode_uncached);
- finishTileLater(tile, offset);
+ SwingUtilities.invokeLater( new Runnable() {
+ public void run() {
+ addTileAt(tile, offset);
+ }
+ } );
}
};
thread.start();
}
public void setBaseLocation(double lat, double lng) {
- for (AltosSiteMapTile tile : mapTiles.values())
- tile.clearMap();
this.lat = lat;
this.lon = lng;
base_location_set = true;
private void initMaps(double lat, double lng) {
setBaseLocation(lat, lng);
+ for (AltosSiteMapTile tile : mapTiles.values())
+ tile.clearMap();
Thread thread = new Thread() {
public void run() {
for (Point k : mapTiles.keySet())
JLabel zoom_label;
public void set_zoom_label() {
- zoom_label.setText(String.format("- %d -", zoom - default_zoom));
+ zoom_label.setText(String.format("Zoom %d", zoom - default_zoom));
}
public void set_zoom(int zoom) {
mapTiles.put(offset, tile);
return tile;
}
- private void finishTileLater(final AltosSiteMapTile tile,
- final Point offset)
- {
- SwingUtilities.invokeLater( new Runnable() {
- public void run() {
- addTileAt(tile, offset);
- }
- } );
- }
private void ensureTilesAround(Point base_offset) {
for (int x = -radius; x <= radius; x++) {
}
}
+ static void debug_component(Component who, String where) {
+// Rectangle r = who.getBounds();
+// int x = r.x / px_size;
+// int y = r.y / px_size;
+//
+// System.out.printf ("%3d, %3d: %s\n", x, y, where);
+ }
+
LatLng latlng(MouseEvent e) {
if (!base_location_set)
return null;
public void mouseReleased(MouseEvent e) {
}
+ public void set_cache_size() {
+ Rectangle r = comp.getVisibleRect();
+
+ int width_tiles = (r.width + 2*px_size) / px_size;
+ int height_tiles = (r.height + 2*px_size) / px_size;
+ int tiles = width_tiles * height_tiles;
+ AltosSiteMapCache.set_cache_size(tiles);
+ }
+
+ /* HierarchyBoundsListener methods */
+ public void ancestorMoved(HierarchyEvent e) {
+ set_cache_size();
+ }
+
+ public void ancestorResized(HierarchyEvent e) {
+ set_cache_size();
+ }
+
JScrollPane pane = new JScrollPane();
public AltosSiteMap(int in_radius) {
comp.addMouseMotionListener(this);
comp.addMouseListener(this);
+ comp.addHierarchyBoundsListener(this);
GrabNDrag scroller = new GrabNDrag(comp);
import java.io.*;
import java.net.*;
-class AltosCacheImage {
- Component component;
- File file;
- VolatileImage image;
- int width;
- int height;
- long used;
-
- public void load_image() throws IOException {
- BufferedImage bimg = ImageIO.read(file);
- if (bimg == null)
- throw new IOException("Can't load image file");
- Graphics2D g = image.createGraphics();
- g.drawImage(bimg, 0, 0, null);
- bimg.flush();
- bimg = null;
- }
-
- public Image validate() {
- int returnCode;
-
- if (image != null)
- returnCode = image.validate(component.getGraphicsConfiguration());
- else
- returnCode = VolatileImage.IMAGE_INCOMPATIBLE;
- if (returnCode == VolatileImage.IMAGE_RESTORED) {
- try {
- load_image();
- } catch (IOException e) {
- return null;
- }
- } else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
- image = component.createVolatileImage(width, height);
- try {
- load_image();
- } catch (IOException e) {
- return null;
- }
- }
- return image;
- }
-
- public void flush() {
- image.flush();
- }
-
- public AltosCacheImage(Component component, File file, int w, int h) throws IOException {
- this.component = component;
- this.file = file;
- width = w;
- height = h;
- image = component.createVolatileImage(w, h);
- used = 0;
- }
-}
-
public class AltosSiteMapCache {
static final long google_maps_ratelimit_ms = 1200;
// Google limits static map queries to 50 per minute per IP, so
static boolean forbidden_set = false;
static final long forbidden_interval = 60l * 1000l * 1000l * 1000l;
- public static synchronized int fetch_map(File file, String url) {
+ static private Object fetch_lock = new Object();
+
+ public static int fetch_map(File file, String url) {
if (file.exists())
return success;
if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval)
return forbidden;
- URL u;
- long startTime = System.nanoTime();
+ synchronized (fetch_lock) {
+ URL u;
+ long startTime = System.nanoTime();
- try {
- u = new URL(url);
- } catch (java.net.MalformedURLException e) {
- return bad_request;
- }
+ 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:
- forbidden_time = System.nanoTime();
- forbidden_set = true;
- return forbidden;
+ 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:
+ 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();
+ 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)
+ if (offset != contentLength)
+ return failed;
+
+ } catch (IOException e) {
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;
+ }
- 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;
+ 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();
+ }
+ }
+ return 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();
+ static final int min_cache_size = 9;
+ static final int max_cache_size = 24;
+
+ static int cache_size = min_cache_size;
+
+ static AltosSiteMapImage[] images = new AltosSiteMapImage[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) {
+ AltosSiteMapImage[] new_images = new AltosSiteMapImage[new_size];
+
+ for (int i = 0; i < cache_size; i++) {
+ if (i < new_size)
+ new_images[i] = images[i];
+ else
+ images[i].flush();
}
+ images = new_images;
+ cache_size = new_size;
}
+ }
- return success;
+ static long used;
+
+ private static Point tile_loc(AltosSiteMapTile tile) {
+ Rectangle r = tile.getBounds();
+ int x = r.x / 512;
+ int y = r.y / 512;
+
+ return new Point (x, y);
}
- static final int cache_size = 12;
+ private static void dump_cache() {
+ int min_x = 1000, max_x = -1000, min_y = 1000, max_y = -1000;
- static AltosCacheImage[] images;
+ for (int i = 0; i < cache_size; i++) {
+ AltosSiteMapImage image = images[i];
+ if (image != null) {
+ Point p = tile_loc(image.tile);
+ min_x = min_x < p.x ? min_x : p.x;
+ max_x = max_x > p.x ? max_x : p.x;
+ min_y = min_y < p.y ? min_y : p.y;
+ max_y = max_y > p.y ? max_y : p.y;
+ System.out.printf ("entry %d %d,%d used %d\n", i, p.x, p.y, image.used);
+ } else {
+ System.out.printf ("entry %d empty\n", i);
+ }
+ }
- static long used;
+ int[][] map = new int[max_x - min_x + 1][max_y - min_y + 1];
+ for (int i = 0; i < cache_size; i++) {
+ AltosSiteMapImage image = images[i];
+ if (image != null) {
+ Point p = tile_loc(image.tile);
+ map[p.x - min_x][p.y - min_y]++;
+ }
+ }
- public static Image get_image(Component component, File file, int width, int height) {
+ for (int y = min_y; y <= max_y; y++) {
+ for (int x = min_x; x <= max_x; x++)
+ System.out.printf (" %2d", map[x - min_x][y - min_y]);
+ System.out.printf("\n");
+ }
+ }
+
+ public static AltosSiteMapImage get_image(AltosSiteMapTile tile, File file, int width, int height) {
int oldest = -1;
long age = used;
- AltosCacheImage image;
- if (images == null)
- images = new AltosCacheImage[cache_size];
- for (int i = 0; i < cache_size; i++) {
- image = images[i];
- if (image == null) {
- oldest = i;
- break;
+ synchronized(cache_lock) {
+ AltosSiteMapImage image = null;
+ for (int i = 0; i < cache_size; i++) {
+ image = images[i];
+
+ if (image == null) {
+ oldest = i;
+ break;
+ }
+ if (image.tile == tile && file.equals(image.file)) {
+ image.used = used++;
+ return image;
+ }
+ if (image.used < age) {
+ oldest = i;
+ age = image.used;
+ }
}
- if (image.component == component && file.equals(image.file)) {
+
+ try {
+ image = new AltosSiteMapImage(tile, file, width, height);
image.used = used++;
- return image.validate();
- }
- if (image.used < age) {
- oldest = i;
- age = image.used;
+ if (images[oldest] != null) {
+// dump_cache();
+ AltosSiteMap.debug_component(images[oldest].tile, "replacing cache");
+ AltosSiteMap.debug_component(tile, "replaced cache");
+ images[oldest].flush();
+ }
+ images[oldest] = image;
+ return image;
+ } catch (IOException e) {
+ return null;
}
}
-
- try {
- image = new AltosCacheImage(component, file, width, height);
- image.used = used++;
- if (images[oldest] != null)
- images[oldest].flush();
- images[oldest] = image;
- return image.validate();
- } catch (IOException e) {
- return null;
- }
}
}
--- /dev/null
+/*
+ * 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.image.*;
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.io.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosSiteMapImage {
+ AltosSiteMapTile tile;
+ File file;
+ BufferedImage image;
+ int width;
+ int height;
+ long used;
+
+ Thread load_thread;
+
+ public boolean validate() {
+ if (image != null) {
+ AltosSiteMap.debug_component(tile, "valid");
+ return true;
+ } else {
+ AltosSiteMap.debug_component(tile, "loading");
+ load_thread = new Thread() {
+ public void run() {
+ image = null;
+ try {
+ image = ImageIO.read(file);
+ } catch (Exception e) {
+ }
+ SwingUtilities.invokeLater( new Runnable() {
+ public void run() {
+ AltosSiteMap.debug_component(tile, "later");
+ Graphics2D g2d = (Graphics2D) tile.getGraphics();
+ tile.paint_graphics(g2d, image);
+ load_thread = null;
+ }
+ });
+ }
+ };
+ load_thread.start();
+ return false;
+ }
+ }
+
+ public void flush() {
+ if (load_thread == null) {
+ AltosSiteMap.debug_component(tile, "flush");
+ image.flush();
+ image = null;
+ }
+ }
+
+ public AltosSiteMapImage (AltosSiteMapTile tile, File file, int w, int h) throws IOException {
+ this.tile = tile;
+ this.file = file;
+ width = w;
+ height = h;
+ image = null;
+ used = 0;
+ }
+}
+
LinkedList<AltosPoint> points;
public synchronized void queue_repaint() {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- repaint();
- }
- });
+ if (SwingUtilities.isEventDispatchThread())
+ repaint();
+ else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ repaint();
+ }
+ });
+ }
}
public void load_map(File pngFile) {
public void set_font(Font font) {
this.font = font;
this.status = AltosSiteMapCache.success;
- queue_repaint();
}
public void set_status(int status) {
- file = null;
- this.status = status;
- queue_repaint();
+ if (status != this.status || file != null) {
+ file = null;
+ this.status = status;
+ queue_repaint();
+ }
}
public void clearMap() {
points = null;
file = null;
status = AltosSiteMapCache.success;
- queue_repaint();
line = null;
}
return String.format(format, distance);
}
- public void paint(Graphics g) {
- Graphics2D g2d = (Graphics2D) g;
- AltosPoint prev = null;
- Image img = null;
+ boolean painting;
- if (file != null)
- img = AltosSiteMapCache.get_image(this, file, px_size, px_size);
-
- if (img != null) {
- g2d.drawImage(img, 0, 0, null);
+ public void paint_graphics(Graphics2D g2d, Image image) {
+ if (image != null) {
+ AltosSiteMap.debug_component(this, "paint_graphics");
+ g2d.drawImage(image, 0, 0, null);
} else {
+ AltosSiteMap.debug_component(this, "erase_graphics");
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, getWidth(), getHeight());
String message = null;
g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
if (points != null) {
+ AltosPoint prev = null;
for (AltosPoint point : points) {
if (prev != null) {
if (0 <= point.state && point.state < stateColors.length)
}
g2d.drawString(message, x, y);
}
+ painting = false;
+ }
+
+ public void paint(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g;
+ Image image = null;
+ boolean queued = false;
+
+ if (painting) {
+ AltosSiteMap.debug_component(this, "already painting");
+ return;
+ }
+ AltosSiteMap.debug_component(this, "paint");
+
+ if (file != null) {
+ AltosSiteMapImage aimage;
+
+ aimage = AltosSiteMapCache.get_image(this, file, px_size, px_size);
+ if (aimage != null) {
+ if (aimage.validate())
+ image = aimage.image;
+ else
+ queued = true;
+ }
+ }
+ if (!queued)
+ paint_graphics(g2d, image);
+ painting = queued;
}
- public synchronized void show(int state, Point2D.Double last_pt, Point2D.Double pt)
+ public void show(int state, Point2D.Double last_pt, Point2D.Double pt)
{
if (points == null)
points = new LinkedList<AltosPoint>();