source: osm/applications/editors/josm/plugins/cadastre-fr/src/cadastre_fr/WMSLayer.java@ 19424

Last change on this file since 19424 was 19387, checked in by pieren, 15 years ago

More about raster image rotation

  • Property svn:eol-style set to native
File size: 23.7 KB
Line 
1// License: GPL. v2 and later. Copyright 2008-2009 by Pieren <pieren3@gmail.com> and others
2package cadastre_fr;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Image;
11import java.awt.Point;
12import java.awt.Toolkit;
13import java.awt.image.BufferedImage;
14import java.awt.image.ImageObserver;
15import java.io.EOFException;
16import java.io.IOException;
17import java.io.ObjectInputStream;
18import java.io.ObjectOutputStream;
19import java.util.ArrayList;
20import java.util.Vector;
21
22import javax.swing.Icon;
23import javax.swing.ImageIcon;
24import javax.swing.JMenuItem;
25import javax.swing.JOptionPane;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.coor.EastNorth;
30import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
31import org.openstreetmap.josm.gui.MapView;
32import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
33import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
34import org.openstreetmap.josm.gui.layer.Layer;
35import org.openstreetmap.josm.io.OsmTransferException;
36
37/**
38 * This is a layer that grabs the current screen from the French cadastre WMS
39 * server. The data fetched this way is tiled and managed to the disc to reduce
40 * server load.
41 */
42public class WMSLayer extends Layer implements ImageObserver {
43
44 Component[] component = null;
45
46 private int lambertZone = -1;
47
48 protected static final Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(
49 CadastrePlugin.class.getResource("/images/cadastre_small.png")));
50
51 protected Vector<GeorefImage> images = new Vector<GeorefImage>();
52
53 /**
54 * v1 to v2 = not supported
55 * v2 to v3 = add 4 more EastNorth coordinates in GeorefImages
56 */
57 protected final int serializeFormatVersion = 3;
58
59 public static int currentFormat;
60
61 private ArrayList<EastNorthBound> dividedBbox = new ArrayList<EastNorthBound>();
62
63 private CacheControl cacheControl = null;
64
65 private String location = "";
66
67 private String codeCommune = "";
68
69 public EastNorthBound communeBBox = new EastNorthBound(new EastNorth(0,0), new EastNorth(0,0));
70
71 private boolean isRaster = false;
72
73 private boolean isAlreadyGeoreferenced = false;
74
75 public double X0, Y0, angle, fX, fY;
76
77 // bbox of the georeferenced raster image (the nice horizontal and vertical box)
78 private EastNorth rasterMin;
79 private EastNorth rasterMax;
80 private double rasterRatio;
81
82 private JMenuItem saveAsPng;
83
84 public boolean adjustModeEnabled;
85
86 public WMSLayer() {
87 this(tr("Blank Layer"), "", -1);
88 }
89
90 public WMSLayer(String location, String codeCommune, int lambertZone) {
91 super(buildName(location, codeCommune));
92 this.location = location;
93 this.codeCommune = codeCommune;
94 this.lambertZone = lambertZone;
95 // enable auto-sourcing option
96 CadastrePlugin.pluginUsed = true;
97 }
98
99 public void destroy() {
100 // if the layer is currently saving the images in the cache, wait until it's finished
101 while (!cacheControl.isCachePipeEmpty()) {
102 System.out.println("Try to close a WMSLayer which is currently saving in cache : wait 1 sec.");
103 CadastrePlugin.safeSleep(1000);
104 }
105 super.destroy();
106 images = null;
107 dividedBbox = null;
108 System.out.println("Layer "+location+" destroyed");
109 }
110
111 private static String buildName(String location, String codeCommune) {
112 String ret = new String(location.toUpperCase());
113 if (codeCommune != null && !codeCommune.equals(""))
114 ret += "(" + codeCommune + ")";
115 return ret;
116 }
117
118 private String rebuildName() {
119 return buildName(this.location.toUpperCase(), this.codeCommune);
120 }
121
122 public void grab(CadastreGrabber grabber, Bounds b) throws IOException {
123 grab(grabber, b, true);
124 }
125
126 public void grab(CadastreGrabber grabber, Bounds b, boolean useFactor) throws IOException {
127 if (useFactor) {
128 if (isRaster) {
129 b = new Bounds(Main.proj.eastNorth2latlon(rasterMin), Main.proj.eastNorth2latlon(rasterMax));
130 divideBbox(b, Integer.parseInt(Main.pref.get("cadastrewms.rasterDivider",
131 CadastrePreferenceSetting.DEFAULT_RASTER_DIVIDER)));
132 } else
133 divideBbox(b, Integer.parseInt(Main.pref.get("cadastrewms.scale", Scale.X1.toString())));
134 } else
135 divideBbox(b, 1);
136
137 for (EastNorthBound n : dividedBbox) {
138 GeorefImage newImage;
139 try {
140 newImage = grabber.grab(this, n.min, n.max);
141 } catch (IOException e) {
142 System.out.println("Download action cancelled by user or server did not respond");
143 break;
144 } catch (OsmTransferException e) {
145 System.out.println("OSM transfer failed");
146 break;
147 }
148 if (grabber.getWmsInterface().downloadCancelled) {
149 System.out.println("Download action cancelled by user");
150 break;
151 }
152 if (CadastrePlugin.backgroundTransparent) {
153 for (GeorefImage img : images) {
154 if (img.overlap(newImage))
155 // mask overlapping zone in already grabbed image
156 img.withdraw(newImage);
157 else
158 // mask overlapping zone in new image only when new
159 // image covers completely the existing image
160 newImage.withdraw(img);
161 }
162 }
163 images.add(newImage);
164 saveToCache(newImage);
165 Main.map.mapView.repaint();
166 }
167 }
168
169 /**
170 *
171 * @param b the original bbox, usually the current bbox on screen
172 * @param factor 1 = source bbox 1:1
173 * 2 = source bbox divided by 2x2 smaller boxes
174 * 3 = source bbox divided by 3x3 smaller boxes
175 * 4 = hard coded size of boxes (100 meters) rounded allowing
176 * grabbing of next contiguous zone
177 */
178 private void divideBbox(Bounds b, int factor) {
179 EastNorth lambertMin = Main.proj.latlon2eastNorth(b.getMin());
180 EastNorth lambertMax = Main.proj.latlon2eastNorth(b.getMax());
181 double minEast = lambertMin.east();
182 double minNorth = lambertMin.north();
183 double dEast = (lambertMax.east() - minEast) / factor;
184 double dNorth = (lambertMax.north() - minNorth) / factor;
185 dividedBbox.clear();
186 if (factor < 4 || isRaster) {
187 for (int xEast = 0; xEast < factor; xEast++)
188 for (int xNorth = 0; xNorth < factor; xNorth++) {
189 dividedBbox.add(new EastNorthBound(new EastNorth(minEast + xEast * dEast, minNorth + xNorth * dNorth),
190 new EastNorth(minEast + (xEast + 1) * dEast, minNorth + (xNorth + 1) * dNorth)));
191 }
192 } else {
193 // divide to fixed size squares
194 int cSquare = Integer.parseInt(Main.pref.get("cadastrewms.squareSize", "100"));
195 minEast = minEast - minEast % cSquare;
196 minNorth = minNorth - minNorth % cSquare;
197 for (int xEast = (int)minEast; xEast < lambertMax.east(); xEast+=cSquare)
198 for (int xNorth = (int)minNorth; xNorth < lambertMax.north(); xNorth+=cSquare) {
199 dividedBbox.add(new EastNorthBound(new EastNorth(xEast, xNorth),
200 new EastNorth(xEast + cSquare, xNorth + cSquare)));
201 }
202 }
203 }
204
205 @Override
206 public Icon getIcon() {
207 return icon;
208 }
209
210 @Override
211 public String getToolTipText() {
212 String str = tr("WMS layer ({0}), {1} tile(s) loaded", getName(), images.size());
213 if (isRaster) {
214 str += "\n"+tr("Is not vectorized.");
215 str += "\n"+tr("Raster size: {0}", communeBBox);
216 } else
217 str += "\n"+tr("Is vectorized.");
218 str += "\n"+tr("Commune bbox: {0}", communeBBox);
219 return str;
220 }
221
222 @Override
223 public boolean isMergable(Layer other) {
224 return false;
225 }
226
227 @Override
228 public void mergeFrom(Layer from) {
229 }
230
231 @Override
232 public void paint(Graphics2D g, final MapView mv, Bounds bounds) {
233 synchronized(this){
234 for (GeorefImage img : images)
235 img.paint(g, mv, CadastrePlugin.backgroundTransparent,
236 CadastrePlugin.transparency, CadastrePlugin.drawBoundaries);
237 }
238 if (this.isRaster) {
239 paintCrosspieces(g, mv);
240 }
241 if (this.adjustModeEnabled) {
242 WMSAdjustAction.paintAdjustFrames(g, mv);
243 }
244 }
245
246 @Override
247 public void visitBoundingBox(BoundingXYVisitor v) {
248 for (GeorefImage img : images) {
249 v.visit(img.min);
250 v.visit(img.max);
251 }
252 }
253
254 @Override
255 public Object getInfoComponent() {
256 return getToolTipText();
257 }
258
259 @Override
260 public Component[] getMenuEntries() {
261 saveAsPng = new JMenuItem(new MenuActionSaveRasterAs(this));
262 saveAsPng.setEnabled(isRaster);
263 component = new Component[] { new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
264 new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
265 new JMenuItem(new MenuActionLoadFromCache()),
266 saveAsPng,
267 new JMenuItem(new LayerListPopup.InfoAction(this)),
268
269 };
270 return component;
271 }
272
273 public GeorefImage findImage(EastNorth eastNorth) {
274 // Iterate in reverse, so we return the image which is painted last.
275 // (i.e. the topmost one)
276 for (int i = images.size() - 1; i >= 0; i--) {
277 if (images.get(i).contains(eastNorth)) {
278 return images.get(i);
279 }
280 }
281 return null;
282 }
283
284 public boolean isOverlapping(Bounds bounds) {
285 GeorefImage georefImage =
286 new GeorefImage(new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB ), // not really important
287 Main.proj.latlon2eastNorth(bounds.getMin()),
288 Main.proj.latlon2eastNorth(bounds.getMax()));
289 for (GeorefImage img : images) {
290 if (img.overlap(georefImage))
291 return true;
292 }
293 return false;
294 }
295
296 public void saveToCache(GeorefImage image) {
297 if (CacheControl.cacheEnabled && !isRaster()) {
298 getCacheControl().saveCache(image);
299 }
300 }
301
302 public void saveNewCache() {
303 if (CacheControl.cacheEnabled) {
304 getCacheControl().deleteCacheFile();
305 for (GeorefImage image : images)
306 getCacheControl().saveCache(image);
307 }
308 }
309
310 public CacheControl getCacheControl() {
311 if (cacheControl == null)
312 cacheControl = new CacheControl(this);
313 return cacheControl;
314 }
315
316 /**
317 * Convert the eastNorth input coordinates to raster coordinates.
318 * The original raster size is [0,0,12286,8730] where 0,0 is the upper left corner and
319 * 12286,8730 is the approx. raster max size.
320 * @return the raster coordinates for the wms server request URL (minX,minY,maxX,maxY)
321 */
322 public String eastNorth2raster(EastNorth min, EastNorth max) {
323 double minX = (min.east() - rasterMin.east()) / rasterRatio;
324 double minY = (min.north() - rasterMin.north()) / rasterRatio;
325 double maxX = (max.east() - rasterMin.east()) / rasterRatio;
326 double maxY = (max.north() - rasterMin.north()) / rasterRatio;
327 return minX+","+minY+","+maxX+","+maxY;
328 }
329
330
331 public String getLocation() {
332 return location;
333 }
334
335 public void setLocation(String location) {
336 this.location = location;
337 setName(rebuildName());
338 }
339
340 public String getCodeCommune() {
341 return codeCommune;
342 }
343
344 public void setCodeCommune(String codeCommune) {
345 this.codeCommune = codeCommune;
346 setName(rebuildName());
347 }
348
349 public boolean isRaster() {
350 return isRaster;
351 }
352
353 public void setRaster(boolean isRaster) {
354 this.isRaster = isRaster;
355 if (saveAsPng != null)
356 saveAsPng.setEnabled(isRaster);
357 }
358
359 public boolean isAlreadyGeoreferenced() {
360 return isAlreadyGeoreferenced;
361 }
362
363 public void setAlreadyGeoreferenced(boolean isAlreadyGeoreferenced) {
364 this.isAlreadyGeoreferenced = isAlreadyGeoreferenced;
365 }
366
367 /**
368 * Set raster positions used for grabbing and georeferencing.
369 * rasterMin is the Eaast North of bottom left corner raster image on the screen when image is grabbed.
370 * The bounds width and height are the raster width and height. The image width matches the current view
371 * and the image height is adapted.
372 * Required: the communeBBox must be set (normally it is catched by CadastreInterface and saved by DownloadWMSPlanImage)
373 * @param bounds the current main map view boundaries
374 */
375 public void setRasterBounds(Bounds bounds) {
376 EastNorth rasterCenter = Main.proj.latlon2eastNorth(bounds.getCenter());
377 EastNorth eaMin = Main.proj.latlon2eastNorth(bounds.getMin());
378 EastNorth eaMax = Main.proj.latlon2eastNorth(bounds.getMax());
379 double rasterSizeX = communeBBox.max.getX() - communeBBox.min.getX();
380 double rasterSizeY = communeBBox.max.getY() - communeBBox.min.getY();
381 double ratio = rasterSizeY/rasterSizeX;
382 // keep same ratio on screen as WMS bbox (stored in communeBBox)
383 rasterMin = new EastNorth(eaMin.getX(), rasterCenter.getY()-(eaMax.getX()-eaMin.getX())*ratio/2);
384 rasterMax = new EastNorth(eaMax.getX(), rasterCenter.getY()+(eaMax.getX()-eaMin.getX())*ratio/2);
385 rasterRatio = (rasterMax.getX()-rasterMin.getX())/rasterSizeX;
386 }
387
388 /**
389 * Called by CacheControl when a new cache file is created on disk.
390 * Save only primitives to keep cache independent of software changes.
391 * @param oos
392 * @throws IOException
393 */
394 public void write(ObjectOutputStream oos) throws IOException {
395 oos.writeInt(this.serializeFormatVersion);
396 oos.writeObject(this.location); // String
397 oos.writeObject(this.codeCommune); // String
398 oos.writeInt(this.lambertZone);
399 oos.writeBoolean(this.isRaster);
400 if (this.isRaster) {
401 oos.writeDouble(this.rasterMin.getX());
402 oos.writeDouble(this.rasterMin.getY());
403 oos.writeDouble(this.rasterMax.getX());
404 oos.writeDouble(this.rasterMax.getY());
405 oos.writeDouble(this.rasterRatio);
406 }
407 oos.writeDouble(this.communeBBox.min.getX());
408 oos.writeDouble(this.communeBBox.min.getY());
409 oos.writeDouble(this.communeBBox.max.getX());
410 oos.writeDouble(this.communeBBox.max.getY());
411 }
412
413 /**
414 * Called by CacheControl when a cache file is read from disk.
415 * Cache uses only primitives to stay independent of software changes.
416 * @param ois
417 * @throws IOException
418 * @throws ClassNotFoundException
419 */
420 public boolean read(ObjectInputStream ois, int currentLambertZone) throws IOException, ClassNotFoundException {
421 currentFormat = ois.readInt();;
422 if (currentFormat < 2) {
423 JOptionPane.showMessageDialog(Main.parent, tr("Unsupported cache file version; found {0}, expected {1}\nCreate a new one.",
424 currentFormat, this.serializeFormatVersion), tr("Cache Format Error"), JOptionPane.ERROR_MESSAGE);
425 return false;
426 }
427 this.setLocation((String) ois.readObject());
428 this.setCodeCommune((String) ois.readObject());
429 this.lambertZone = ois.readInt();
430 this.setRaster(ois.readBoolean());
431 if (this.isRaster) {
432 double X = ois.readDouble();
433 double Y = ois.readDouble();
434 this.rasterMin = new EastNorth(X, Y);
435 X = ois.readDouble();
436 Y = ois.readDouble();
437 this.rasterMax = new EastNorth(X, Y);
438 this.rasterRatio = ois.readDouble();
439 }
440 double minX = ois.readDouble();
441 double minY = ois.readDouble();
442 double maxX = ois.readDouble();
443 double maxY = ois.readDouble();
444 this.communeBBox = new EastNorthBound(new EastNorth(minX, minY), new EastNorth(maxX, maxY));
445 if (this.lambertZone != currentLambertZone && currentLambertZone != -1) {
446 JOptionPane.showMessageDialog(Main.parent, tr("Lambert zone {0} in cache "+
447 "incompatible with current Lambert zone {1}",
448 this.lambertZone+1, currentLambertZone), tr("Cache Lambert Zone Error"), JOptionPane.ERROR_MESSAGE);
449 return false;
450 }
451 synchronized(this){
452 boolean EOF = false;
453 try {
454 while (!EOF) {
455 GeorefImage newImage = (GeorefImage) ois.readObject();
456 for (GeorefImage img : this.images) {
457 if (CadastrePlugin.backgroundTransparent) {
458 if (img.overlap(newImage))
459 // mask overlapping zone in already grabbed image
460 img.withdraw(newImage);
461 else
462 // mask overlapping zone in new image only when
463 // new image covers completely the existing image
464 newImage.withdraw(img);
465 }
466 }
467 this.images.add(newImage);
468 }
469 } catch (EOFException ex) {
470 // expected exception when all images are read
471 }
472 }
473 System.out.println("Cache loaded for location "+location+" with "+images.size()+" images");
474 return true;
475 }
476
477 /**
478 * Join the grabbed images into one single.
479 * Works only for images grabbed from non-georeferenced images (Feuilles cadastrales)(same amount of
480 * images in x and y)
481 */
482 public void joinRasterImages() {
483 if (images.size() > 1) {
484 EastNorth min = images.get(0).min;
485 EastNorth max = images.get(images.size()-1).max;
486 int oldImgWidth = images.get(0).image.getWidth();
487 int oldImgHeight = images.get(0).image.getHeight();
488 int newWidth = oldImgWidth*(int)Math.sqrt(images.size());
489 int newHeight = oldImgHeight*(int)Math.sqrt(images.size());
490 BufferedImage new_img = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
491 Graphics g = new_img.getGraphics();
492 // Coordinate (0,0) is on top,left corner where images are grabbed from bottom left
493 int rasterDivider = (int)Math.sqrt(images.size());
494 for (int h = 0; h < rasterDivider; h++) {
495 for (int v = 0; v < rasterDivider; v++) {
496 int newx = h*oldImgWidth;
497 int newy = newHeight - oldImgHeight - (v*oldImgHeight);
498 int j = h*rasterDivider + v;
499 g.drawImage(images.get(j).image, newx, newy, this);
500 }
501 }
502 synchronized(this) {
503 images.clear();
504 images.add(new GeorefImage(new_img, min, max));
505 }
506 }
507 }
508
509 /**
510 * Image cropping based on two EN coordinates pointing to two corners in diagonal
511 * Because it's coming from user mouse clics, we have to sort de positions first.
512 * Works only for raster image layer (only one image in collection).
513 * Updates layer georeferences.
514 * @param en1
515 * @param en2
516 */
517 public void cropImage(EastNorth en1, EastNorth en2){
518 // adj1 is corner bottom, left
519 EastNorth adj1 = new EastNorth(en1.east() <= en2.east() ? en1.east() : en2.east(),
520 en1.north() <= en2.north() ? en1.north() : en2.north());
521 // adj2 is corner top, right
522 EastNorth adj2 = new EastNorth(en1.east() > en2.east() ? en1.east() : en2.east(),
523 en1.north() > en2.north() ? en1.north() : en2.north());
524 // s1 and s2 have 0,0 at top, left where all EastNorth coord. have 0,0 at bottom, left
525 int sx1 = (int)((adj1.getX() - images.get(0).min.getX())*images.get(0).getPixelPerEast());
526 int sy1 = (int)((images.get(0).max.getY() - adj2.getY())*images.get(0).getPixelPerNorth());
527 int sx2 = (int)((adj2.getX() - images.get(0).min.getX())*images.get(0).getPixelPerEast());
528 int sy2 = (int)((images.get(0).max.getY() - adj1.getY())*images.get(0).getPixelPerNorth());
529 int newWidth = Math.abs(sx2 - sx1);
530 int newHeight = Math.abs(sy2 - sy1);
531 BufferedImage new_img = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
532 Graphics g = new_img.getGraphics();
533 g.drawImage(images.get(0).image, 0, 0, newWidth-1, newHeight-1,
534 sx1, sy1, sx2, sy2,
535 this);
536 images.set(0, new GeorefImage(new_img, adj1, adj2));
537 // important: update the layer georefs !
538 rasterMin = adj1;
539 rasterMax = adj2;
540 rasterRatio = (rasterMax.getX()-rasterMin.getX())/(communeBBox.max.getX() - communeBBox.min.getX());
541 setCommuneBBox(new EastNorthBound(new EastNorth(0,0), new EastNorth(newWidth-1,newHeight-1)));
542 }
543
544 public EastNorthBound getCommuneBBox() {
545 return communeBBox;
546 }
547
548 public void setCommuneBBox(EastNorthBound entireCommune) {
549 this.communeBBox = entireCommune;
550 }
551
552 /**
553 * Method required by ImageObserver when drawing an image
554 */
555 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
556 return false;
557 }
558
559 public int getLambertZone() {
560 return lambertZone;
561 }
562
563 public EastNorth getRasterCenter() {
564 return new EastNorth((images.get(0).max.east()+images.get(0).min.east())/2,
565 (images.get(0).max.north()+images.get(0).min.north())/2);
566 }
567
568 public void displace(double dx, double dy) {
569 this.rasterMin = new EastNorth(rasterMin.east() + dx, rasterMin.north() + dy);
570 images.get(0).shear(dx, dy);
571 }
572
573 public void resize(EastNorth rasterCenter, double proportion) {
574 this.rasterMin = rasterMin.interpolate(rasterCenter, proportion);
575 images.get(0).scale(rasterCenter, proportion);
576 }
577
578 public void rotate(EastNorth rasterCenter, double angle) {
579 this.rasterMin = rasterMin.rotate(rasterCenter, angle);
580 images.get(0).rotate(rasterCenter, angle);
581 }
582
583 private void paintCrosspieces(Graphics g, MapView mv) {
584 String crosspieces = Main.pref.get("cadastrewms.crosspieces", "0");
585 if (!crosspieces.equals("0")) {
586 int modulo = 25;
587 if (crosspieces.equals("2")) modulo = 50;
588 if (crosspieces.equals("3")) modulo = 100;
589 EastNorthBound currentView = new EastNorthBound(mv.getEastNorth(0, mv.getHeight()),
590 mv.getEastNorth(mv.getWidth(), 0));
591 int minX = ((int)currentView.min.east()/modulo+1)*modulo;
592 int minY = ((int)currentView.min.north()/modulo+1)*modulo;
593 int maxX = ((int)currentView.max.east()/modulo)*modulo;
594 int maxY = ((int)currentView.max.north()/modulo)*modulo;
595 int size=(maxX-minX)/modulo;
596 if (size<20) {
597 int px= size > 10 ? 2 : Math.abs(12-size);
598 g.setColor(Color.green);
599 for (int x=minX; x<=maxX; x+=modulo) {
600 for (int y=minY; y<=maxY; y+=modulo) {
601 Point p = mv.getPoint(new EastNorth(x,y));
602 g.drawLine(p.x-px, p.y, p.x+px, p.y);
603 g.drawLine(p.x, p.y-px, p.x, p.y+px);
604 }
605 }
606 }
607 }
608 }
609
610}
Note: See TracBrowser for help on using the repository browser.