Revision: 5927 http://sourceforge.net/p/jump-pilot/code/5927 Author: ma15569 Date: 2018-08-25 16:31:05 +0000 (Sat, 25 Aug 2018) Log Message: ----------- Moved Sextante raster file export to OpenJUMP inner methods (RasterImageIO class) so any enhencement in OJ raster output will affect Sextante raster output
Modified Paths: -------------- plug-ins/SextantePlugIn/src/es/unex/sextante/openjump/core/OpenJUMPRasterLayer.java Modified: plug-ins/SextantePlugIn/src/es/unex/sextante/openjump/core/OpenJUMPRasterLayer.java =================================================================== --- plug-ins/SextantePlugIn/src/es/unex/sextante/openjump/core/OpenJUMPRasterLayer.java 2018-08-24 10:54:19 UTC (rev 5926) +++ plug-ins/SextantePlugIn/src/es/unex/sextante/openjump/core/OpenJUMPRasterLayer.java 2018-08-25 16:31:05 UTC (rev 5927) @@ -3,6 +3,7 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.BufferedWriter; @@ -10,6 +11,7 @@ import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.nio.ByteOrder; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; @@ -25,6 +27,10 @@ import javax.xml.transform.stream.StreamResult; import org.openjump.core.rasterimage.GeoTiffConstants; +import org.openjump.core.rasterimage.GridAscii; +import org.openjump.core.rasterimage.GridFloat; +import org.openjump.core.rasterimage.RasterImageIO; +import org.openjump.core.rasterimage.RasterImageIO.CellSizeXY; import org.openjump.core.rasterimage.RasterImageLayer; import org.openjump.core.rasterimage.Stats; import org.openjump.core.rasterimage.TiffTags; @@ -55,18 +61,24 @@ RasterImageLayer m_Layer; public void create(RasterImageLayer layer) throws IOException { - this.m_Layer = layer; - this.m_Raster = layer.getRasterData(null); - this.m_sName = layer.getName(); - this.m_sFilename = layer.getImageFileName(); - Envelope env = layer.getWholeImageEnvelope(); - this.m_LayerExtent = new AnalysisExtent(); - this.m_LayerExtent.setCellSize((env.getMaxX() - env.getMinX()) - / this.m_Raster.getWidth()); - this.m_LayerExtent.setXRange(env.getMinX(), env.getMaxX(), true); - this.m_LayerExtent.setYRange(env.getMinY(), env.getMaxY(), true); - this.m_dNoDataValue = layer.getNoDataValue(); + m_Layer = layer; + m_Raster = layer.getRasterData(null); + m_sName = layer.getName(); + m_sFilename = layer.getImageFileName(); + final Envelope env = layer.getWholeImageEnvelope(); + m_LayerExtent = new AnalysisExtent(); + m_LayerExtent.setCellSize((env.getMaxX() - env.getMinX()) + / m_Raster.getWidth()); + m_LayerExtent.setXRange(env.getMinX(), env.getMaxX(), true); + m_LayerExtent.setYRange(env.getMinY(), env.getMaxY(), true); + m_dNoDataValue = layer.getNoDataValue(); + + + // [Giuseppe Aruta 25 Aug. 2018] Moved raster file export to OpenJUMP + //inner methods (RasterImageIO class) so any enhencement in OJ raster + //output will affect Sextante raster output + // ------------------------------------------ // [Giuseppe Aruta 30 Gen. 2018] deactivated as OJ calculate anyhow // statistics (and writes .xml file) when loads raster // m_Stats = stats(layer); @@ -89,39 +101,39 @@ public void create(RasterImageLayer layer, boolean loadFromFile) throws IOException { if (!loadFromFile) { - this.m_Layer = layer; + m_Layer = layer; // [sstein 2 Aug 2010], changed so we work now with the raster and // not the image, which may be scaled for display. // m_Raster = layer.getImage().getData(); - this.m_Raster = layer.getRasterData(null); + m_Raster = layer.getRasterData(null); - this.m_sName = layer.getName(); - this.m_sFilename = layer.getImageFileName(); - Envelope env = layer.getWholeImageEnvelope(); - this.m_LayerExtent = new AnalysisExtent(); + m_sName = layer.getName(); + m_sFilename = layer.getImageFileName(); + final Envelope env = layer.getWholeImageEnvelope(); + m_LayerExtent = new AnalysisExtent(); // [sstein 18 Mar 2013], set cell size first, and then the extent, // otherwise maxX and maxY will be reset - this.m_LayerExtent.setCellSize((env.getMaxX() - env.getMinX()) - / this.m_Raster.getWidth()); - this.m_LayerExtent.setXRange(env.getMinX(), env.getMaxX(), true); - this.m_LayerExtent.setYRange(env.getMinY(), env.getMaxY(), true); + m_LayerExtent.setCellSize((env.getMaxX() - env.getMinX()) + / m_Raster.getWidth()); + m_LayerExtent.setXRange(env.getMinX(), env.getMaxX(), true); + m_LayerExtent.setYRange(env.getMinY(), env.getMaxY(), true); // [Giuseppe Aruta 8 Oct. 2016] using selected rasterlayer no data // value instead // m_dNoDataValue = DEFAULT_NO_DATA_VALUE; - this.m_dNoDataValue = layer.getNoDataValue(); + m_dNoDataValue = layer.getNoDataValue(); } else { - RasterImageLayer rasterLayer = new RasterImageLayer( + final RasterImageLayer rasterLayer = new RasterImageLayer( layer.getName(), layer.getLayerManager(), layer.getImageFileName(), null, layer.getWholeImageEnvelope()); - this.m_Layer = rasterLayer; - this.m_Raster = rasterLayer.getRasterData(null); + m_Layer = rasterLayer; + m_Raster = rasterLayer.getRasterData(null); - this.m_sName = rasterLayer.getName(); - this.m_sFilename = rasterLayer.getImageFileName(); - Envelope env = rasterLayer.getWholeImageEnvelope(); - this.m_LayerExtent = new AnalysisExtent(); + m_sName = rasterLayer.getName(); + m_sFilename = rasterLayer.getImageFileName(); + final Envelope env = rasterLayer.getWholeImageEnvelope(); + m_LayerExtent = new AnalysisExtent(); // [sstein 18 Mar 2013], set cell size first, and then the extent, // otherwise maxX and maxY will be reset m_LayerExtent.setCellSize((env.getMaxX() - env.getMinX()) @@ -137,24 +149,24 @@ public void create(String name, String filename, AnalysisExtent ge, int dataType, int numBands, Object crs) { - this.m_Raster = RasterFactory.createBandedRaster(dataType, ge.getNX(), + m_Raster = RasterFactory.createBandedRaster(dataType, ge.getNX(), ge.getNY(), numBands, null); - OpenJUMPOutputFactory fact = (OpenJUMPOutputFactory) SextanteGUI + final OpenJUMPOutputFactory fact = (OpenJUMPOutputFactory) SextanteGUI .getOutputFactory(); - Envelope envelope = new Envelope(); + final Envelope envelope = new Envelope(); envelope.init(ge.getXMin(), ge.getXMax(), ge.getYMin(), ge.getYMax()); - ColorModel colorModel = PlanarImage.createColorModel(this.m_Raster + final ColorModel colorModel = PlanarImage.createColorModel(m_Raster .getSampleModel()); - BufferedImage bufimg = new BufferedImage(colorModel, - (WritableRaster) this.m_Raster, false, null); + final BufferedImage bufimg = new BufferedImage(colorModel, + (WritableRaster) m_Raster, false, null); - this.m_Layer = new RasterImageLayer(name, fact.getContext() + m_Layer = new RasterImageLayer(name, fact.getContext() .getLayerManager(), filename, bufimg, envelope); - this.m_sName = name; - this.m_sFilename = filename; - this.m_LayerExtent = ge; + m_sName = name; + m_sFilename = filename; + m_LayerExtent = ge; // [Giuseppe Aruta 8 Oct. 2016] using Sextante GUI to get no data value // instead m_dNoDataValue = SextanteGUI.getOutputFactory().getDefaultNoDataValue(); @@ -163,28 +175,53 @@ @Override public int getBandsCount() { - if (this.m_Raster != null) { - return this.m_Raster.getNumBands(); + if (m_Raster != null) { + return m_Raster.getNumBands(); } return 0; } + // @Override + // public double getCellValueInLayerCoords(int x, int y, int band) { + // try { + // if (m_Raster != null) { + // return m_Raster.getSampleDouble(x, y, band); + // } + // return getNoDataValue(); + // } catch (final Exception e) { + // } + // return getNoDataValue(); + // } + @Override public double getCellValueInLayerCoords(int x, int y, int band) { + final DataBuffer db = m_Raster.getDataBuffer(); try { - if (this.m_Raster != null) { - return this.m_Raster.getSampleDouble(x, y, band); + switch (db.getDataType()) { + case 5: + return m_Raster.getSampleDouble(x, y, band); + case 4: + return m_Raster.getSampleFloat(x, y, band); + case 3: + return m_Raster.getSample(x, y, band); + case 1: + case 2: + return (short) m_Raster.getSampleDouble(x, y, band); + case 0: + return (byte) m_Raster.getSampleDouble(x, y, band) & 0xFF; } - return getNoDataValue(); - } catch (Exception e) { + return m_Layer.getNoDataValue(); + } catch (final Exception e) { + throw new RuntimeException( + "Interrupted while getting value of cell x = " + x + + ", y = " + y + ", band = " + band, e); } - return getNoDataValue(); } @Override public int getDataType() { - if (this.m_Raster != null) { - return this.m_Raster.getDataBuffer().getDataType(); + if (m_Raster != null) { + return m_Raster.getDataBuffer().getDataType(); } return 5; } @@ -191,8 +228,8 @@ @Override public double getLayerCellSize() { - if (this.m_LayerExtent != null) { - return this.m_LayerExtent.getCellSize(); + if (m_LayerExtent != null) { + return m_LayerExtent.getCellSize(); } return 0.0D; } @@ -199,25 +236,62 @@ @Override public AnalysisExtent getLayerGridExtent() { - return this.m_LayerExtent; + return m_LayerExtent; } @Override public double getNoDataValue() { - return this.m_dNoDataValue; + return m_dNoDataValue; } + // public void setCellValue(int x, int y, int band, double value) { + // if (((this.m_Raster instanceof WritableRaster)) + // && (getWindowGridExtent().containsCell(x, y))) { + // ((WritableRaster) this.m_Raster).setSample(x, y, band, value); + // } + // } + @Override - public void setCellValue(int x, int y, int band, double value) { - if (((this.m_Raster instanceof WritableRaster)) + public void setCellValue(int x, int y, int band, double dValue) { + if (((m_Raster instanceof WritableRaster)) && (getWindowGridExtent().containsCell(x, y))) { - ((WritableRaster) this.m_Raster).setSample(x, y, band, value); + final DataBuffer db = m_Raster.getDataBuffer(); + + try { + switch (db.getDataType()) { + case 5: + ((WritableRaster) m_Raster).setSample(x, y, band, dValue); + break; + case 4: + ((WritableRaster) m_Raster).setSample(x, y, band, + (float) dValue); + break; + case 3: + ((WritableRaster) m_Raster).setSample(x, y, band, + (int) dValue); + break; + case 1: + case 2: + ((WritableRaster) m_Raster).setSample(x, y, band, + (short) dValue); + break; + case 0: + ((WritableRaster) m_Raster).setSample(x, y, band, + (byte) dValue); + } + } catch (final Exception e) { + throw new RuntimeException( + "Interrupted while setting value of cell x = " + x + + ", y = " + y + ", band = " + band + + ", value = " + dValue, e); + + } } } @Override public void setNoDataValue(double noDataValue) { - this.m_dNoDataValue = noDataValue; + m_dNoDataValue = noDataValue; } @Override @@ -232,8 +306,8 @@ */ @Override public Rectangle2D getFullExtent() { - if (this.m_Layer != null) { - Envelope envelope = this.m_Layer.getWholeImageEnvelope(); + if (m_Layer != null) { + final Envelope envelope = m_Layer.getWholeImageEnvelope(); return new Rectangle2D.Double(envelope.getMinX(), envelope.getMinY(), envelope.getWidth(), envelope.getHeight()); @@ -249,8 +323,7 @@ public void close() { } - @Override - public void postProcess() throws Exception { + public void postProcess_old() throws Exception { if (m_Layer != null) { @@ -257,7 +330,7 @@ final FileOutputStream tifOut = new FileOutputStream(m_sFilename); final TIFFEncodeParam param = new TIFFEncodeParam(); param.setCompression(TIFFEncodeParam.COMPRESSION_NONE); - TIFFField[] tiffFields = new TIFFField[2]; + final TIFFField[] tiffFields = new TIFFField[2]; // [Giuseppe Aruta 8 Oct. 2016] the following parameters come from // RasterImageIO class @@ -267,13 +340,13 @@ tiffFields[0] = new TIFFField(GeoTiffConstants.ModelPixelScaleTag, TIFFField.TIFF_DOUBLE, 2, getLayerCellSize()); // No data - String noDataS = Double.toString(getNoDataValue()); - byte[] bytes = noDataS.getBytes(); - tiffFields[0] = new TIFFField(TiffTags.TIFFTAG_GDAL_NODATA, + final String noDataS = Double.toString(getNoDataValue()); + final byte[] bytes = noDataS.getBytes(); + tiffFields[1] = new TIFFField(TiffTags.TIFFTAG_GDAL_NODATA, TIFFField.TIFF_BYTE, noDataS.length(), bytes); // Tie point final Envelope envelope = m_Layer.getWholeImageEnvelope(); - tiffFields[1] = new TIFFField(GeoTiffConstants.ModelTiepointTag, + tiffFields[2] = new TIFFField(GeoTiffConstants.ModelTiepointTag, TIFFField.TIFF_DOUBLE, 6, new double[] { 0, 0, 0, envelope.getMinX(), envelope.getMaxY(), 0 }); param.setExtraFields(tiffFields); @@ -309,84 +382,105 @@ } - public boolean export(String sFilename) { - if (sFilename.endsWith("asc")) { - return exportToArcInfoASCIIFile(sFilename); + @Override + public void postProcess() throws Exception { + + if (m_Layer != null) { + + exportToTIFF(); + + // Switch RAM mode of the RasterImage + m_Layer.setImageFileName(m_sFilename); + m_Layer.setNeedToKeepImage(false); + } - if (sFilename.endsWith("tif")) { - return exportToGeoTIFFFile(sFilename); - } - return exportToGeoTIFFFile(sFilename); + } - private boolean exportToGeoTIFFFile(String sFilename) { - try { - FileOutputStream tifOut = new FileOutputStream(this.m_sFilename); - TIFFEncodeParam param = new TIFFEncodeParam(); - param.setCompression(1); - TIFFField[] tiffFields = new TIFFField[3]; + private void exportToTIFF() throws Exception { + if (m_Layer != null) { - tiffFields[0] = new TIFFField(33550, 12, 2, new double[] { - getLayerCellSize(), getLayerCellSize() }); + final RasterImageIO rasterImageIO = new RasterImageIO(); + final Envelope env = m_Layer.getWholeImageEnvelope(); - String noDataS = Double.toString(getNoDataValue()); - byte[] bytes = noDataS.getBytes(); - tiffFields[1] = new TIFFField(42113, 1, noDataS.length(), bytes); + final File file = new File(m_sFilename); + rasterImageIO.writeImage( + file, + m_Raster, + env, + rasterImageIO.new CellSizeXY(env.getWidth() + / m_Raster.getWidth(), env.getHeight() + / m_Raster.getHeight()), m_Layer.getNoDataValue()); - Envelope envelope = this.m_Layer.getWholeImageEnvelope(); - tiffFields[2] = new TIFFField(33922, 12, 6, new double[] { 0.0D, - 0.0D, 0.0D, envelope.getMinX(), envelope.getMaxY(), 0.0D }); - param.setExtraFields(tiffFields); - TIFFImageEncoder encoder = (TIFFImageEncoder) TIFFCodec - .createImageEncoder("tiff", tifOut, param); + // Switch RAM mode of the RasterImage + m_Layer.setImageFileName(m_sFilename); + m_Layer.setNeedToKeepImage(false); - ColorModel colorModel = PlanarImage.createColorModel(this.m_Raster - .getSampleModel()); - BufferedImage image = new BufferedImage(colorModel, - (WritableRaster) this.m_Raster, false, null); + } - encoder.encode(image); - tifOut.close(); + } - WorldFileHandler worldFileHandler = new WorldFileHandler( - this.m_sFilename, false); - worldFileHandler.writeWorldFile(envelope, image.getWidth(), - image.getHeight()); + private void exportToASC() throws Exception { + if (m_Layer != null) { + final RasterImageIO rasterImageIO = new RasterImageIO(); + final Envelope env = m_Layer.getWholeImageEnvelope(); + final CellSizeXY cellsize = rasterImageIO.new CellSizeXY( + env.getWidth() / m_Raster.getWidth(), env.getHeight() + / m_Raster.getHeight()); - this.m_Layer.setImageFileName(this.m_sFilename); - this.m_Layer.setNeedToKeepImage(false); - } catch (Exception e) { - return false; + final GridAscii ga = new GridAscii(m_sFilename, + m_Raster.getWidth(), m_Raster.getHeight(), true, + env.getMinX(), env.getMinY(), + cellsize.getAverageCellSize(), m_Layer.getNoDataValue()); + ga.setRas(m_Raster); + ga.writeGrid(); } - return true; + } + private void exportToFLT() throws Exception { + if (m_Layer != null) { + final RasterImageIO rasterImageIO = new RasterImageIO(); + final Envelope env = m_Layer.getWholeImageEnvelope(); + final CellSizeXY cellsize = rasterImageIO.new CellSizeXY( + env.getWidth() / m_Raster.getWidth(), env.getHeight() + / m_Raster.getHeight()); + + final GridFloat gf = new GridFloat(m_sFilename, + m_Raster.getWidth(), m_Raster.getHeight(), true, + env.getMinX(), env.getMinY(), + cellsize.getAverageCellSize(), m_Layer.getNoDataValue(), + ByteOrder.LITTLE_ENDIAN); + gf.setRas(m_Raster); + gf.writeGrid(); + } + + } + private boolean exportToArcInfoASCIIFile(String sFilename) { try { - FileWriter f = new FileWriter(sFilename); - BufferedWriter fout = new BufferedWriter(f); - DecimalFormat df = new DecimalFormat("##.###"); + final FileWriter f = new FileWriter(sFilename); + final BufferedWriter fout = new BufferedWriter(f); + final DecimalFormat df = new DecimalFormat("##.###"); df.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); df.setDecimalSeparatorAlwaysShown(true); - fout.write("ncols " + Integer.toString(this.m_LayerExtent.getNX())); + fout.write("ncols " + Integer.toString(m_LayerExtent.getNX())); fout.newLine(); - fout.write("nrows " + Integer.toString(this.m_LayerExtent.getNY())); + fout.write("nrows " + Integer.toString(m_LayerExtent.getNY())); fout.newLine(); - fout.write("xllcorner " - + Double.toString(this.m_LayerExtent.getXMin())); + fout.write("xllcorner " + Double.toString(m_LayerExtent.getXMin())); fout.newLine(); - fout.write("yllcorner " - + Double.toString(this.m_LayerExtent.getYMin())); + fout.write("yllcorner " + Double.toString(m_LayerExtent.getYMin())); fout.newLine(); fout.write("cellsize " - + Double.toString(this.m_LayerExtent.getCellSize())); + + Double.toString(m_LayerExtent.getCellSize())); fout.newLine(); fout.write("nodata_value " + Double.toString(getNoDataValue())); fout.newLine(); - for (int i = 0; i < this.m_LayerExtent.getNY(); i++) { - for (int j = 0; j < this.m_LayerExtent.getNX(); j++) { + for (int i = 0; i < m_LayerExtent.getNY(); i++) { + for (int j = 0; j < m_LayerExtent.getNX(); j++) { fout.write(df.format(getCellValueAsDouble(j, i)) + " "); } fout.newLine(); @@ -393,7 +487,7 @@ } fout.close(); f.close(); - } catch (Exception e) { + } catch (final Exception e) { return false; } return true; @@ -401,14 +495,14 @@ @Override public String getName() { - return this.m_sName; + return m_sName; } @Override public void setName(String sName) { - this.m_sName = sName; - if (this.m_Layer != null) { - this.m_Layer.setName(sName); + m_sName = sName; + if (m_Layer != null) { + m_Layer.setName(sName); } } @@ -418,16 +512,16 @@ @Override public Object getBaseDataObject() { - return this.m_Layer; + return m_Layer; } @Override public IOutputChannel getOutputChannel() { - return new FileOutputChannel(this.m_sFilename); + return new FileOutputChannel(m_sFilename); } public String getFilename() { - return this.m_sFilename; + return m_sFilename; } // [Giuseppe Aruta 30 Gen. 2018] The following code is used to a) calculate @@ -444,10 +538,10 @@ } public void writeXLM(File auxXmlFile) throws Exception { - Stats stats = m_Stats; - DocumentBuilderFactory docFactory = DocumentBuilderFactory + final Stats stats = m_Stats; + final DocumentBuilderFactory docFactory = DocumentBuilderFactory .newInstance(); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document doc; Element pamDatasetElement; @@ -455,7 +549,7 @@ doc = docBuilder.newDocument(); // Check if PAMDataset element exists and, if not, create it - String pamDatasetTagName = "PAMDataset"; + final String pamDatasetTagName = "PAMDataset"; pamDatasetElement = (Element) doc.getElementsByTagName( pamDatasetTagName).item(0); if (pamDatasetElement == null) { @@ -462,12 +556,12 @@ pamDatasetElement = doc.createElement(pamDatasetTagName); } - String pamRasterBandTagName = "PAMRasterBand"; - String pamRasterSridTagName = "SRS"; - String bandAttribute = "band"; - String metadataElementName = "Metadata"; + final String pamRasterBandTagName = "PAMRasterBand"; + final String pamRasterSridTagName = "SRS"; + final String bandAttribute = "band"; + final String metadataElementName = "Metadata"; - String SRID = null; + final String SRID = null; // String fileSourcePath = m_Layer.getImageFileName(); // //// String srsCode = m_Layer.getSRSInfo().getCode(); @@ -505,9 +599,9 @@ if (pamRasterBandNodeList != null && pamRasterBandNodeList.getLength() > 0) { for (int b = 0; b < pamRasterBandNodeList.getLength(); b++) { - Element pamRasterBandElement = (Element) pamRasterBandNodeList + final Element pamRasterBandElement = (Element) pamRasterBandNodeList .item(b); - int bandNr = Integer.parseInt(pamRasterBandElement + final int bandNr = Integer.parseInt(pamRasterBandElement .getAttribute(bandAttribute)); if (bandNr == b + 1) { @@ -525,9 +619,9 @@ } else { for (int b = 0; b < stats.getBandCount(); b++) { - Element pamRasterBandElement = doc + final Element pamRasterBandElement = doc .createElement(pamRasterBandTagName); - Attr attr = doc.createAttribute(bandAttribute); + final Attr attr = doc.createAttribute(bandAttribute); attr.setValue(Integer.toString(b + 1)); pamRasterBandElement.setAttributeNode(attr); @@ -543,14 +637,14 @@ } // write the content into xml file - TransformerFactory transformerFactory = TransformerFactory + final TransformerFactory transformerFactory = TransformerFactory .newInstance(); - Transformer transformer = transformerFactory.newTransformer(); + final Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2"); - DOMSource source = new DOMSource(doc); - StreamResult result = new StreamResult(auxXmlFile); + final DOMSource source = new DOMSource(doc); + final StreamResult result = new StreamResult(auxXmlFile); transformer.transform(source, result); } @@ -557,7 +651,7 @@ private Element updateMetadataElement(Document doc, Element metadataElement, RasterImageLayer layer, int band) { - Stats stats = m_Stats; + final Stats stats = m_Stats; ; Element mdi = doc.createElement("MDI"); mdi.setAttribute("key", "STATISTICS_MINIMUM"); ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Jump-pilot-devel mailing list Jump-pilot-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel