2 * Copyright © 2014 Keith Packard <keithp@keithp.com>
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.
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.
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.
19 package org.altusmetrum.altoslib_12;
25 public class AltosMapStore {
28 LinkedList<AltosMapStoreListener> listeners = new LinkedList<AltosMapStoreListener>();
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';
38 if (lat < 0) lat = -lat;
39 if (lon < 0) lon = -lon;
40 String maptype_string = String.format("%s-", AltosMap.maptype_names[maptype]);
42 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
43 format_string = "jpg";
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));
51 private static String map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
55 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
56 format_string = "jpg";
58 format_string = "png32";
60 for (int s = 1; s < scale; s <<= 1)
63 if (AltosVersion.has_google_maps_api_key())
64 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",
65 center.lat, center.lon, z, px_size/scale, px_size/scale, scale, AltosMap.maptype_names[maptype], format_string, AltosVersion.google_maps_api_key);
67 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",
68 center.lat, center.lon, z, px_size/scale, px_size/scale, AltosMap.maptype_names[maptype], format_string);
75 public synchronized void add_listener(AltosMapStoreListener listener) {
76 if (!listeners.contains(listener))
77 listeners.add(listener);
78 listener.notify_store(this, status);
81 public synchronized void remove_listener(AltosMapStoreListener listener) {
82 listeners.remove(listener);
85 private synchronized void notify_listeners(int status) {
87 for (AltosMapStoreListener listener : listeners)
88 listener.notify_store(this, status);
91 static Object forbidden_lock = new Object();
92 static long forbidden_time;
93 static boolean forbidden_set;
94 public static int forbidden_response;
96 private int fetch_url() {
101 } catch (java.net.MalformedURLException e) {
102 return AltosMapTile.bad_request;
106 URLConnection uc = null;
108 uc = u.openConnection();
109 String type = uc.getContentType();
110 int contentLength = uc.getContentLength();
111 if (uc instanceof HttpURLConnection) {
112 int response = ((HttpURLConnection) uc).getResponseCode();
114 case HttpURLConnection.HTTP_FORBIDDEN:
115 case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
116 case HttpURLConnection.HTTP_UNAUTHORIZED:
117 synchronized (forbidden_lock) {
118 forbidden_time = System.nanoTime();
119 forbidden_set = true;
120 forbidden_response = response;
121 return AltosMapTile.forbidden;
125 InputStream in = new BufferedInputStream(uc.getInputStream());
128 data = new byte[contentLength];
129 while (offset < contentLength) {
130 bytesRead = in.read(data, offset, data.length - offset);
137 if (offset != contentLength)
138 return AltosMapTile.failed;
140 } catch (IOException e) {
141 return AltosMapTile.failed;
145 FileOutputStream out = new FileOutputStream(file);
149 } catch (FileNotFoundException e) {
150 return AltosMapTile.bad_request;
151 } catch (IOException e) {
154 return AltosMapTile.bad_request;
156 return AltosMapTile.fetched;
159 static Object fetch_lock = new Object();
161 static final long forbidden_interval = 60l * 1000l * 1000l * 1000l;
162 static final long google_maps_ratelimit_ms = 1200;
164 static Object fetcher_lock = new Object();
166 static LinkedList<AltosMapStore> waiting = new LinkedList<AltosMapStore>();
167 static LinkedList<AltosMapStore> running = new LinkedList<AltosMapStore>();
169 static final int concurrent_fetchers = 128;
171 static void start_fetchers() {
172 while (!waiting.isEmpty() && running.size() < concurrent_fetchers) {
173 AltosMapStore s = waiting.remove();
175 Thread lt = s.make_fetcher_thread();
180 void finish_fetcher() {
181 synchronized(fetcher_lock) {
182 running.remove(this);
188 synchronized(fetcher_lock) {
194 class fetcher implements Runnable {
199 notify_listeners(AltosMapTile.fetched);
203 synchronized(forbidden_lock) {
204 if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) {
205 notify_listeners(AltosMapTile.forbidden);
212 if (!AltosVersion.has_google_maps_api_key()) {
213 synchronized (fetch_lock) {
214 long startTime = System.nanoTime();
215 new_status = fetch_url();
216 if (new_status == AltosMapTile.fetched) {
217 long duration_ms = (System.nanoTime() - startTime) / 1000000;
218 if (duration_ms < google_maps_ratelimit_ms) {
220 Thread.sleep(google_maps_ratelimit_ms - duration_ms);
221 } catch (InterruptedException e) {
222 Thread.currentThread().interrupt();
228 new_status = fetch_url();
230 notify_listeners(new_status);
237 private Thread make_fetcher_thread() {
238 return new Thread(new fetcher());
241 private void fetch() {
245 private AltosMapStore (String url, File file) {
250 status = AltosMapTile.fetched;
252 status = AltosMapTile.fetching;
257 public int hashCode() {
258 return url.hashCode();
261 public boolean equals(Object o) {
265 if (!(o instanceof AltosMapStore))
268 AltosMapStore other = (AltosMapStore) o;
269 return url.equals(other.url);
272 static HashMap<String,AltosMapStore> stores = new HashMap<String,AltosMapStore>();
274 public static AltosMapStore get(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
275 String url = map_url(center, zoom, maptype, px_size, scale);
278 synchronized(stores) {
279 if (stores.containsKey(url)) {
280 store = stores.get(url);
282 store = new AltosMapStore(url, map_file(center, zoom, maptype, px_size, scale));
283 stores.put(url, store);