altosdroid: Display online/offline maps in same tab
authorKeith Packard <keithp@keithp.com>
Tue, 23 Jun 2015 01:26:34 +0000 (18:26 -0700)
committerKeith Packard <keithp@keithp.com>
Tue, 23 Jun 2015 04:04:43 +0000 (21:04 -0700)
Make the map portion switchable between online and offline maps,
leaving the rest of the tab alone.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosdroid/res/layout/tab_map.xml
altosdroid/res/layout/tab_map_offline.xml
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/AltosMapView.java [deleted file]
altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java
altosdroid/src/org/altusmetrum/AltosDroid/TabMapOffline.java

index f611ae4..b4c97f9 100644 (file)
        android:layout_height="match_parent"
        android:orientation="vertical" >
 
-       <LinearLayout
+       <LinearLayout
            android:id="@+id/map"
-               android:orientation="horizontal"
-               android:layout_width="fill_parent"
-               android:layout_height="0dp"
-               android:layout_weight="1">
-
+           android:orientation="horizontal"
+           android:layout_width="fill_parent"
+           android:layout_height="fill_parent"
+           android:layout_weight="1">
        </LinearLayout>
 
        <LinearLayout
                                android:textAppearance="?android:attr/textAppearanceSmall" />
                </RelativeLayout>
        </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
index e6f134d..1b0f28a 100644 (file)
        android:layout_height="match_parent"
        android:orientation="vertical" >
 
-  <org.altusmetrum.AltosDroid.AltosMapView android:id="@+id/map_view_offline"
-                                          android:layout_width="fill_parent"
-                                          android:layout_height="wrap_content"
-                                          android:layout_weight="1"
-                                          />
+  <FrameLayout
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:baselineAligned="true"
+      android:orientation="horizontal"
+      android:layout_weight="1">
+    <LinearLayout
+       android:id="@+id/map_online"
+       android:orientation="horizontal"
+       android:layout_width="fill_parent"
+       android:layout_height="fill_parent"
+       android:layout_weight="1">
+    </LinearLayout>
+    <org.altusmetrum.AltosDroid.AltosMapOffline android:id="@+id/map_view_offline"
+                                               android:layout_width="fill_parent"
+                                               android:layout_height="wrap_content"
+                                               android:layout_weight="1"
+                                               />
+  </FrameLayout>
   
   <LinearLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
index fb669c5..426e2e8 100644 (file)
@@ -503,7 +503,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
                return tab_view;
        }
 
-       public void set_map_source() {
+       public void set_map_source(int source) {
+               for (AltosDroidTab mTab : mTabs)
+                       mTab.set_map_source(source);
        }
 
        @Override
@@ -928,7 +930,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
                        int source = AltosDroidPreferences.map_source();
                        int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE;
                        AltosDroidPreferences.set_map_source(new_source);
-                       set_map_source();
+                       set_map_source(new_source);
                        return true;
                case R.id.select_tracker:
                        if (serials != null) {
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java
new file mode 100644 (file)
index 0000000..681cd31
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2015 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.AltosDroid;
+
+import java.util.*;
+import java.io.*;
+import android.location.Location;
+import org.altusmetrum.altoslib_7.*;
+
+public interface AltosDroidMapInterface {
+       public void onCreateView(int map_type);
+
+       public void set_visible(boolean visible);
+
+       public void center(double lat, double lon, double accuracy);
+
+       public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
+}
index 6a71021..f75035d 100644 (file)
@@ -42,6 +42,9 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen
        public void set_map_type(int map_type) {
        }
 
+       public void set_map_source(int map_source) {
+       }
+
        public void units_changed(boolean imperial_units) {
                if (!isHidden())
                        show(last_telem_state, last_state, last_from_receiver, last_receiver);
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java
new file mode 100644 (file)
index 0000000..3ff6ff2
--- /dev/null
@@ -0,0 +1,469 @@
+/*
+ * Copyright © 2015 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.AltosDroid;
+
+import java.util.*;
+import java.io.*;
+
+import org.altusmetrum.altoslib_7.*;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.*;
+import android.widget.*;
+import android.location.Location;
+import android.content.*;
+import android.util.*;
+
+class Rocket implements Comparable {
+       AltosLatLon     position;
+       String          name;
+       long            last_packet;
+       AltosMapOffline map_offline;
+
+       void paint() {
+               map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
+               map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
+       }
+
+       void set_position(AltosLatLon position, long last_packet) {
+               this.position = position;
+               this.last_packet = last_packet;
+       }
+
+       Rocket(String name, AltosMapOffline map_offline) {
+               this.name = name;
+               this.map_offline = map_offline;
+       }
+
+       public int compareTo(Object o) {
+               Rocket other = (Rocket) o;
+
+               long    diff = last_packet - other.last_packet;
+
+               if (diff > 0)
+                       return 1;
+               if (diff < 0)
+                       return -1;
+               return 0;
+       }
+}
+
+public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface {
+       ScaleGestureDetector    scale_detector;
+       boolean                 scaling;
+       AltosMap                map;
+
+       AltosLatLon     here;
+       AltosLatLon     pad;
+
+       Canvas  canvas;
+       Paint   paint;
+
+       Bitmap  pad_bitmap;
+       int     pad_off_x, pad_off_y;
+       Bitmap  rocket_bitmap;
+       int     rocket_off_x, rocket_off_y;
+       Bitmap  here_bitmap;
+       int     here_off_x, here_off_y;
+
+       static  final int       WHITE = 0xffffffff;
+       static  final int       RED   = 0xffff0000;
+       static  final int       PINK  = 0xffff8080;
+       static  final int       YELLOW= 0xffffff00;
+       static  final int       CYAN  = 0xff00ffff;
+       static  final int       BLUE  = 0xff0000ff;
+       static  final int       BLACK = 0xff000000;
+
+       public static final int stateColors[] = {
+               WHITE,  // startup
+               WHITE,  // idle
+               WHITE,  // pad
+               RED,    // boost
+               PINK,   // fast
+               YELLOW, // coast
+               CYAN,   // drogue
+               BLUE,   // main
+               BLACK,  // landed
+               BLACK,  // invalid
+               CYAN,   // stateless
+       };
+
+       /* AltosMapInterface */
+       public void debug(String format, Object ... arguments) {
+               AltosDebug.debug(format, arguments);
+       }
+
+       class MapTile extends AltosMapTile {
+               public void paint(AltosMapTransform t) {
+                       AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
+
+                       if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
+                               return;
+
+                       AltosImage              altos_image = cache.get(this, store, px_size, px_size);
+
+                       MapImage                map_image = (MapImage) altos_image;
+
+                       Bitmap                  bitmap = null;
+
+                       if (map_image != null)
+                               bitmap = map_image.bitmap;
+
+                       if (bitmap != null) {
+                               canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
+                       } else {
+                               paint.setColor(0xff808080);
+                               canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
+                               if (t.has_location()) {
+                                       String  message = null;
+                                       switch (status) {
+                                       case AltosMapTile.loading:
+                                               message = "Loading...";
+                                               break;
+                                       case AltosMapTile.bad_request:
+                                               message = "Internal error";
+                                               break;
+                                       case AltosMapTile.failed:
+                                               message = "Network error, check connection";
+                                               break;
+                                       case AltosMapTile.forbidden:
+                                               message = "Too many requests, try later";
+                                               break;
+                                       }
+                                       if (message != null) {
+                                               Rect    bounds = new Rect();
+                                               paint.getTextBounds(message, 0, message.length(), bounds);
+
+                                               int     width = bounds.right - bounds.left;
+                                               int     height = bounds.bottom - bounds.top;
+
+                                               float x = pt.x + px_size / 2.0f;
+                                               float y = pt.y + px_size / 2.0f;
+                                               x = x - width / 2.0f;
+                                               y = y + height / 2.0f;
+                                               paint.setColor(0xff000000);
+                                               canvas.drawText(message, 0, message.length(), x, y, paint);
+                                       }
+                               }
+                       }
+               }
+
+               public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+                       super(listener, upper_left, center, zoom, maptype, px_size, 2);
+               }
+
+       }
+
+       public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+               return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
+       }
+
+       public AltosMapPath new_path() {
+               return null;
+       }
+
+       public AltosMapLine new_line() {
+               return null;
+       }
+
+       class MapImage implements AltosImage {
+               public Bitmap   bitmap;
+
+               public void flush() {
+                       if (bitmap != null) {
+                               bitmap.recycle();
+                               bitmap = null;
+                       }
+               }
+
+               public MapImage(File file) {
+                       bitmap = BitmapFactory.decodeFile(file.getPath());
+               }
+       }
+
+       public AltosImage load_image(File file) throws Exception {
+               return new MapImage(file);
+       }
+
+       class MapMark extends AltosMapMark {
+               public void paint(AltosMapTransform t) {
+               }
+
+               MapMark(double lat, double lon, int state) {
+                       super(lat, lon, state);
+               }
+       }
+
+       public AltosMapMark new_mark(double lat, double lon, int state) {
+               return new MapMark(lat, lon, state);
+       }
+
+       public int width() {
+               return getWidth();
+       }
+
+       public int height() {
+               return getHeight();
+       }
+
+       public void repaint() {
+               postInvalidate();
+       }
+
+       public void repaint(AltosRectangle damage) {
+               postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
+       }
+
+       public void set_zoom_label(String label) {
+       }
+
+       class Line {
+               AltosLatLon     a, b;
+
+               void paint() {
+                       if (a != null && b != null) {
+                               AltosPointDouble        a_screen = map.transform.screen(a);
+                               AltosPointDouble        b_screen = map.transform.screen(b);
+                               paint.setColor(0xff8080ff);
+                               canvas.drawLine((float) a_screen.x, (float) a_screen.y,
+                                                   (float) b_screen.x, (float) b_screen.y,
+                                                   paint);
+                       }
+               }
+
+               void set_a(AltosLatLon a) {
+                       this.a = a;
+               }
+
+               void set_b(AltosLatLon b) {
+                       this.b = b;
+               }
+
+               Line() {
+               }
+       }
+
+       Line line = new Line();
+
+       int     stroke_width = 20;
+
+       void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
+               if (lat_lon != null && map != null && map.transform != null) {
+                       AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
+
+                       Rect    bounds = new Rect();
+                       paint.getTextBounds(text, 0, text.length(), bounds);
+
+                       int     width = bounds.right - bounds.left;
+                       int     height = bounds.bottom - bounds.top;
+
+                       float x = pt.x;
+                       float y = pt.y;
+                       x = x - width / 2.0f - off_x;
+                       y = y + height / 2.0f - off_y;
+                       paint.setColor(0xff000000);
+                       canvas.drawText(text, 0, text.length(), x, y, paint);
+               }
+       }
+
+       HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
+
+       void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
+               if (lat_lon != null && map != null && map.transform != null) {
+                       AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
+
+                       canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
+               }
+       }
+
+       private void draw_positions() {
+               line.set_a(map.last_position);
+               line.set_b(here);
+               line.paint();
+               draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
+
+               Rocket[]        rocket_array = rockets.values().toArray(new Rocket[0]);
+
+               Arrays.sort(rocket_array);
+               for (Rocket rocket : rocket_array)
+                       rocket.paint();
+               draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
+       }
+
+       @Override public void invalidate() {
+               Rect r = new Rect();
+               getDrawingRect(r);
+               super.invalidate();
+       }
+
+       @Override public void invalidate(int l, int t, int r, int b) {
+               Rect rect = new Rect();
+               getDrawingRect(rect);
+               super.invalidate();
+       }
+
+       @Override
+       protected void onDraw(Canvas view_canvas) {
+               debug("onDraw");
+               if (map == null) {
+                       debug("MapView draw without map\n");
+                       return;
+               }
+               canvas = view_canvas;
+               paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+               paint.setStrokeWidth(stroke_width);
+               paint.setStrokeCap(Paint.Cap.ROUND);
+               paint.setStrokeJoin(Paint.Join.ROUND);
+               paint.setTextSize(40);
+               map.paint();
+               draw_positions();
+               canvas = null;
+       }
+
+       public boolean onScale(ScaleGestureDetector detector) {
+               float   f = detector.getScaleFactor();
+
+               if (f <= 0.8) {
+                       map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
+                       return true;
+               }
+               if (f >= 1.2) {
+                       map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
+                       return true;
+               }
+               return false;
+       }
+
+       public boolean onScaleBegin(ScaleGestureDetector detector) {
+               return true;
+       }
+
+       public void onScaleEnd(ScaleGestureDetector detector) {
+       }
+
+       @Override
+       public boolean dispatchTouchEvent(MotionEvent event) {
+               scale_detector.onTouchEvent(event);
+
+               if (scale_detector.isInProgress()) {
+                       scaling = true;
+               }
+
+               if (scaling) {
+                       if (event.getAction() == MotionEvent.ACTION_UP) {
+                               scaling = false;
+                       }
+                       return true;
+               }
+
+               if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                       map.touch_start((int) event.getX(), (int) event.getY(), true);
+               } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+                       map.touch_continue((int) event.getX(), (int) event.getY(), true);
+               }
+               return true;
+       }
+
+       double  mapAccuracy;
+
+       public void center(double lat, double lon, double accuracy) {
+               if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
+                       if (map != null)
+                               map.maybe_centre(lat, lon);
+                       mapAccuracy = accuracy;
+               }
+       }
+
+       public void set_visible(boolean visible) {
+               if (visible)
+                       setVisibility(VISIBLE);
+               else
+                       setVisibility(GONE);
+       }
+
+       public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+               if (state != null) {
+                       map.show(state, null);
+                       if (state.pad_lat != AltosLib.MISSING && pad == null)
+                               pad = new AltosLatLon(state.pad_lat, state.pad_lon);
+               }
+
+               if (telem_state != null) {
+                       Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
+                       Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
+
+                       /* remove deleted keys */
+                       for (int serial : old_serial) {
+                               if (!telem_state.states.containsKey(serial))
+                                       rockets.remove(serial);
+                       }
+
+                       /* set remaining keys */
+
+                       for (int serial : new_serial) {
+                               Rocket          rocket;
+                               AltosState      t_state = telem_state.states.get(serial);
+                               if (rockets.containsKey(serial))
+                                       rocket = rockets.get(serial);
+                               else {
+                                       rocket = new Rocket(String.format("%d", serial), this);
+                                       rockets.put(serial, rocket);
+                               }
+                               if (t_state.gps != null)
+                                       rocket.set_position(new AltosLatLon(t_state.gps.lat, t_state.gps.lon), t_state.received_time);
+                       }
+               }
+               if (receiver != null) {
+                       here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
+               }
+       }
+
+       public void onCreateView(int map_type) {
+               map = new AltosMap(this);
+               map.set_maptype(map_type);
+
+               pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
+               /* arrow at the bottom of the launchpad image */
+               pad_off_x = pad_bitmap.getWidth() / 2;
+               pad_off_y = pad_bitmap.getHeight();
+
+               rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
+               /* arrow at the bottom of the rocket image */
+               rocket_off_x = rocket_bitmap.getWidth() / 2;
+               rocket_off_y = rocket_bitmap.getHeight();
+
+               here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
+               /* Center of the dot */
+               here_off_x = here_bitmap.getWidth() / 2;
+               here_off_y = here_bitmap.getHeight() / 2;
+       }
+
+       public void set_map_type(int map_type) {
+               if (map != null)
+                       map.set_maptype(map_type);
+       }
+
+       public AltosMapOffline(Context context, AttributeSet attrs) {
+               super(context, attrs);
+               scale_detector = new ScaleGestureDetector(context, this);
+       }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java
new file mode 100644 (file)
index 0000000..9503a0b
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright © 2013 Mike Beattie <mike@ethernal.org>
+ *
+ * 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.AltosDroid;
+
+import java.util.*;
+
+import org.altusmetrum.altoslib_7.*;
+
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.maps.model.Polyline;
+import com.google.android.gms.maps.model.PolylineOptions;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.*;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+//import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.location.Location;
+import android.content.*;
+
+class RocketOnline implements Comparable {
+       Marker          marker;
+       long            last_packet;
+
+       void set_position(AltosLatLon position, long last_packet) {
+               marker.setPosition(new LatLng(position.lat, position.lon));
+               this.last_packet = last_packet;
+       }
+
+       private Bitmap rocket_bitmap(Context context, String text) {
+
+               /* From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/
+                */
+               Bitmap orig_bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocket);
+               Bitmap bitmap = orig_bitmap.copy(Bitmap.Config.ARGB_8888, true);
+
+               Canvas canvas = new Canvas(bitmap);
+               Paint paint = new Paint();
+               paint.setTextSize(40);
+               paint.setColor(0xff000000);
+
+               Rect    bounds = new Rect();
+               paint.getTextBounds(text, 0, text.length(), bounds);
+
+               int     width = bounds.right - bounds.left;
+               int     height = bounds.bottom - bounds.top;
+
+               float x = bitmap.getWidth() / 2.0f - width / 2.0f;
+               float y = bitmap.getHeight() / 2.0f - height / 2.0f;
+
+               canvas.drawText(text, 0, text.length(), x, y, paint);
+               return bitmap;
+       }
+
+       public void remove() {
+               marker.remove();
+       }
+
+       public int compareTo(Object o) {
+               RocketOnline other = (RocketOnline) o;
+
+               long    diff = last_packet - other.last_packet;
+
+               if (diff > 0)
+                       return 1;
+               if (diff < 0)
+                       return -1;
+               return 0;
+       }
+
+       RocketOnline(Context context, String name, GoogleMap map, double lat, double lon, long last_packet) {
+               this.marker = map.addMarker(new MarkerOptions()
+                                           .icon(BitmapDescriptorFactory.fromBitmap(rocket_bitmap(context, name)))
+                                           .position(new LatLng(lat, lon))
+                                           .visible(true));
+               this.last_packet = last_packet;
+       }
+}
+
+public class AltosMapOnline implements AltosDroidMapInterface {
+       public SupportMapFragment mMapFragment;
+       private GoogleMap mMap;
+       private boolean mapLoaded = false;
+       Context context;
+
+       private HashMap<Integer,RocketOnline> rockets = new HashMap<Integer,RocketOnline>();
+       private Marker mPadMarker;
+       private boolean pad_set;
+       private Polyline mPolyline;
+
+       private View map_view;
+
+       private double mapAccuracy = -1;
+
+       private AltosLatLon my_position = null;
+       private AltosLatLon target_position = null;
+
+       public void onCreateView(final int map_type) {
+               mMapFragment = new SupportMapFragment() {
+                       @Override
+                       public void onActivityCreated(Bundle savedInstanceState) {
+                               super.onActivityCreated(savedInstanceState);
+                               setupMap(map_type);
+                       }
+                       @Override
+                       public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+                               map_view = super.onCreateView(inflater, container, savedInstanceState);
+                               return map_view;
+                       }
+                       @Override
+                       public void onDestroyView() {
+                               super.onDestroyView();
+                               map_view = null;
+                       }
+               };
+       }
+
+//     public void onActivityCreated() {
+//             getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit();
+//     }
+
+       public void setupMap(int map_type) {
+               mMap = mMapFragment.getMap();
+               if (mMap != null) {
+                       set_map_type(map_type);
+                       mMap.setMyLocationEnabled(true);
+                       mMap.getUiSettings().setTiltGesturesEnabled(false);
+                       mMap.getUiSettings().setZoomControlsEnabled(false);
+
+                       mPadMarker = mMap.addMarker(
+                                       new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
+                                                          .position(new LatLng(0,0))
+                                                          .visible(false)
+                                       );
+
+                       mPolyline = mMap.addPolyline(
+                                       new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
+                                                            .width(20)
+                                                            .color(Color.BLUE)
+                                                            .visible(false)
+                                       );
+
+                       mapLoaded = true;
+               }
+       }
+
+       public void center(double lat, double lon, double accuracy) {
+               if (mMap == null)
+                       return;
+
+               if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
+                       mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14));
+                       mapAccuracy = accuracy;
+               }
+       }
+
+       private void set_rocket(int serial, AltosState state) {
+               RocketOnline    rocket;
+
+               if (state.gps == null || state.gps.lat == AltosLib.MISSING)
+                       return;
+
+               if (mMap == null)
+                       return;
+
+               if (rockets.containsKey(serial)) {
+                       rocket = rockets.get(serial);
+                       rocket.set_position(new AltosLatLon(state.gps.lat, state.gps.lon), state.received_time);
+               } else {
+                       rocket = new RocketOnline(context,
+                                                 String.format("%d", serial),
+                                                 mMap, state.gps.lat, state.gps.lon,
+                                                 state.received_time);
+                       rockets.put(serial, rocket);
+               }
+       }
+
+       private void remove_rocket(int serial) {
+               RocketOnline rocket = rockets.get(serial);
+               rocket.remove();
+               rockets.remove(serial);
+       }
+
+       public void set_visible(boolean visible) {
+               if (map_view == null)
+                       return;
+               if (visible)
+                       map_view.setVisibility(View.VISIBLE);
+               else
+                       map_view.setVisibility(View.GONE);
+       }
+
+       public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+
+               if (telem_state != null) {
+                       for (int serial : rockets.keySet()) {
+                               if (!telem_state.states.containsKey(serial))
+                                       remove_rocket(serial);
+                       }
+
+                       for (int serial : telem_state.states.keySet()) {
+                               set_rocket(serial, telem_state.states.get(serial));
+                       }
+               }
+
+               if (state != null) {
+                       if (mapLoaded) {
+                               if (!pad_set && state.pad_lat != AltosLib.MISSING) {
+                                       pad_set = true;
+                                       mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon));
+                                       mPadMarker.setVisible(true);
+                               }
+                       }
+                       if (state.gps != null) {
+
+                               target_position = new AltosLatLon(state.gps.lat, state.gps.lon);
+                               if (state.gps.locked && state.gps.nsat >= 4)
+                                       center (state.gps.lat, state.gps.lon, 10);
+                       }
+               }
+
+               if (receiver != null) {
+                       double accuracy;
+
+                       if (receiver.hasAccuracy())
+                               accuracy = receiver.getAccuracy();
+                       else
+                               accuracy = 1000;
+
+                       my_position = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
+                       center (my_position.lat, my_position.lon, accuracy);
+               }
+
+               if (my_position != null && target_position != null) {
+                       mPolyline.setPoints(Arrays.asList(new LatLng(my_position.lat, my_position.lon), new LatLng(target_position.lat, target_position.lon)));
+                       mPolyline.setVisible(true);
+               }
+
+       }
+
+       public void set_map_type(int map_type) {
+               if (mMap != null) {
+                       if (map_type == AltosMap.maptype_hybrid)
+                               mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
+                       else if (map_type == AltosMap.maptype_satellite)
+                               mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
+                       else if (map_type == AltosMap.maptype_terrain)
+                               mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
+                       else
+                               mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+               }
+       }
+
+       public AltosMapOnline(Context context) {
+               this.context = context;
+       }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapView.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapView.java
deleted file mode 100644 (file)
index 65cc0b9..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright © 2015 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.AltosDroid;
-
-import java.util.*;
-import java.io.*;
-
-import org.altusmetrum.altoslib_7.*;
-
-import android.app.Activity;
-import android.graphics.*;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentTransaction;
-import android.view.*;
-import android.widget.*;
-import android.location.Location;
-import android.content.*;
-import android.util.*;
-
-public class AltosMapView extends View implements ScaleGestureDetector.OnScaleGestureListener {
-       ScaleGestureDetector    scale_detector;
-       boolean                 scaling;
-       TabMapOffline           tab;
-
-       class Line {
-               AltosLatLon     a, b;
-
-               void paint() {
-                       if (a != null && b != null) {
-                               AltosPointDouble        a_screen = tab.map.transform.screen(a);
-                               AltosPointDouble        b_screen = tab.map.transform.screen(b);
-                               tab.paint.setColor(0xff8080ff);
-                               tab.canvas.drawLine((float) a_screen.x, (float) a_screen.y,
-                                                   (float) b_screen.x, (float) b_screen.y,
-                                                   tab.paint);
-                       }
-               }
-
-               void set_a(AltosLatLon a) {
-                       this.a = a;
-               }
-
-               void set_b(AltosLatLon b) {
-                       this.b = b;
-               }
-
-               Line() {
-               }
-       }
-
-       Line line = new Line();
-
-       private void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
-               if (lat_lon != null && tab.map != null && tab.map.transform != null) {
-                       AltosPointInt pt = new AltosPointInt(tab.map.transform.screen(lat_lon));
-
-                       tab.canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, tab.paint);
-               }
-       }
-
-       private void draw_positions() {
-               line.set_a(tab.map.last_position);
-               line.set_b(tab.here);
-               line.paint();
-               draw_bitmap(tab.pad, tab.pad_bitmap, tab.pad_off_x, tab.pad_off_y);
-
-               Rocket[]        rockets = tab.rockets.values().toArray(new Rocket[0]);
-
-               Arrays.sort(rockets);
-               for (Rocket rocket : rockets)
-                       rocket.paint();
-               draw_bitmap(tab.here, tab.here_bitmap, tab.here_off_x, tab.here_off_y);
-       }
-
-       @Override public void invalidate() {
-               Rect r = new Rect();
-               getDrawingRect(r);
-               super.invalidate();
-       }
-
-       @Override public void invalidate(int l, int t, int r, int b) {
-               Rect rect = new Rect();
-               getDrawingRect(rect);
-               super.invalidate();
-       }
-
-       @Override
-       protected void onDraw(Canvas view_canvas) {
-               tab.canvas = view_canvas;
-               tab.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-               tab.paint.setStrokeWidth(tab.stroke_width);
-               tab.paint.setStrokeCap(Paint.Cap.ROUND);
-               tab.paint.setStrokeJoin(Paint.Join.ROUND);
-               tab.paint.setTextSize(40);
-               tab.map.paint();
-               draw_positions();
-               tab.canvas = null;
-       }
-
-       public boolean onScale(ScaleGestureDetector detector) {
-               float   f = detector.getScaleFactor();
-
-               if (f <= 0.8) {
-                       tab.map.set_zoom_centre(tab.map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
-                       return true;
-               }
-               if (f >= 1.2) {
-                       tab.map.set_zoom_centre(tab.map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
-                       return true;
-               }
-               return false;
-       }
-
-       public boolean onScaleBegin(ScaleGestureDetector detector) {
-               return true;
-       }
-
-       public void onScaleEnd(ScaleGestureDetector detector) {
-       }
-
-       @Override
-       public boolean dispatchTouchEvent(MotionEvent event) {
-               scale_detector.onTouchEvent(event);
-
-               if (scale_detector.isInProgress()) {
-                       scaling = true;
-               }
-
-               if (scaling) {
-                       if (event.getAction() == MotionEvent.ACTION_UP) {
-                               scaling = false;
-                       }
-                       return true;
-               }
-
-               if (event.getAction() == MotionEvent.ACTION_DOWN) {
-                       tab.map.touch_start((int) event.getX(), (int) event.getY(), true);
-               } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
-                       tab.map.touch_continue((int) event.getX(), (int) event.getY(), true);
-               }
-               return true;
-       }
-
-       public void set_tab(TabMapOffline tab) {
-               this.tab = tab;
-       }
-
-       public AltosMapView(Context context, AttributeSet attrs) {
-               super(context, attrs);
-               scale_detector = new ScaleGestureDetector(context, this);
-       }
-}
index d5d1d26..ee52d63 100644 (file)
@@ -112,7 +112,6 @@ public class TabMap extends AltosDroidTab {
                                setupMap();
                        }
                };
-
        }
 
        @Override
index 296e212..26e04c8 100644 (file)
@@ -32,58 +32,9 @@ import android.widget.*;
 import android.location.Location;
 import android.content.*;
 
-class Rocket implements Comparable {
-       AltosLatLon     position;
-       String          name;
-       long            last_packet;
-       TabMapOffline   tab;
-
-       void paint() {
-               tab.draw_bitmap(position, tab.rocket_bitmap, tab.rocket_off_x, tab.rocket_off_y);
-               tab.draw_text(position, name, 0, 3*tab.rocket_bitmap.getHeight()/4);
-       }
-
-       void set_position(AltosLatLon position, long last_packet) {
-               this.position = position;
-               this.last_packet = last_packet;
-       }
-
-       Rocket(String name, TabMapOffline tab) {
-               this.name = name;
-               this.tab = tab;
-       }
-
-       public int compareTo(Object o) {
-               Rocket other = (Rocket) o;
-
-               long    diff = last_packet - other.last_packet;
-
-               if (diff > 0)
-                       return 1;
-               if (diff < 0)
-                       return -1;
-               return 0;
-       }
-}
-
-public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
-
-       AltosMap map;
+public class TabMapOffline extends AltosDroidTab {
 
        AltosLatLon     here;
-       AltosLatLon     pad;
-
-       Canvas  canvas;
-       Paint   paint;
-
-       Bitmap  pad_bitmap;
-       int     pad_off_x, pad_off_y;
-       Bitmap  rocket_bitmap;
-       int     rocket_off_x, rocket_off_y;
-       Bitmap  here_bitmap;
-       int     here_off_x, here_off_y;
-
-       private boolean pad_set;
 
        private TextView mDistanceView;
        private TextView mBearingView;
@@ -91,221 +42,14 @@ public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
        private TextView mTargetLongitudeView;
        private TextView mReceiverLatitudeView;
        private TextView mReceiverLongitudeView;
-       private AltosMapView map_view;
-
-       private double mapAccuracy = -1;
-
-       int     stroke_width = 20;
-
-
-       void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
-               if (lat_lon != null && map != null && map.transform != null) {
-                       AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
-
-                       Rect    bounds = new Rect();
-                       paint.getTextBounds(text, 0, text.length(), bounds);
-
-                       int     width = bounds.right - bounds.left;
-                       int     height = bounds.bottom - bounds.top;
-
-                       float x = pt.x;
-                       float y = pt.y;
-                       x = x - width / 2.0f - off_x;
-                       y = y + height / 2.0f - off_y;
-                       paint.setColor(0xff000000);
-                       canvas.drawText(text, 0, text.length(), x, y, paint);
-               }
-       }
-
-       void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
-               if (lat_lon != null && map != null && map.transform != null) {
-                       AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
-
-                       canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
-               }
-       }
-
-       HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
-
-       /* AltosMapInterface */
-
-       static  final int       WHITE = 0xffffffff;
-       static  final int       RED   = 0xffff0000;
-       static  final int       PINK  = 0xffff8080;
-       static  final int       YELLOW= 0xffffff00;
-       static  final int       CYAN  = 0xff00ffff;
-       static  final int       BLUE  = 0xff0000ff;
-       static  final int       BLACK = 0xff000000;
-
-       public static final int stateColors[] = {
-               WHITE,  // startup
-               WHITE,  // idle
-               WHITE,  // pad
-               RED,    // boost
-               PINK,   // fast
-               YELLOW, // coast
-               CYAN,   // drogue
-               BLUE,   // main
-               BLACK,  // landed
-               BLACK,  // invalid
-               CYAN,   // stateless
-       };
-
-       public AltosMapPath new_path() {
-               return null;
-       }
-
-       public AltosMapLine new_line() {
-               return null;
-       }
-
-       class MapImage implements AltosImage {
-               public Bitmap   bitmap;
-
-               public void flush() {
-                       if (bitmap != null) {
-                               bitmap.recycle();
-                               bitmap = null;
-                       }
-               }
-
-               public MapImage(File file) {
-                       bitmap = BitmapFactory.decodeFile(file.getPath());
-               }
-       }
-
-       public AltosImage load_image(File file) throws Exception {
-               return new MapImage(file);
-       }
-
-       class MapMark extends AltosMapMark {
-               public void paint(AltosMapTransform t) {
-               }
-
-               MapMark(double lat, double lon, int state) {
-                       super(lat, lon, state);
-               }
-       }
-
-       public AltosMapMark new_mark(double lat, double lon, int state) {
-               return new MapMark(lat, lon, state);
-       }
-
-       class MapTile extends AltosMapTile {
-               public void paint(AltosMapTransform t) {
-                       AltosPointInt           pt = new AltosPointInt(t.screen(upper_left));
-
-                       if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
-                               return;
-
-                       AltosImage              altos_image = cache.get(this, store, px_size, px_size);
-
-                       MapImage                map_image = (MapImage) altos_image;
-
-                       Bitmap                  bitmap = null;
-
-                       if (map_image != null)
-                               bitmap = map_image.bitmap;
-
-                       if (bitmap != null) {
-                               canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
-                       } else {
-                               paint.setColor(0xff808080);
-                               canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
-                               if (t.has_location()) {
-                                       String  message = null;
-                                       switch (status) {
-                                       case AltosMapTile.loading:
-                                               message = "Loading...";
-                                               break;
-                                       case AltosMapTile.bad_request:
-                                               message = "Internal error";
-                                               break;
-                                       case AltosMapTile.failed:
-                                               message = "Network error, check connection";
-                                               break;
-                                       case AltosMapTile.forbidden:
-                                               message = "Too many requests, try later";
-                                               break;
-                                       }
-                                       if (message != null) {
-                                               Rect    bounds = new Rect();
-                                               paint.getTextBounds(message, 0, message.length(), bounds);
-
-                                               int     width = bounds.right - bounds.left;
-                                               int     height = bounds.bottom - bounds.top;
-
-                                               float x = pt.x + px_size / 2.0f;
-                                               float y = pt.y + px_size / 2.0f;
-                                               x = x - width / 2.0f;
-                                               y = y + height / 2.0f;
-                                               paint.setColor(0xff000000);
-                                               canvas.drawText(message, 0, message.length(), x, y, paint);
-                                       }
-                               }
-                       }
-               }
-
-               public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
-                       super(listener, upper_left, center, zoom, maptype, px_size, 2);
-               }
-
-       }
-
-       public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
-               return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
-       }
-
-       public int width() {
-               if (map_view != null)
-                       return map_view.getWidth();
-               return 500;
-       }
-
-       public int height() {
-               if (map_view != null)
-                       return map_view.getHeight();
-               return 500;
-       }
-
-       public void repaint() {
-               if (map_view != null)
-                       map_view.postInvalidate();
-       }
-
-       public void repaint(AltosRectangle damage) {
-               if (map_view != null)
-                       map_view.postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
-       }
-
-       public void set_zoom_label(String label) {
-       }
-
-       public void debug(String format, Object ... arguments) {
-               AltosDebug.debug(format, arguments);
-       }
+       private AltosMapOffline map_offline;
+       private AltosMapOnline map_online;
+       private View view;
+       private int map_source;
 
        @Override
        public void onAttach(Activity activity) {
                super.onAttach(activity);
-
-               map = new AltosMap(this);
-               map.set_maptype(altos_droid.map_type);
-
-               pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
-               /* arrow at the bottom of the launchpad image */
-               pad_off_x = pad_bitmap.getWidth() / 2;
-               pad_off_y = pad_bitmap.getHeight();
-
-               rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
-               /* arrow at the bottom of the rocket image */
-               rocket_off_x = rocket_bitmap.getWidth() / 2;
-               rocket_off_y = rocket_bitmap.getHeight();
-
-               here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
-               /* Center of the dot */
-               here_off_x = here_bitmap.getWidth() / 2;
-               here_off_y = here_bitmap.getHeight() / 2;
        }
 
        @Override
@@ -313,42 +57,54 @@ public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
                super.onCreate(savedInstanceState);
        }
 
+       private void make_offline_map() {
+       }
+
+       private void make_online_map() {
+               map_online = new AltosMapOnline(view.getContext());
+               map_online.onCreateView(altos_droid.map_type);
+       }
+
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-               View v = inflater.inflate(R.layout.tab_map_offline, container, false);
-
-               map_view = (AltosMapView)v.findViewById(R.id.map_view_offline);
-               map_view.set_tab(this);
-               mDistanceView  = (TextView)v.findViewById(R.id.distance_value_offline);
-               mBearingView   = (TextView)v.findViewById(R.id.bearing_value_offline);
-               mTargetLatitudeView  = (TextView)v.findViewById(R.id.target_lat_value_offline);
-               mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value_offline);
-               mReceiverLatitudeView  = (TextView)v.findViewById(R.id.receiver_lat_value_offline);
-               mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value_offline);
-               return v;
+               view = inflater.inflate(R.layout.tab_map_offline, container, false);
+               int map_source = AltosDroidPreferences.map_source();
+
+               mDistanceView  = (TextView)view.findViewById(R.id.distance_value_offline);
+               mBearingView   = (TextView)view.findViewById(R.id.bearing_value_offline);
+               mTargetLatitudeView  = (TextView)view.findViewById(R.id.target_lat_value_offline);
+               mTargetLongitudeView = (TextView)view.findViewById(R.id.target_lon_value_offline);
+               mReceiverLatitudeView  = (TextView)view.findViewById(R.id.receiver_lat_value_offline);
+               mReceiverLongitudeView = (TextView)view.findViewById(R.id.receiver_lon_value_offline);
+               map_offline = (AltosMapOffline)view.findViewById(R.id.map_view_offline);
+               map_offline.onCreateView(altos_droid.map_type);
+               map_online = new AltosMapOnline(view.getContext());
+               map_online.onCreateView(altos_droid.map_type);
+               set_map_source(AltosDroidPreferences.map_source());
+               return view;
        }
 
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
                super.onActivityCreated(savedInstanceState);
+               if (map_online != null)
+                       getChildFragmentManager().beginTransaction().add(R.id.map_online, map_online.mMapFragment).commit();
        }
 
        @Override
        public void onDestroyView() {
                super.onDestroyView();
-
        }
 
+       public String tab_name() { return "offmap"; }
+
        private void center(double lat, double lon, double accuracy) {
-               if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
-                       if (map != null)
-                               map.maybe_centre(lat, lon);
-                       mapAccuracy = accuracy;
-               }
+               if (map_offline != null)
+                       map_offline.center(lat, lon, accuracy);
+               if (map_online != null)
+                       map_online.center(lat, lon, accuracy);
        }
 
-       public String tab_name() { return "offmap"; }
-
        public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
                if (from_receiver != null) {
                        mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
@@ -356,40 +112,9 @@ public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
                }
 
                if (state != null) {
-                       map.show(state, null);
                        if (state.gps != null) {
                                mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
                                mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
-                               if (state.gps.locked && state.gps.nsat >= 4)
-                                       center (state.gps.lat, state.gps.lon, 10);
-                       }
-                       if (state.pad_lat != AltosLib.MISSING && pad == null)
-                               pad = new AltosLatLon(state.pad_lat, state.pad_lon);
-               }
-
-               if (telem_state != null) {
-                       Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
-                       Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
-
-                       /* remove deleted keys */
-                       for (int serial : old_serial) {
-                               if (!telem_state.states.containsKey(serial))
-                                       rockets.remove(serial);
-                       }
-
-                       /* set remaining keys */
-
-                       for (int serial : new_serial) {
-                               Rocket          rocket;
-                               AltosState      t_state = telem_state.states.get(serial);
-                               if (rockets.containsKey(serial))
-                                       rocket = rockets.get(serial);
-                               else {
-                                       rocket = new Rocket(String.format("%d", serial), this);
-                                       rockets.put(serial, rocket);
-                               }
-                               if (t_state.gps != null)
-                                       rocket.set_position(new AltosLatLon(t_state.gps.lat, t_state.gps.lon), t_state.received_time);
                        }
                }
 
@@ -405,13 +130,41 @@ public class TabMapOffline extends AltosDroidTab implements AltosMapInterface {
                        mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
                        center (here.lat, here.lon, accuracy);
                }
-
+               if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
+                       if (map_offline != null)
+                               map_offline.show(telem_state, state, from_receiver, receiver);
+               } else {
+                       if (map_online != null)
+                               map_online.show(telem_state, state, from_receiver, receiver);
+               }
        }
 
        @Override
        public void set_map_type(int map_type) {
-               if (map != null)
-                       map.set_maptype(map_type);
+               if (map_offline != null)
+                       map_offline.set_map_type(map_type);
+               if (map_online != null)
+                       map_online.set_map_type(map_type);
+       }
+
+       @Override
+       public void set_map_source(int map_source) {
+               this.map_source = map_source;
+               if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
+                       if (map_online != null)
+                               map_online.set_visible(false);
+                       if (map_offline != null) {
+                               map_offline.set_visible(true);
+                               map_offline.show(last_telem_state, last_state, last_from_receiver, last_receiver);
+                       }
+               } else {
+                       if (map_offline != null)
+                               map_offline.set_visible(false);
+                       if (map_online != null) {
+                               map_online.set_visible(true);
+                               map_online.show(last_telem_state, last_state, last_from_receiver, last_receiver);
+                       }
+               }
        }
 
        public TabMapOffline() {