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

Last change on this file since 18320 was 18320, checked in by pieren, 16 years ago

Fixed bug in saving cache.

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