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; version 2 of the License.
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.
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.
18 package org.altusmetrum.altoslib_11;
24 public class AltosMapStore {
27 LinkedList<AltosMapStoreListener> listeners = new LinkedList<AltosMapStoreListener>();
31 private static File map_file(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
32 double lat = center.lat;
33 double lon = center.lon;
34 char chlat = lat < 0 ? 'S' : 'N';
35 char chlon = lon < 0 ? 'W' : 'E';
37 if (lat < 0) lat = -lat;
38 if (lon < 0) lon = -lon;
39 String maptype_string = String.format("%s-", AltosMap.maptype_names[maptype]);
41 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
42 format_string = "jpg";
44 format_string = "png";
45 return new File(AltosPreferences.mapdir(),
46 String.format("map-%c%.6f,%c%.6f-%s%d%s.%s",
47 chlat, lat, chlon, lon, maptype_string, zoom, scale == 1 ? "" : String.format("-%d", scale), format_string));
50 private static String map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
54 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
55 format_string = "jpg";
57 format_string = "png32";
59 for (int s = 1; s < scale; s <<= 1)
62 if (AltosVersion.has_google_maps_api_key())
63 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",
64 center.lat, center.lon, z, px_size/scale, px_size/scale, scale, AltosMap.maptype_names[maptype], format_string, AltosVersion.google_maps_api_key);
66 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",
67 center.lat, center.lon, z, px_size/scale, px_size/scale, AltosMap.maptype_names[maptype], format_string);
74 public synchronized void add_listener(AltosMapStoreListener listener) {
75 if (!listeners.contains(listener))
76 listeners.add(listener);
77 listener.notify_store(this, status);
80 public synchronized void remove_listener(AltosMapStoreListener listener) {
81 listeners.remove(listener);
84 private synchronized void notify_listeners(int status) {
86 for (AltosMapStoreListener listener : listeners)
87 listener.notify_store(this, status);
90 static Object forbidden_lock = new Object();
91 static long forbidden_time;
92 static boolean forbidden_set;
94 private int fetch_url() {
99 } catch (java.net.MalformedURLException e) {
100 return AltosMapTile.bad_request;
104 URLConnection uc = null;
106 uc = u.openConnection();
107 String type = uc.getContentType();
108 int contentLength = uc.getContentLength();
109 if (uc instanceof HttpURLConnection) {
110 int response = ((HttpURLConnection) uc).getResponseCode();
112 case HttpURLConnection.HTTP_FORBIDDEN:
113 case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
114 case HttpURLConnection.HTTP_UNAUTHORIZED:
115 synchronized (forbidden_lock) {
116 forbidden_time = System.nanoTime();
117 forbidden_set = true;
118 return AltosMapTile.forbidden;
122 InputStream in = new BufferedInputStream(uc.getInputStream());
125 data = new byte[contentLength];
126 while (offset < contentLength) {
127 bytesRead = in.read(data, offset, data.length - offset);
134 if (offset != contentLength)
135 return AltosMapTile.failed;
137 } catch (IOException e) {
138 return AltosMapTile.failed;
142 FileOutputStream out = new FileOutputStream(file);
146 } catch (FileNotFoundException e) {
147 return AltosMapTile.bad_request;
148 } catch (IOException e) {
151 return AltosMapTile.bad_request;
153 return AltosMapTile.fetched;
156 static Object fetch_lock = new Object();
158 static final long forbidden_interval = 60l * 1000l * 1000l * 1000l;
159 static final long google_maps_ratelimit_ms = 1200;
161 static Object fetcher_lock = new Object();
163 static LinkedList<AltosMapStore> waiting = new LinkedList<AltosMapStore>();
164 static LinkedList<AltosMapStore> running = new LinkedList<AltosMapStore>();
166 static final int concurrent_fetchers = 128;
168 static void start_fetchers() {
169 while (!waiting.isEmpty() && running.size() < concurrent_fetchers) {
170 AltosMapStore s = waiting.remove();
172 Thread lt = s.make_fetcher_thread();
177 void finish_fetcher() {
178 synchronized(fetcher_lock) {
179 running.remove(this);
185 synchronized(fetcher_lock) {
191 class fetcher implements Runnable {
196 notify_listeners(AltosMapTile.fetched);
200 synchronized(forbidden_lock) {
201 if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) {
202 notify_listeners(AltosMapTile.forbidden);
209 if (!AltosVersion.has_google_maps_api_key()) {
210 synchronized (fetch_lock) {
211 long startTime = System.nanoTime();
212 new_status = fetch_url();
213 if (new_status == AltosMapTile.fetched) {
214 long duration_ms = (System.nanoTime() - startTime) / 1000000;
215 if (duration_ms < google_maps_ratelimit_ms) {
217 Thread.sleep(google_maps_ratelimit_ms - duration_ms);
218 } catch (InterruptedException e) {
219 Thread.currentThread().interrupt();
225 new_status = fetch_url();
227 notify_listeners(new_status);
234 private Thread make_fetcher_thread() {
235 return new Thread(new fetcher());
238 private void fetch() {
242 private AltosMapStore (String url, File file) {
247 status = AltosMapTile.fetched;
249 status = AltosMapTile.fetching;
254 public int hashCode() {
255 return url.hashCode();
258 public boolean equals(Object o) {
262 if (!(o instanceof AltosMapStore))
265 AltosMapStore other = (AltosMapStore) o;
266 return url.equals(other.url);
269 static HashMap<String,AltosMapStore> stores = new HashMap<String,AltosMapStore>();
271 public static AltosMapStore get(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
272 String url = map_url(center, zoom, maptype, px_size, scale);
275 synchronized(stores) {
276 if (stores.containsKey(url)) {
277 store = stores.get(url);
279 store = new AltosMapStore(url, map_file(center, zoom, maptype, px_size, scale));
280 stores.put(url, store);