]> git.gag.com Git - fw/altos/blob - altoslib/AltosMapStore.java
doc: Add 1.9.22 release notes
[fw/altos] / altoslib / AltosMapStore.java
1 /*
2  * Copyright © 2014 Keith Packard <keithp@keithp.com>
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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.altoslib_14;
20
21 import java.io.*;
22 import java.net.*;
23 import java.util.*;
24
25 public class AltosMapStore {
26         String                                  url;
27         public File                             file;
28         LinkedList<AltosMapStoreListener>       listeners = new LinkedList<AltosMapStoreListener>();
29
30         int                                     status;
31
32         private static File map_file(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
33                 double lat = center.lat;
34                 double lon = center.lon;
35                 char chlat = lat < 0 ? 'S' : 'N';
36                 char chlon = lon < 0 ? 'W' : 'E';
37
38                 if (lat < 0) lat = -lat;
39                 if (lon < 0) lon = -lon;
40                 String maptype_string = String.format("%s-", AltosMap.maptype_names[maptype]);
41                 String format_string;
42                 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
43                         format_string = "jpg";
44                 else
45                         format_string = "png";
46                 return new File(AltosPreferences.mapdir(),
47                                 String.format("map-%c%.6f,%c%.6f-%s%d%s.%s",
48                                               chlat, lat, chlon, lon, maptype_string, zoom, scale == 1 ? "" : String.format("-%d", scale), format_string));
49         }
50
51         public static String google_maps_api_key = null;
52
53         private static String google_map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale, String format_string) {
54                 return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&scale=%d&sensor=false&maptype=%s&format=%s&key=%s",
55                                      center.lat, center.lon, zoom, px_size, px_size, scale,
56                                      AltosMap.maptype_names[maptype], format_string, google_maps_api_key);
57         }
58
59         private static String altos_map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale, String format_string) {
60                 return String.format("https://maps.altusmetrum.org/cgi-bin/altos-map?lat=%.6f&lon=%.6f&zoom=%d",
61                                      center.lat, center.lon, zoom);
62         }
63
64         private static String map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
65                 String format_string;
66
67                 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
68                         format_string = "jpg";
69                 else
70                         format_string = "png32";
71
72                 for (int s = 1; s < scale; s <<= 1)
73                         zoom--;
74
75                 px_size /= scale;
76
77                 if (google_maps_api_key != null)
78                         return google_map_url(center, zoom, maptype, px_size, scale, format_string);
79                 else
80                         return altos_map_url(center, zoom, maptype, px_size, scale, format_string);
81         }
82
83         public synchronized int status() {
84                 return status;
85         }
86
87         public synchronized void add_listener(AltosMapStoreListener listener) {
88                 if (!listeners.contains(listener))
89                         listeners.add(listener);
90                 listener.notify_store(this, status);
91         }
92
93         public synchronized void remove_listener(AltosMapStoreListener listener) {
94                 listeners.remove(listener);
95         }
96
97         private synchronized void notify_listeners(int status) {
98                 this.status = status;
99                 for (AltosMapStoreListener listener : listeners)
100                         listener.notify_store(this, status);
101         }
102
103         private int fetch_url() {
104                 URL u;
105
106                 try {
107                         u = new URL(url);
108                 } catch (java.net.MalformedURLException e) {
109                         return AltosMapTile.bad_request;
110                 }
111
112                 byte[] data = null;
113                 URLConnection uc = null;
114
115                 int status = AltosMapTile.failed;
116                 int tries = 0;
117
118                 while (tries < 10 && status != AltosMapTile.fetched) {
119                         try {
120                                 uc = u.openConnection();
121                                 String type = uc.getContentType();
122                                 int contentLength = uc.getContentLength();
123                                 if (uc instanceof HttpURLConnection) {
124                                         int response = ((HttpURLConnection) uc).getResponseCode();
125                                         switch (response) {
126                                         case HttpURLConnection.HTTP_FORBIDDEN:
127                                         case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
128                                         case HttpURLConnection.HTTP_UNAUTHORIZED:
129                                                 return AltosMapTile.forbidden;
130                                         }
131                                 }
132                                 InputStream in = new BufferedInputStream(uc.getInputStream());
133                                 int bytesRead = 0;
134                                 byte[] buffer = new byte[4096];
135                                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
136                                 while ((bytesRead = in.read(buffer)) != -1) {
137                                         baos.write(buffer, 0, bytesRead);
138                                 }
139                                 in.close();
140                                 data = baos.toByteArray();
141
142                                 status = AltosMapTile.fetched;
143
144                         } catch (IOException e) {
145                                 status = AltosMapTile.failed;
146                         }
147
148                         if (status != AltosMapTile.fetched) {
149                                 try {
150                                         Thread.sleep(100);
151                                 } catch (InterruptedException ie) {
152                                 }
153                                 tries++;
154                                 System.out.printf("Fetch failed, retrying %d\n", tries);
155                         }
156                 }
157
158                 if (status != AltosMapTile.fetched)
159                         return status;
160
161                 try {
162                         FileOutputStream out = new FileOutputStream(file);
163                         if (data != null)
164                                 out.write(data);
165                         out.flush();
166                         out.close();
167                 } catch (FileNotFoundException e) {
168                         return AltosMapTile.bad_request;
169                 } catch (IOException e) {
170                         if (file.exists())
171                                 file.delete();
172                         return AltosMapTile.bad_request;
173                 }
174                 return AltosMapTile.fetched;
175         }
176
177         static Object   fetch_lock = new Object();
178
179         static Object   fetcher_lock = new Object();
180
181         static LinkedList<AltosMapStore> waiting = new LinkedList<AltosMapStore>();
182         static LinkedList<AltosMapStore> running = new LinkedList<AltosMapStore>();
183
184         static int concurrent_fetchers() {
185                 if (google_maps_api_key == null)
186                         return 16;
187                 return 128;
188         }
189
190         static void start_fetchers() {
191                 while (!waiting.isEmpty() && running.size() < concurrent_fetchers()) {
192                         AltosMapStore   s = waiting.remove();
193                         running.add(s);
194                         Thread lt = s.make_fetcher_thread();
195                         lt.start();
196                 }
197         }
198
199         void finish_fetcher() {
200                 synchronized(fetcher_lock) {
201                         running.remove(this);
202                         start_fetchers();
203                 }
204         }
205
206         void add_fetcher() {
207                 synchronized(fetcher_lock) {
208                         waiting.add(this);
209                         start_fetchers();
210                 }
211         }
212
213         class fetcher implements Runnable {
214
215                 public void run() {
216                         try {
217                                 if (file.exists()) {
218                                         notify_listeners(AltosMapTile.fetched);
219                                         return;
220                                 }
221
222                                 int new_status;
223
224                                 new_status = fetch_url();
225
226                                 notify_listeners(new_status);
227                         } finally {
228                                 finish_fetcher();
229                         }
230                 }
231         }
232
233         private Thread make_fetcher_thread() {
234                 return new Thread(new fetcher());
235         }
236
237         private void fetch() {
238                 add_fetcher();
239         }
240
241         private AltosMapStore (String url, File file) {
242                 this.url = url;
243                 this.file = file;
244
245                 if (file.exists())
246                         status = AltosMapTile.fetched;
247                 else {
248                         status = AltosMapTile.fetching;
249                         fetch();
250                 }
251         }
252
253         public int hashCode() {
254                 return url.hashCode();
255         }
256
257         public boolean equals(Object o) {
258                 if (o == null)
259                         return false;
260
261                 if (!(o instanceof AltosMapStore))
262                         return false;
263
264                 AltosMapStore other = (AltosMapStore) o;
265                 return url.equals(other.url);
266         }
267
268         static HashMap<String,AltosMapStore> stores = new HashMap<String,AltosMapStore>();
269
270         public static AltosMapStore get(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
271                 String url = map_url(center, zoom, maptype, px_size, scale);
272
273                 AltosMapStore   store;
274                 synchronized(stores) {
275                         if (stores.containsKey(url)) {
276                                 store = stores.get(url);
277                         } else {
278                                 store = new AltosMapStore(url, map_file(center, zoom, maptype, px_size, scale));
279                                 stores.put(url, store);
280                         }
281                 }
282                 return store;
283         }
284
285 }