altoslib: Preload maps based on distance rather than number of tiles
authorKeith Packard <keithp@keithp.com>
Sun, 12 Jul 2015 02:15:08 +0000 (19:15 -0700)
committerKeith Packard <keithp@keithp.com>
Sun, 12 Jul 2015 02:16:41 +0000 (19:16 -0700)
This lets you get the specific area requested at all zoom levels,
rather than having further detail only at lower resolution zooms.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosdroid/res/layout/map_preload.xml
altosdroid/res/values/strings.xml
altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java
altoslib/AltosMapLoader.java
altoslib/AltosMapLoaderListener.java
altosuilib/AltosUIMapPreloadNew.java

index 1d1fca3..dc613bf 100644 (file)
               android:prompt="@string/preload_max_zoom"
               android:spinnerMode="dropdown"
               />
-      <TextView android:id="@+id/preload_tile_radius_label"
+      <TextView android:id="@+id/preload_radius_label"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
-               android:text="@string/preload_tile_radius"
+               android:text="@string/preload_radius"
                />
-      <Spinner android:id="@+id/preload_tile_radius"
+      <Spinner android:id="@+id/preload_radius"
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
-              android:prompt="@string/preload_tile_radius"
+              android:prompt="@string/preload_radius"
               android:spinnerMode="dropdown"
               />
       <Button android:id="@+id/preload_load"
index 8c299a3..36b07bc 100644 (file)
        <string name="preload_terrain">Terrain</string>
        <string name="preload_min_zoom">Minimum Zoom</string>
        <string name="preload_max_zoom">Maximum Zoom</string>
-       <string name="preload_tile_radius">Tile Radius</string>
+       <string name="preload_radius">Radius</string>
        
        <string name="preload_load">Load Map</string>
 </resources>
index 1968eda..498b208 100644 (file)
@@ -61,7 +61,8 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
        private Spinner         known_sites_spinner;
        private Spinner         min_zoom;
        private Spinner         max_zoom;
-       private Spinner         tile_radius;
+       private TextView        radius_label;
+       private Spinner         radius;
 
        private EditText        latitude;
        private EditText        longitude;
@@ -227,8 +228,17 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
                return value(max_zoom);
        }
 
-       private int radius() {
-               return value(tile_radius);
+       private double value_distance(Spinner spinner) {
+               return (Double) spinner.getSelectedItem();
+       }
+
+       private double radius() {
+               double r = value_distance(radius);
+               if (AltosPreferences.imperial_units())
+                       r = AltosConvert.distance.inverse(r);
+               else
+                       r = r * 1000;
+               return r;
        }
 
        private int bit(CheckBox box, int value) {
@@ -250,11 +260,14 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
                        double  lon = longitude();
                        int     min = min_z();
                        int     max = max_z();
-                       int     r = radius();
+                       double  r = radius();
                        int     t = types();
 
+                       AltosDebug.debug("PreloadMap load %f %f %d %d %f %d\n",
+                                        lat, lon, min, max, r, t);
                        loader.load(lat, lon, min, max, r, t);
                } catch (ParseException e) {
+                       AltosDebug.debug("PreloadMap load raised exception %s", e.toString());
                }
        }
 
@@ -276,6 +289,37 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
                spinner.setSelection(spinner_def);
        }
 
+
+       private void add_distance(Spinner spinner, double[] distances_km, double def_km, double[] distances_mi, double def_mi) {
+
+               ArrayAdapter<Double> adapter = new ArrayAdapter<Double>(this, android.R.layout.simple_spinner_item);
+
+               int     spinner_def = 0;
+               int     pos = 0;
+
+               double[] distances;
+               double  def;
+               if (AltosPreferences.imperial_units()) {
+                       distances = distances_mi;
+                       def = def_mi;
+               } else {
+                       distances = distances_km;
+                       def = def_km;
+               }
+
+               for (int i = 0; i < distances.length; i++) {
+                       adapter.add(distances[i]);
+                       if (distances[i] == def)
+                               spinner_def = pos;
+                       pos++;
+               }
+
+               spinner.setAdapter(adapter);
+               spinner.setSelection(spinner_def);
+       }
+
+
+
        class SiteListListener implements OnItemSelectedListener {
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                        AltosLaunchSite site = (AltosLaunchSite) parent.getItemAtPosition(pos);
@@ -289,6 +333,11 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
                }
        }
 
+       double[]        radius_mi = { 1, 2, 5, 10, 20 };
+       double          radius_def_mi = 2;
+       double[]        radius_km = { 1, 2, 5, 10, 20, 30 };
+       double          radius_def_km = 2;
+
        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
@@ -326,8 +375,13 @@ public class PreloadMapActivity extends Activity implements AltosLaunchSiteListe
                add_numbers(max_zoom,
                            AltosMap.min_zoom - AltosMap.default_zoom,
                            AltosMap.max_zoom - AltosMap.default_zoom, 2);
-               tile_radius = (Spinner) findViewById(R.id.preload_tile_radius);
-               add_numbers(tile_radius, 1, 5, 5);
+               radius_label = (TextView) findViewById(R.id.preload_radius_label);
+               radius = (Spinner) findViewById(R.id.preload_radius);
+               if (AltosPreferences.imperial_units())
+                       radius_label.setText("Radius (miles)");
+               else
+                       radius_label.setText("Radius (km)");
+               add_distance(radius, radius_km, radius_def_km, radius_mi, radius_def_mi);
 
                progress = (ProgressBar) findViewById(R.id.preload_progress);
 
index cf7169b..2cd5dbd 100644 (file)
@@ -24,7 +24,7 @@ import java.lang.Math;
 import java.net.URL;
 import java.net.URLConnection;
 
-public class AltosMapLoader implements AltosMapTileListener {
+public class AltosMapLoader implements AltosMapTileListener, AltosMapStoreListener {
        AltosMapLoaderListener  listener;
 
        double  latitude, longitude;
@@ -33,17 +33,71 @@ public class AltosMapLoader implements AltosMapTileListener {
        int     cur_z;
        int     all_types;
        int     cur_type;
-       int     radius;
+       double  radius;
 
-       int     tiles_per_layer;
-       int     tiles_loaded;
+       int     tiles_loaded_layer;
+       int     tiles_loaded_total;
+       int     tiles_this_layer;
+       int     tiles_total;
        int     layers_total;
        int     layers_loaded;
 
        AltosMap        map;
 
+       int tile_radius(int zoom) {
+               double  delta_lon = AltosMapTransform.lon_from_distance(latitude, radius);
+
+               AltosMapTransform t = new AltosMapTransform(256, 256, zoom + AltosMap.default_zoom, new AltosLatLon(latitude, longitude));
+
+               AltosPointDouble        center = t.point(new AltosLatLon(latitude, longitude));
+               AltosPointDouble        edge = t.point(new AltosLatLon(latitude, longitude + delta_lon));
+
+               int tile_radius = (int) Math.ceil(Math.abs(center.x - edge.x) / AltosMap.px_size);
+
+               return tile_radius;
+       }
+
+       int tiles_per_layer(int zoom) {
+               int     tile_radius = tile_radius(zoom);
+               return (tile_radius * 2 + 1) * (tile_radius * 2 + 1);
+       }
+
        public void do_load() {
-               map.set_load_params(cur_z + AltosMap.default_zoom, cur_type, latitude, longitude, radius, this);
+               tiles_this_layer = tiles_per_layer(cur_z);
+               tiles_loaded_layer = 0;
+               listener.debug("tiles_this_layer %d (zoom %d)\n", tiles_this_layer, cur_z);
+
+               int load_radius = tile_radius(cur_z);
+               int zoom = cur_z + AltosMap.default_zoom;
+               int maptype = cur_type;
+               AltosLatLon load_centre = new AltosLatLon(latitude, longitude);
+               AltosMapTransform transform = new AltosMapTransform(256, 256, zoom, load_centre);
+
+               map.centre(load_centre);
+
+               AltosPointInt   upper_left;
+               AltosPointInt   lower_right;
+
+               AltosPointInt centre = AltosMap.floor(transform.point(load_centre));
+
+               upper_left = new AltosPointInt(centre.x - load_radius * AltosMap.px_size,
+                                              centre.y - load_radius * AltosMap.px_size);
+               lower_right = new AltosPointInt(centre.x + load_radius * AltosMap.px_size,
+                                               centre.y + load_radius * AltosMap.px_size);
+
+
+               for (int y = (int) upper_left.y; y <= lower_right.y; y += AltosMap.px_size) {
+                       for (int x = (int) upper_left.x; x <= lower_right.x; x += AltosMap.px_size) {
+                               listener.debug("Make tile at %d, %d\n", x, y);
+                               AltosPointInt   point = new AltosPointInt(x, y);
+                               AltosLatLon     ul = transform.lat_lon(point);
+                               AltosLatLon     center = transform.lat_lon(new AltosPointDouble(x + AltosMap.px_size/2, y + AltosMap.px_size/2));
+                               AltosMapTile    tile = map.map_interface.new_tile(this, ul, center, zoom, maptype, AltosMap.px_size);
+                               tile.add_store_listener(this);
+                               if (tile.store_status() != AltosMapTile.loading)
+                                       notify_tile(tile, tile.store_status());
+                       }
+               }
        }
 
        public int next_type(int start) {
@@ -84,16 +138,23 @@ public class AltosMapLoader implements AltosMapTileListener {
                }
 
                cur_type = next_type(0);
-               tiles_per_layer = (radius * 2 + 1) * (radius * 2 + 1);
+
+               for (int z = min_z; z <= max_z; z++)
+                       tiles_total += tiles_per_layer(z);
+
                layers_total = (max_z - min_z + 1) * ntype;
                layers_loaded = 0;
-               tiles_loaded = 0;
+               tiles_loaded_total = 0;
+
+               listener.debug("total tiles %d\n", tiles_total);
 
-               listener.loader_start(layers_total * tiles_per_layer);
+               listener.loader_start(tiles_total);
                do_load();
        }
 
-       public void load(double latitude, double longitude, int min_z, int max_z, int radius, int all_types) {
+       public void load(double latitude, double longitude, int min_z, int max_z, double radius, int all_types) {
+               listener.debug("lat %f lon %f min_z %d max_z %d radius %f all_types %d\n",
+                              latitude, longitude, min_z, max_z, radius, all_types);
                this.latitude = latitude;
                this.longitude = longitude;
                this.min_z = min_z;
@@ -103,7 +164,7 @@ public class AltosMapLoader implements AltosMapTileListener {
                start_load();
        }
 
-       public synchronized void notify_tile(AltosMapTile tile, int status) {
+       public synchronized void notify_store(AltosMapStore store, int status) {
                boolean do_next = false;
                if (status == AltosMapTile.loading)
                        return;
@@ -111,24 +172,30 @@ public class AltosMapLoader implements AltosMapTileListener {
                if (layers_loaded >= layers_total)
                        return;
 
-               ++tiles_loaded;
+               ++tiles_loaded_total;
+               ++tiles_loaded_layer;
+               listener.debug("total %d layer %d\n", tiles_loaded_total, tiles_loaded_layer);
 
-               if (tiles_loaded == tiles_per_layer) {
-                       tiles_loaded = 0;
+               if (tiles_loaded_layer == tiles_this_layer) {
                        ++layers_loaded;
+                       listener.debug("%d layers loaded\n", layers_loaded);
                        if (layers_loaded == layers_total) {
-                               listener.loader_done(layers_total * tiles_per_layer);
+                               listener.loader_done(tiles_total);
                                return;
                        } else {
                                do_next = true;
                        }
                }
-               listener.loader_notify(layers_loaded * tiles_per_layer + tiles_loaded,
-                                      layers_total * tiles_per_layer, tile.store.file.toString());
+               listener.loader_notify(tiles_loaded_total,
+                                      tiles_total, store.file.toString());
                if (do_next)
                        next_load();
        }
 
+       public synchronized void notify_tile(AltosMapTile tile, int status) {
+               notify_store(tile.store, status);
+       }
+
        public AltosMapCache cache() { return map.cache(); }
 
        public AltosMapLoader(AltosMap map, AltosMapLoaderListener listener) {
index 79f8b9d..11f59b2 100644 (file)
@@ -23,4 +23,6 @@ public interface AltosMapLoaderListener {
        public abstract void loader_notify(int cur, int max, String name);
 
        public abstract void loader_done(int max);
+
+       public abstract void debug(String format, Object ... arguments);
 }
index 4a5764c..ba55953 100644 (file)
@@ -140,10 +140,15 @@ public class AltosUIMapPreloadNew extends AltosUIFrame implements ActionListener
 
        JComboBox<Integer>      min_zoom;
        JComboBox<Integer>      max_zoom;
-       JComboBox<Integer>      radius;
+       JComboBox<Double>       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 };
+
+       Double[]        radius_mi = { 1.0, 2.0, 5.0, 10.0, 20.0 };
+       Double          radius_def_mi = 5.0;
+       Double[]        radius_km = { 2.0, 5.0, 10.0, 20.0, 30.0 };
+       Double          radius_def_km = 10.0;
+
 
        static final String[]   lat_hemi_names = { "N", "S" };
        static final String[]   lon_hemi_names = { "E", "W" };
@@ -183,6 +188,11 @@ public class AltosUIMapPreloadNew extends AltosUIFrame implements ActionListener
                        });
        }
 
+       public void debug(String format, Object ... arguments) {
+               System.out.printf(format, arguments);
+       }
+
+
        private int all_types() {
                int all_types = 0;
                for (int t = AltosMap.maptype_hybrid; t <= AltosMap.maptype_terrain; t++)
@@ -219,7 +229,12 @@ public class AltosUIMapPreloadNew extends AltosUIFrame implements ActionListener
                                        int max_z = (Integer) max_zoom.getSelectedItem();
                                        if (max_z < min_z)
                                                max_z = min_z;
-                                       int r = (Integer) radius.getSelectedItem();
+                                       Double r = (Double) radius.getSelectedItem();
+
+                                       if (AltosPreferences.imperial_units())
+                                               r = AltosConvert.distance.inverse(r);
+                                       else
+                                               r = r * 1000;
                                        loading = true;
 
                                        loader.load(latitude, longitude, min_z, max_z, r, all_types());
@@ -430,13 +445,21 @@ public class AltosUIMapPreloadNew extends AltosUIFrame implements ActionListener
                c.gridy = 3;
                pane.add(max_zoom, c);
 
-               JLabel radius_label = new JLabel("Tile Radius");
+               JLabel radius_label = new JLabel(String.format("Map Radius (%s)",
+                                                              AltosPreferences.imperial_units() ? "miles" : "km"));
                c.gridx = 4;
                c.gridy = 4;
                pane.add(radius_label, c);
 
-               radius = new JComboBox<Integer>(radii);
-               radius.setSelectedItem(radii[4]);
+               Double[]        radii;
+               Double          radius_default;
+
+               if (AltosPreferences.imperial_units())
+                       radii = radius_mi;
+               else
+                       radii = radius_km;
+               radius = new JComboBox<Double>(radii);
+               radius.setSelectedItem(radii[2]);
                radius.setEditable(true);
                c.gridx = 5;
                c.gridy = 4;