1 | package org.openstreetmap.josm.plugins.ywms;
|
---|
2 |
|
---|
3 | import java.awt.*;
|
---|
4 | import java.awt.image.*;
|
---|
5 | import java.io.*;
|
---|
6 | import java.net.MalformedURLException;
|
---|
7 | import java.net.URL;
|
---|
8 | import java.util.ArrayList;
|
---|
9 | import java.util.List;
|
---|
10 | import java.util.StringTokenizer;
|
---|
11 | import java.util.regex.Matcher;
|
---|
12 | import java.util.regex.Pattern;
|
---|
13 |
|
---|
14 | import javax.imageio.ImageIO;
|
---|
15 |
|
---|
16 |
|
---|
17 | /**
|
---|
18 | * Loads a stellite image from Yahoo accordin to a WMS request.
|
---|
19 | * <p>
|
---|
20 | * The image is captured from firefox using one of its features: when the
|
---|
21 | * environment variable MOZ_FORCE_PAINT_AFTER_ONLOAD is set, it causes firefox
|
---|
22 | * to dump, as PPM files, the contents of all the pages loaded. The value of the
|
---|
23 | * variable is used to locate where the files are dumped.
|
---|
24 | * <p>
|
---|
25 | * As the image served by Yahoo is bigger than the one requested, it is cropped
|
---|
26 | * and resized to the desired size.
|
---|
27 | * <p>
|
---|
28 | * Currently, there is no way of knowing whether a zone has imagery or not. If
|
---|
29 | * not, the Yahoo message "We're sorry, the data you have requested is not
|
---|
30 | * available. Please zoom out to see more map information or refresh your
|
---|
31 | * browser to try again".
|
---|
32 | * <p>
|
---|
33 | * <br>
|
---|
34 | * <b>Implementation note:</b> <lu>
|
---|
35 | * <li>Some information is passed from Javascript to Java, so Firefox must be
|
---|
36 | * configured with the method "dump" to work. To allow this method in firefox,
|
---|
37 | * create or modify the option "browser.dom.window.dump.enabled=true" in
|
---|
38 | * "about:config"
|
---|
39 | * <p>
|
---|
40 | * <li>Also, as firefox must be started and killed once and again, it is
|
---|
41 | * recommended to create a profile with the option
|
---|
42 | * "browser.sessionstore.resume_from_crash" to false and set other profile to
|
---|
43 | * default, so no nag screens are shown. </lu>
|
---|
44 | *
|
---|
45 | * @author frsantos
|
---|
46 | *
|
---|
47 | */
|
---|
48 | public class ImageLoader
|
---|
49 | {
|
---|
50 | /** Firefox writes lines starting with this for each file loadded */
|
---|
51 | public static final String GECKO_DEBUG_LINE = "GECKO: PAINT FORCED AFTER ONLOAD:";
|
---|
52 |
|
---|
53 | private URL yahooUrl;
|
---|
54 | double[] orig_bbox = null;
|
---|
55 | int width = -1;
|
---|
56 | int height = -1;
|
---|
57 | int final_width = -1;
|
---|
58 | int final_height = -1;
|
---|
59 | Image image;
|
---|
60 | List<String> firefoxFiles = new ArrayList<String>();
|
---|
61 |
|
---|
62 | /** The regular expression used to locate the bounding boxes */
|
---|
63 | private static final Pattern BBOX_RE = Pattern.compile("bbox=([+-]?\\d+\\.\\d+),([+-]?\\d+\\.\\d+),([+-]?\\d+\\.\\d+),([+-]?\\d+\\.\\d+)", Pattern.CASE_INSENSITIVE);
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * Constructor.
|
---|
67 | *
|
---|
68 | * @param wmsUrl The WMS request
|
---|
69 | * @param pluginDir The directory of the plugin
|
---|
70 | * @throws ImageLoaderException When error loading the image
|
---|
71 | */
|
---|
72 | public ImageLoader(String wmsUrl) throws ImageLoaderException
|
---|
73 | {
|
---|
74 | System.out.println("YWMS::Requested WMS URL: " + wmsUrl);
|
---|
75 | try {
|
---|
76 | URL request = new URL("file:///page" + wmsUrl);
|
---|
77 | String query = request.getQuery().toLowerCase();
|
---|
78 | yahooUrl = new File(YWMSPlugin.getStaticPluginDir(), "ymap.html").toURI().toURL();
|
---|
79 | yahooUrl = new URL( yahooUrl.toExternalForm() + "?" + query);
|
---|
80 |
|
---|
81 | // Parse query to find original bounding box and dimensions
|
---|
82 | StringTokenizer st = new StringTokenizer(query, "&");
|
---|
83 | while( st.hasMoreTokens() )
|
---|
84 | {
|
---|
85 | String param = st.nextToken();
|
---|
86 | if( param.startsWith("width=") )
|
---|
87 | width=Integer.parseInt(param.substring("width=".length()));
|
---|
88 | else if( param.startsWith("height=") )
|
---|
89 | height=Integer.parseInt(param.substring("height=".length()));
|
---|
90 | else if( param.startsWith("bbox=") )
|
---|
91 | {
|
---|
92 | orig_bbox = getBbox(param);
|
---|
93 | }
|
---|
94 | }
|
---|
95 |
|
---|
96 | if( width == -1 || height == -1)
|
---|
97 | throw new ImageLoaderException("Can't find dimensions");
|
---|
98 |
|
---|
99 | load();
|
---|
100 | }
|
---|
101 | catch (MalformedURLException e) {
|
---|
102 | throw new ImageLoaderException(e);
|
---|
103 | }
|
---|
104 | }
|
---|
105 |
|
---|
106 | /**
|
---|
107 | * Does the hard work.
|
---|
108 | * <p>
|
---|
109 | * It spawns a Firefox process with an HTML page that loads Yahoo imagery
|
---|
110 | * using Yahoo's AJAX API. Firefox must be configured to allow the "dump"
|
---|
111 | * method for this to work.
|
---|
112 | * <p>
|
---|
113 | * The image is cropped and reescaled to meet requested dimensions.
|
---|
114 | * @throws ImageLoaderException When error loading the page
|
---|
115 | */
|
---|
116 | private void load() throws ImageLoaderException
|
---|
117 | {
|
---|
118 | Process browser = null;
|
---|
119 | try
|
---|
120 | {
|
---|
121 | browser = GeckoSupport.browse(yahooUrl.toString(), true);
|
---|
122 | // TODO: set focus in main window
|
---|
123 | File imageFilePpm = null;
|
---|
124 |
|
---|
125 | // Parse output
|
---|
126 | BufferedReader in = new BufferedReader( new InputStreamReader( browser.getInputStream() ) );
|
---|
127 | String line = in.readLine();
|
---|
128 | while( line != null )
|
---|
129 | {
|
---|
130 | System.out.println("YWMS::" + line);
|
---|
131 | if( line.startsWith("new_width=") )
|
---|
132 | {
|
---|
133 | final_width = (int)Math.round( Double.parseDouble( line.substring(10)) );
|
---|
134 | }
|
---|
135 | else if( line.startsWith("new_height=") )
|
---|
136 | {
|
---|
137 | final_height = (int)Math.round( Double.parseDouble( line.substring(11)) );
|
---|
138 | }
|
---|
139 | else if( line.startsWith(GECKO_DEBUG_LINE))
|
---|
140 | {
|
---|
141 | // Find out the screenshot file
|
---|
142 | StringTokenizer st = new StringTokenizer(line);
|
---|
143 | // Skip header
|
---|
144 | for( int i = 0; i < 5; i++) st.nextToken();
|
---|
145 | String url = st.nextToken();
|
---|
146 | String file = st.nextToken();
|
---|
147 | firefoxFiles.add(file);
|
---|
148 |
|
---|
149 | URL browserUrl;
|
---|
150 | try {
|
---|
151 | browserUrl = new URL(url);
|
---|
152 | if( browserUrl.sameFile(yahooUrl))
|
---|
153 | {
|
---|
154 | String status = st.nextToken();
|
---|
155 | if( !"(OK)".equals(status) )
|
---|
156 | throw new ImageLoaderException("Firefox couldn't load image");
|
---|
157 |
|
---|
158 | imageFilePpm = new File(file);
|
---|
159 | break;
|
---|
160 | }
|
---|
161 | }
|
---|
162 | catch (MalformedURLException mue)
|
---|
163 | {
|
---|
164 | // Probably a mozilla "chrome://" URL. Do nothing
|
---|
165 | }
|
---|
166 | }
|
---|
167 | else if( line.startsWith("WYMS ERROR:") )
|
---|
168 | {
|
---|
169 | throw new ImageLoaderException("Error in JavaScript page:" + line);
|
---|
170 | }
|
---|
171 | line = in.readLine();
|
---|
172 | }
|
---|
173 |
|
---|
174 | if( final_width == -1 && imageFilePpm == null && !firefoxFiles.isEmpty() )
|
---|
175 | {
|
---|
176 | throw new ImageLoaderException("Is there any other firefox window open with same profile?");
|
---|
177 | }
|
---|
178 | if( final_width == -1)
|
---|
179 | {
|
---|
180 | throw new ImageLoaderException("Couldn't find new dimension. Is browser.dom.window.dump.enabled set in Firefox config?");
|
---|
181 | }
|
---|
182 | if( imageFilePpm == null )
|
---|
183 | {
|
---|
184 | throw new ImageLoaderException("Couldn't find dumped image. Is it a modern Gecko browser (i.e., firefox 1.5)?");
|
---|
185 | }
|
---|
186 |
|
---|
187 | PPM ppmImage = new PPM(imageFilePpm.getAbsolutePath());
|
---|
188 | image = ppmImage.getImage();
|
---|
189 | cleanImages();
|
---|
190 |
|
---|
191 | resizeImage();
|
---|
192 | } catch (IOException e)
|
---|
193 | {
|
---|
194 | throw new ImageLoaderException(e);
|
---|
195 | }
|
---|
196 | finally
|
---|
197 | {
|
---|
198 | if( browser != null )
|
---|
199 | browser.destroy();
|
---|
200 | }
|
---|
201 | }
|
---|
202 |
|
---|
203 | /**
|
---|
204 | * Transforms the Image into a BufferedImage
|
---|
205 | * @return The current image as a BufferedImage
|
---|
206 | */
|
---|
207 | public BufferedImage getBufferedImage()
|
---|
208 | {
|
---|
209 | if( image == null )
|
---|
210 | return null;
|
---|
211 |
|
---|
212 | BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
---|
213 | Graphics g = bufferedImage.createGraphics();
|
---|
214 | g.setColor(Color.white);
|
---|
215 | g.fillRect(0, 0, width, height);
|
---|
216 | g.drawImage(image, 0, 0, null);
|
---|
217 | g.dispose();
|
---|
218 |
|
---|
219 | return bufferedImage;
|
---|
220 | }
|
---|
221 |
|
---|
222 | /**
|
---|
223 | * Resizes the image to meet the requested dimensions
|
---|
224 | */
|
---|
225 | private void resizeImage()
|
---|
226 | {
|
---|
227 | Toolkit tk = Toolkit.getDefaultToolkit();
|
---|
228 | //save("/tmp/image_orig.png");
|
---|
229 | image = tk.createImage (new FilteredImageSource (image.getSource(), new CropImageFilter(0, 0, final_width, final_height)));
|
---|
230 | //save("/tmp/image_crop.png");
|
---|
231 | image = tk.createImage (new FilteredImageSource (image.getSource(), new ReplicateScaleFilter(width, height)));
|
---|
232 | //save("/tmp/image_scale.png");
|
---|
233 | }
|
---|
234 |
|
---|
235 | /**
|
---|
236 | * Parses a line of the form bbox=xmin,ymin,xmax,ymax and extracts the bounding box
|
---|
237 | *
|
---|
238 | * @param line The string to parse
|
---|
239 | * @return The bound box as a double array[4]
|
---|
240 | * @throws ImageLoaderException
|
---|
241 | */
|
---|
242 | private double[] getBbox(String line) throws ImageLoaderException
|
---|
243 | {
|
---|
244 | Matcher matcher = BBOX_RE.matcher(line);
|
---|
245 | if( !matcher.matches() )
|
---|
246 | {
|
---|
247 | throw new ImageLoaderException("Can't find bounding box");
|
---|
248 | }
|
---|
249 |
|
---|
250 | double[] bbox = new double[4];
|
---|
251 | for( int i = 0; i < 4; i++)
|
---|
252 | {
|
---|
253 | bbox[i] = Double.parseDouble( matcher.group(i+1) );
|
---|
254 | }
|
---|
255 |
|
---|
256 | return bbox;
|
---|
257 | }
|
---|
258 |
|
---|
259 | /**
|
---|
260 | * Saves the current image as a PNG file
|
---|
261 | * @param fileName The name of the new file
|
---|
262 | * @throws IOException When error saving the file
|
---|
263 | */
|
---|
264 | public void save(String fileName)
|
---|
265 | {
|
---|
266 | try
|
---|
267 | {
|
---|
268 | FileOutputStream fileStream = new FileOutputStream(fileName);
|
---|
269 | ImageIO.write(getBufferedImage(), "png", fileStream);
|
---|
270 | fileStream.close();
|
---|
271 | }
|
---|
272 | catch(Exception e)
|
---|
273 | {
|
---|
274 | e.printStackTrace();
|
---|
275 | }
|
---|
276 | }
|
---|
277 |
|
---|
278 | /**
|
---|
279 | * Delete all images created by firefox when they are not longer used
|
---|
280 | */
|
---|
281 | public void cleanImages()
|
---|
282 | {
|
---|
283 | for(String fileName : firefoxFiles)
|
---|
284 | {
|
---|
285 | try
|
---|
286 | {
|
---|
287 | File file = new File(fileName);
|
---|
288 | file.delete();
|
---|
289 | }
|
---|
290 | catch(Exception e) { }
|
---|
291 | }
|
---|
292 |
|
---|
293 | }
|
---|
294 | }
|
---|