X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=blobdiff_plain;f=altosdroid%2Fapp%2Fsrc%2Fmain%2Fjava%2Forg%2Faltusmetrum%2FAltosDroid%2FAltosMapOffline.java;fp=altosdroid%2Fapp%2Fsrc%2Fmain%2Fjava%2Forg%2Faltusmetrum%2FAltosDroid%2FAltosMapOffline.java;h=1aebcd3656fbfd62fc837354b1655fecc1d1a08f;hp=0000000000000000000000000000000000000000;hb=8b53f860eb3171cd43e4bd0e440f2889bd810662;hpb=4a257455b2dc57069c41e1845daf66239c5cbe1d diff --git a/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosMapOffline.java b/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosMapOffline.java new file mode 100644 index 00000000..1aebcd36 --- /dev/null +++ b/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosMapOffline.java @@ -0,0 +1,532 @@ +/* + * Copyright © 2015 Keith Packard + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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_13.*; + +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; + int serial; + long last_packet; + boolean active; + 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; + } + + void set_active(boolean active) { + this.active = active; + } + + public int compareTo(Object o) { + Rocket other = (Rocket) o; + + if (active && !other.active) + return 1; + if (other.active && !active) + return -1; + + long diff = last_packet - other.last_packet; + + if (diff > 0) + return 1; + if (diff < 0) + return -1; + return 0; + } + + Rocket(int serial, AltosMapOffline map_offline) { + this.serial = serial; + this.name = String.format("%d", serial); + this.map_offline = map_offline; + } +} + +public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface, AltosMapTypeListener { + ScaleGestureDetector scale_detector; + boolean scaling; + AltosMap map; + AltosDroid altos_droid; + + static int scale = 1; + + AltosLatLon here; + AltosLatLon there; + 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 = this.get_image(); + + 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.fetching: + message = "Fetching..."; + break; + case AltosMapTile.bad_request: + message = "Internal error"; + break; + case AltosMapTile.failed: + message = "Network error"; + break; + case AltosMapTile.forbidden: + message = "Outside of known launch areas"; + 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(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) { + super(cache, upper_left, center, zoom, maptype, px_size, scale); + } + + } + + public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) { + return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale); + } + + 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) { + } + + public void select_object(AltosLatLon latlon) { + if (map.transform == null) + return; + ArrayList near = new ArrayList(); + + for (Rocket rocket : sorted_rockets()) { + if (rocket.position == null) { + debug("rocket %d has no position\n", rocket.serial); + continue; + } + double distance = map.transform.hypot(latlon, rocket.position); + debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth()); + if (distance < rocket_bitmap.getWidth() * 2.0) { + debug("selecting %d\n", rocket.serial); + near.add(rocket.serial); + } + } + if (near.size() != 0) + altos_droid.touch_trackers(near.toArray(new Integer[0])); + } + + 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 rockets = new HashMap(); + + 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 Rocket[] sorted_rockets() { + Rocket[] rocket_array = rockets.values().toArray(new Rocket[0]); + + Arrays.sort(rocket_array); + return rocket_array; + } + + private void draw_positions() { + line.set_a(there); + line.set_b(here); + line.paint(); + draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y); + + for (Rocket rocket : sorted_rockets()) + 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) { + 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); + } else if (event.getAction() == MotionEvent.ACTION_UP) { + map.touch_stop((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 || (map != null && !map.has_centre())) { + 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) { + boolean changed = false; + + 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(serial, this); + rockets.put(serial, rocket); + } + if (t_state.gps != null) { + AltosLatLon latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon); + rocket.set_position(latlon, t_state.received_time); + if (state.cal_data().serial == serial) + there = latlon; + } + if (state != null) + rocket.set_active(state.cal_data().serial == serial); + } + } + if (receiver != null) { + AltosLatLon new_here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude()); + if (!new_here.equals(here)) { + here = new_here; + AltosDebug.debug("Location changed, redraw"); + repaint(); + } + } + } + + public void onCreateView(AltosDroid altos_droid) { + this.altos_droid = altos_droid; + map = new AltosMap(this, scale); + AltosPreferences.register_map_type_listener(this); + map.set_maptype(AltosPreferences.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 onDestroyView() { + AltosPreferences.unregister_map_type_listener(this); + } + + public void map_type_changed(int map_type) { + if (map != null) + map.set_maptype(map_type); + } + + public AltosMapOffline(Context context, AttributeSet attrs) { + super(context, attrs); + this.altos_droid = altos_droid; + scale_detector = new ScaleGestureDetector(context, this); + } +}