altosuilib: Get rid of AltosUIVersion.java
[fw/altos] / altoslib / AltosMap.java
1 /*
2  * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.altoslib_7;
19
20 import java.io.*;
21 import java.lang.*;
22 import java.util.*;
23 import java.util.concurrent.*;
24
25 public class AltosMap implements AltosMapTileListener, AltosMapStoreListener {
26
27         public static final int px_size = 512;
28
29         public static final int maptype_hybrid = 0;
30         public static final int maptype_roadmap = 1;
31         public static final int maptype_satellite = 2;
32         public static final int maptype_terrain = 3;
33         public static final int maptype_default = maptype_hybrid;
34
35         public static final int default_zoom = 15;
36         public static final int min_zoom = 3;
37         public static final int max_zoom = 21;
38
39         public static final String[] maptype_names = {
40                 "hybrid",
41                 "roadmap",
42                 "satellite",
43                 "terrain"
44         };
45
46         public static final String[] maptype_labels = {
47                 "Hybrid",
48                 "Roadmap",
49                 "Satellite",
50                 "Terrain"
51         };
52
53         AltosMapInterface       map_interface;
54
55         AltosMapCache           cache;
56
57         public AltosMapCache cache() { return cache; }
58
59         LinkedList<AltosMapMark> marks = new LinkedList<AltosMapMark>();
60
61         AltosMapPath    path;
62         AltosMapLine    line;
63
64         boolean         have_boost = false;
65         boolean         have_landed = false;
66
67         ConcurrentHashMap<AltosPointInt,AltosMapTile> tiles = new ConcurrentHashMap<AltosPointInt,AltosMapTile>();
68         int             load_radius;
69         AltosLatLon     load_centre = null;
70         AltosMapTileListener    load_listener;
71
72         int             zoom = AltosMap.default_zoom;
73         int             maptype = AltosMap.maptype_default;
74
75         long            user_input_time;
76
77         /* Milliseconds to wait after user action before auto-scrolling
78          */
79         static final long auto_scroll_delay = 20 * 1000;
80
81         public AltosMapTransform        transform;
82         AltosLatLon             centre;
83
84         public void reset() {
85                 // nothing
86         }
87
88         /* MapInterface wrapping functions */
89
90         public void repaint(int x, int y, int w, int h) {
91                 map_interface.repaint(new AltosRectangle(x, y, w, h));
92         }
93
94         public void repaint(AltosMapRectangle damage, int pad) {
95                 AltosRectangle r = transform.screen(damage);
96                 repaint(r.x - pad, r.y - pad, r.width + pad * 2, r.height + pad * 2);
97         }
98
99         public void repaint() {
100                 map_interface.repaint();
101         }
102
103         public int width() {
104                 return map_interface.width();
105         }
106
107         public int height() {
108                 return map_interface.height();
109         }
110
111         public AltosPointInt floor(AltosPointDouble point) {
112                 return new AltosPointInt ((int) Math.floor(point.x / AltosMap.px_size) * AltosMap.px_size,
113                                               (int) Math.floor(point.y / AltosMap.px_size) * AltosMap.px_size);
114         }
115
116         public AltosPointInt ceil(AltosPointDouble point) {
117                 return new AltosPointInt ((int) Math.ceil(point.x / AltosMap.px_size) * AltosMap.px_size,
118                                               (int) Math.ceil(point.y / AltosMap.px_size) * AltosMap.px_size);
119         }
120
121         public void notice_user_input() {
122                 user_input_time = System.currentTimeMillis();
123         }
124
125         public boolean recent_user_input() {
126                 return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay;
127         }
128
129         public boolean far_from_centre(AltosLatLon lat_lon) {
130
131                 if (centre == null || transform == null)
132                         return true;
133
134                 AltosPointDouble        screen = transform.screen(lat_lon);
135
136                 int             width = width();
137                 int             dx = Math.abs ((int) (double) screen.x - width/2);
138
139                 if (dx > width / 4)
140                         return true;
141
142                 int             height = height();
143                 int             dy = Math.abs ((int) (double) screen.y - height/2);
144
145                 if (dy > height / 4)
146                         return true;
147
148                 return false;
149         }
150
151         public void set_transform() {
152                 transform = new AltosMapTransform(width(), height(), zoom, centre);
153                 repaint();
154         }
155
156         private void set_zoom_label() {
157                 map_interface.set_zoom_label(String.format("Zoom %d", get_zoom() - default_zoom));
158         }
159
160
161         public boolean set_zoom(int zoom) {
162                 if (AltosMap.min_zoom <= zoom && zoom <= AltosMap.max_zoom && zoom != this.zoom) {
163                         this.zoom = zoom;
164                         tiles.clear();
165                         set_transform();
166                         set_zoom_label();
167                         return true;
168                 }
169                 return false;
170         }
171
172         public int get_zoom() {
173                 return zoom;
174         }
175
176         public boolean set_maptype(int maptype) {
177                 if (maptype != this.maptype) {
178                         this.maptype = maptype;
179                         tiles.clear();
180                         repaint();
181                         return true;
182                 }
183                 return false;
184         }
185
186         public void show(AltosState state, AltosListenerState listener_state) {
187
188                 /* If insufficient gps data, nothing to update
189                  */
190                 AltosGPS        gps = state.gps;
191
192                 if (gps == null)
193                         return;
194
195                 if (!gps.locked && gps.nsat < 4)
196                         return;
197
198                 AltosMapRectangle       damage = path.add(gps.lat, gps.lon, state.state);
199
200                 switch (state.state) {
201                 case AltosLib.ao_flight_boost:
202                         if (!have_boost) {
203                                 add_mark(gps.lat, gps.lon, state.state);
204                                 have_boost = true;
205                         }
206                         break;
207                 case AltosLib.ao_flight_landed:
208                         if (!have_landed) {
209                                 add_mark(gps.lat, gps.lon, state.state);
210                                 have_landed = true;
211                         }
212                         break;
213                 }
214
215                 if (damage != null)
216                         repaint(damage, AltosMapPath.stroke_width);
217                 maybe_centre(gps.lat, gps.lon);
218         }
219
220         public void centre(AltosLatLon lat_lon) {
221                 centre = lat_lon;
222                 set_transform();
223         }
224
225         public void centre(double lat, double lon) {
226                 centre(new AltosLatLon(lat, lon));
227         }
228
229         public void centre(AltosState state) {
230                 if (!state.gps.locked && state.gps.nsat < 4)
231                         return;
232                 centre(state.gps.lat, state.gps.lon);
233         }
234
235         public void maybe_centre(double lat, double lon) {
236                 AltosLatLon     lat_lon = new AltosLatLon(lat, lon);
237                 if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
238                         centre(lat_lon);
239         }
240
241         public void add_mark(double lat, double lon, int state) {
242                 synchronized(marks) {
243                         marks.add(map_interface.new_mark(lat, lon, state));
244                 }
245                 repaint();
246         }
247
248         public void clear_marks() {
249                 synchronized(marks) {
250                         marks.clear();
251                 }
252         }
253
254         private void make_tiles() {
255                 AltosPointInt   upper_left;
256                 AltosPointInt   lower_right;
257
258                 if (load_centre != null) {
259                         AltosPointInt centre = floor(transform.point(load_centre));
260
261                         upper_left = new AltosPointInt(centre.x - load_radius * AltosMap.px_size,
262                                                                centre.y - load_radius * AltosMap.px_size);
263                         lower_right = new AltosPointInt(centre.x + load_radius * AltosMap.px_size,
264                                                                 centre.y + load_radius * AltosMap.px_size);
265                 } else {
266                         upper_left = floor(transform.screen_point(new AltosPointInt(0, 0)));
267                         lower_right = floor(transform.screen_point(new AltosPointInt(width(), height())));
268                 }
269                 LinkedList<AltosPointInt> to_remove = new LinkedList<AltosPointInt>();
270
271                 for (AltosPointInt point : tiles.keySet()) {
272                         if (point.x < upper_left.x || lower_right.x < point.x ||
273                             point.y < upper_left.y || lower_right.y < point.y) {
274                                 to_remove.add(point);
275                         }
276                 }
277
278                 for (AltosPointInt point : to_remove)
279                         tiles.remove(point);
280
281                 cache.set_cache_size((width() / AltosMap.px_size + 2) * (height() / AltosMap.px_size + 2));
282                 for (int y = (int) upper_left.y; y <= lower_right.y; y += AltosMap.px_size) {
283                         for (int x = (int) upper_left.x; x <= lower_right.x; x += AltosMap.px_size) {
284                                 AltosPointInt point = new AltosPointInt(x, y);
285
286                                 if (!tiles.containsKey(point)) {
287                                         AltosLatLon     ul = transform.lat_lon(new AltosPointDouble(x, y));
288                                         AltosLatLon     center = transform.lat_lon(new AltosPointDouble(x + AltosMap.px_size/2, y + AltosMap.px_size/2));
289                                         AltosMapTile tile = map_interface.new_tile(this, ul, center, zoom, maptype,
290                                                                                    AltosMap.px_size);
291                                         tiles.put(point, tile);
292                                 }
293                         }
294                 }
295         }
296
297         public void set_load_params(double lat, double lon, int radius, AltosMapTileListener listener) {
298                 load_centre = new AltosLatLon(lat, lon);
299                 load_radius = radius;
300                 load_listener = listener;
301                 centre(lat, lon);
302                 make_tiles();
303                 for (AltosMapTile tile : tiles.values()) {
304                         tile.add_store_listener(this);
305                         if (tile.store_status() != AltosMapTile.loading)
306                                 listener.notify_tile(tile, tile.store_status());
307                 }
308                 repaint();
309         }
310
311         public String getName() {
312                 return "Map";
313         }
314
315         public void paint() {
316                 make_tiles();
317
318                 for (AltosMapTile tile : tiles.values())
319                         tile.paint(transform);
320
321                 synchronized(marks) {
322                         for (AltosMapMark mark : marks)
323                                 mark.paint(transform);
324                 }
325
326                 path.paint(transform);
327
328                 line.paint(transform);
329         }
330
331         /* AltosMapTileListener methods */
332         public synchronized void notify_tile(AltosMapTile tile, int status) {
333                 for (AltosPointInt point : tiles.keySet()) {
334                         if (tile == tiles.get(point)) {
335                                 AltosPointInt   screen = transform.screen(point);
336                                 repaint(screen.x, screen.y, AltosMap.px_size, AltosMap.px_size);
337                         }
338                 }
339         }
340
341         /* AltosMapStoreListener methods */
342         public synchronized void notify_store(AltosMapStore store, int status) {
343                 if (load_listener != null) {
344                         for (AltosMapTile tile : tiles.values())
345                                 if (store.equals(tile.store))
346                                         load_listener.notify_tile(tile, status);
347                 }
348         }
349
350         /* UI elements */
351
352         AltosPointInt   drag_start;
353
354         private void drag(int x, int y) {
355                 if (drag_start == null)
356                         return;
357
358                 int dx = x - drag_start.x;
359                 int dy = y - drag_start.y;
360
361                 AltosLatLon new_centre = transform.screen_lat_lon(new AltosPointInt(width() / 2 - dx, height() / 2 - dy));
362                 centre(new_centre);
363                 drag_start = new AltosPointInt(x, y);
364         }
365
366         private void drag_start(int x, int y) {
367                 drag_start = new AltosPointInt(x, y);
368         }
369
370         private void line_start(int x, int y) {
371                 line.pressed(new AltosPointInt(x, y), transform);
372                 repaint();
373         }
374
375         private void line(int x, int y) {
376                 line.dragged(new AltosPointInt(x, y), transform);
377                 repaint();
378         }
379
380         public void touch_start(int x, int y, boolean is_drag) {
381                 notice_user_input();
382                 if (is_drag)
383                         drag_start(x, y);
384                 else
385                         line_start(x, y);
386         }
387
388         public void touch_continue(int x, int y, boolean is_drag) {
389                 notice_user_input();
390                 if (is_drag)
391                         drag(x, y);
392                 else
393                         line(x, y);
394         }
395
396         public AltosMap(AltosMapInterface map_interface) {
397                 this.map_interface = map_interface;
398                 cache = new AltosMapCache(map_interface);
399                 line = map_interface.new_line();
400                 path = map_interface.new_path();
401                 set_zoom_label();
402                 centre(0, 0);
403         }
404 }