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

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

Re-implement the layer adjustment mode for the raster images fine positioning.

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