Wow, Thanks for this contribution Manuele. I will try to apply it later today. (I'm the shapefile module maintainer BTW :).) I'm going to put on trunk. I hope that is ok.
Jesse Le 7-Feb-08 à 12:38 AM, Manuele Ventoruzzo a écrit : > Hi Jody, > > I have just finished my implementation of index for shapefile > attributes. > > Since I had to code it quickly, it only works with these suppositions: > > 1. > > The attributes index is a map between attribute value and feature > ID (this is due to the fact that I use geotools as a library, so I > don't overwrite its source code). > > 2. > > The attributes in the shapefile never change. If so, the index has > to be rebuilt. > > 3. > > The attribute to be index may have duplicate values. > > > The index itself consists of: > > 1. > > One index summary file. This lists every attribute that has an > index. > > 2. > > One index file for every attribute to be indexed. This kind of > index is a collection sorted by attribute value with indication of > Feature ID (that is the position of the feature record in the dbf > file). > > > Of course, you can improve it and make it more suitable for general > use. > For example you can merge its behaviour with the behaviour of > FIDIndexer and have a more direct index for attributes. > > > I hope that this is useful. ;) You will find it in attachment. > > > Best regards > > Bye, > > Manuele. > > > > in data 25/01/2008 18.07 Jody Garnett ha detto: >> Manuele Ventoruzzo wrote: >>> Hi All, >>> >>> I have to do a search by attribute into a large shapefile. So I >>> need a feature like attribute index, but I know that at the moment >>> Geotools doesn't support attribute indexes for shapefiles. >> But we do welcome the idea; these are straight DBF indexes as far >> as I know so you should be able to find and example and send us a >> patch. >> As a general warning the shapefile code has been cleaned up on >> 2.5.x (the first time this has been done for a while). So if you >> are serious about >> shapefile use that is the place to be. >>> So I'm trying to find a way to use a FidFilter. >>> First of all, what is build a Fid index for shapefiles like? >>> I have read the source code and it appears that Fid is always the >>> position of the record in the attributes (.dbf) file. >>> >>> Is this correct? >>> >> It is; the "FeatureId" is made up of the file name (because the WFS >> specification asks feature ids to start with a letter) followed by >> a "." and the row number. >>> If not, is there a way to tell Geotools which attribute should be >>> used as Feature ID? >>> >> The file used to access rows quickly is part of the shapefile >> definition; and it is defined in terms of row numbers. Row numbers >> are used in the shp file as well >> to indicate which attribtues a geometry is bound to. >>> Otherwise, can I assume that Fid is always the position of the >>> record into dbf file and use another file to map attributes into >>> corresponding Fid. >>> >> Correct; and if you do that your other file is an attribute index >> and we would *love* to try out your implementation! >>> Thank you for the attention. >>> Manuele >>> >>> >>> ------------------------------------------------------------------------- >>> This SF.net email is sponsored by: Microsoft >>> Defy all challenges. Microsoft(R) Visual Studio 2008. >>> http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/ >>> _______________________________________________ >>> Geotools-gt2-users mailing list >>> [email protected] >>> https://lists.sourceforge.net/lists/listinfo/geotools-gt2-users >>> >> >> > > > -- > ------------------------------------------------------------------------ > *Manuele Ventoruzzo* > Over I.T. srl - Gruppo Engineering > Via Bassi 81 > 33080 Fiume Veneto (PN) > email: [EMAIL PROTECTED] > ------------------------------------------------------------------------ > import java.io.IOException; > import java.nio.ByteBuffer; > import java.nio.channels.FileChannel; > import java.util.ArrayList; > import java.util.Collection; > import java.util.Date; > import java.util.NoSuchElementException; > import org.geotools.data.shapefile.StreamLogging; > > /** > * Class to read an attribute index file > * @author Manuele Ventoruzzo > */ > public class AttributeIndexReader { > > private int record_size; > private String attribute; > private int numRecords; > private char attributeType; > private StreamLogging streamLogger = new > StreamLogging("AttributeIndexReader"); > private FileChannel readChannel; > private ByteBuffer buffer; > private int foundPos; > > /** Crea una nuova istanza di AttributeIndexReader */ > public AttributeIndexReader(String attribute, FileChannel > readChannel) throws IOException { > this.readChannel = readChannel; > this.attribute = attribute; > streamLogger.open(); > readHeader(); > allocateBuffers(); > } > > /** > * Returns the number of attributes in this index > */ > public int getCount() { > return numRecords; > } > > public void goTo(int recno) throws IOException { > long newPos = AttributeIndexWriter.HEADER_SIZE + (recno * > record_size); > if (newPos > readChannel.size()) > throw new NoSuchElementException(); > readChannel.position(newPos); > buffer.limit(buffer.capacity()); > buffer.position(buffer.limit()); > } > > public IndexRecord next() throws IOException { > if (!hasNext()) { > throw new NoSuchElementException(); > } > return getRecord(); > } > > /** > * Check if there's more elements to read > */ > public boolean hasNext() throws IOException { > if (isEOF()) > return false; > if (buffer.position() == buffer.limit()) { > buffer.position(0); > buffer.limit((int) > Math.min(buffer.limit(),remainingInFile())); > int read = readChannel.read(buffer); > if (read!=0) > buffer.position(0); > } > return buffer.remaining() != 0; > } > > public boolean isEOF() throws IOException { > return (buffer.position() == buffer.limit()) && > (readChannel.position() == readChannel.size()); > } > > /** > * Finds an attribute and returns its FIDs. > * @param reqAttribute Attribute to find. > * @return Collection of FID found (empty if nothing could be > found). > * It can found more than one record if it isn't univocal. > * @throws java.io.IOException > */ > public Collection findFids(Object reqAttribute) throws > IOException { > IndexRecord rec = findRecord(reqAttribute); > if (rec==null) > return new ArrayList(0); > goTo(foundPos+1); > ArrayList l = new ArrayList(); > while (rec.getAttribute().equals(reqAttribute)) { > l.add(new Long(rec.getFeatureID())); > if (!hasNext()) > break; > rec = next(); > } > return l; > } > > /** > * Finds an attibute. > * @param reqAttribute Attribute to find. > * @return Record as in index file. > * @throws java.io.IOException > */ > public IndexRecord findRecord(Object reqAttribute) throws > IOException { > foundPos = 0; > return search(reqAttribute,0,numRecords,(numRecords/2)-1); > } > > private void readHeader() throws IOException { > ByteBuffer buf = > ByteBuffer.allocate(AttributeIndexWriter.HEADER_SIZE); > readChannel.read(buf); > buf.position(0); > attributeType = (char) buf.get(); > record_size = buf.getInt(); > numRecords = buf.getInt(); > } > > private void allocateBuffers() throws IOException { > buffer = ByteBuffer.allocateDirect(record_size*1024); > buffer.position(buffer.limit()); > } > > /** > * Searches for the desired record. > * > * @param desired the id of the desired record. > * @param minRec the last record that is known to be <em>before</ > em> the > * desired record. > * @param maxRec the first record that is known to be <em>after</ > em> the > * desired record. > * @param predictedRec the record that is predicted to be the > desired > * record. > * > * @return returns the record found. > * > * @throws IOException > */ > private IndexRecord search(Object desired, int minRec, int maxRec, > int predictedRec) throws IOException { > if (maxRec == minRec) { > goTo(minRec); > buffer.limit(record_size); > IndexRecord currentRecord = next(); > buffer.limit(buffer.capacity()); > return (currentRecord != null && > currentRecord.getAttribute().equals(desired)) > ? firstInstance(minRec, currentRecord) : null; > } > goTo(predictedRec); > buffer.limit(record_size); > IndexRecord currentRecord = next(); > buffer.limit(buffer.capacity()); > if (currentRecord == null) > return null; > int comparison = currentRecord.compareTo(desired); > if (comparison == 0) { > return firstInstance(predictedRec, currentRecord); > } > if (maxRec - minRec < 2) { > int llimit = (minRec == predictedRec) ? minRec + 1 : > minRec; > int ulimit = (minRec == predictedRec) ? maxRec : maxRec - > 1; > int newPrediction = (minRec == predictedRec) ? llimit : > ulimit; > return search(desired, llimit, ulimit, newPrediction); > } > if (comparison < 1) { > int newPrediction = (maxRec - predictedRec) / 2 + > predictedRec; > return search(desired, ++predictedRec, maxRec, > newPrediction); > } else { > int newPrediction = (predictedRec - minRec) / 2 + minRec; > return search(desired, minRec, --predictedRec, > newPrediction); > } > } > > private IndexRecord getRecord() throws IOException { > Comparable obj = null; > switch (attributeType) { > case 'N': > if (record_size == 12) { > obj = new Integer(buffer.getInt()); > } else { > obj = new Long(buffer.getLong()); > } > break; > case 'F': > obj = new Double(buffer.getDouble()); > break; > case 'L': > obj = (buffer.get() == (byte) 1) ? Boolean.TRUE : > Boolean.FALSE; > break; > case 'D': > obj = new Date(buffer.getLong()); > break; > case 'C': > default: > byte[] b = new byte[record_size - 8]; > buffer.get(b); > obj = (new String(b, "ISO-8859-1")).trim(); > } > long id = buffer.getLong(); > return new IndexRecord(obj, id); > } > > private IndexRecord firstInstance(int position, IndexRecord rec) > throws IOException { > if (rec == null) > return null; > IndexRecord current = rec; > IndexRecord prev = rec; > while (rec.compareTo(current) == 0 && position > 0) { > goTo(--position); > buffer.limit(record_size); > prev = current; > current = next(); > buffer.limit(buffer.capacity()); > } > boolean b = (rec.compareTo(current) == 0); > foundPos = b ? position : position + 1; > return b ? current : prev; > } > > private long remainingInFile() throws IOException { > return readChannel.size() - readChannel.position(); > } > > } > import java.io.BufferedReader; > import java.io.File; > import java.io.FileNotFoundException; > import java.io.FileReader; > import java.io.FileWriter; > import java.io.IOException; > import java.io.PrintWriter; > import java.io.RandomAccessFile; > import java.net.MalformedURLException; > import java.net.URL; > import java.nio.channels.FileChannel; > import java.text.DecimalFormat; > > /** > * <P>Class to manage a summary for attribute indexes.</P> > * <P>It's just map that associates attribute name with index file. > * Using the name instead of its position on dbf permits to abstract > from position, > * so attribute order can change with no influence on indexes. > * </P> > * @author Manuele Ventoruzzo > */ > public class AttributeIndexSummary { > > public static final String SUMMARY_EXT = ".ids"; > > public static final String INDEX_EXT = ".i"; > > public static final DecimalFormat SUFFIX = new DecimalFormat("00"); > > public static final int DEFAULT_CACHE_SIZE = 134217728; //128MB > > /** Url of summary file */ > protected URL summaryURL = null; > > protected String filename = null; > > protected int cacheSize; > > /** > * Creates an IndexSummary > * @param shapefileUrl url of shapefile for wich indexes are > related to > */ > public AttributeIndexSummary(URL shpURL) throws > MalformedURLException, IOException { > this(shpURL,DEFAULT_CACHE_SIZE); > } > > /** > * Creates an IndexSummary > * @param shapefileUrl url of shapefile for wich indexes are > related to > * @param cacheSize maximum amount of memory to be used for index > creation > */ > public AttributeIndexSummary(URL shpURL, int cacheSize) throws > MalformedURLException, IOException { > try { > filename = java.net.URLDecoder.decode(shpURL.toString(), > "US-ASCII"); > filename = filename.substring(0, > filename.lastIndexOf(".shp")); > } catch (java.io.UnsupportedEncodingException use) { > throw new java.net.MalformedURLException("Unable to > decode " + shpURL + " cause " + use.getMessage()); > } > int indexslash = filename.lastIndexOf(File.pathSeparator); > > if (indexslash == -1) { > indexslash = 0; > } > summaryURL = new URL(filename + SUMMARY_EXT); > //create summary file (if it doesn't exist) empty > new File(summaryURL.getFile()).createNewFile(); > this.cacheSize = cacheSize; > } > > /** > * Index creation. Adds attribute name to summary and invokes > attribute index creation. > * @param attribute > */ > public void createIndex(String attribute) throws > FileNotFoundException, IOException { > URL url = getIndexURL(attribute); > if (url==null) { > addIndex(attribute); > url = getIndexURL(attribute); > } > synchronized (this) { > //now invokes AttributeIndexWriter to create the index > File f = new File(url.getFile()); > if (f.exists()) { > if (!f.delete()) > throw new IOException("File index cannot be > deleted. Probably it's locked."); > } > RandomAccessFile raf = new RandomAccessFile(f, "rw"); > FileChannel writeChannel = raf.getChannel(); > AttributeIndexWriter indexWriter = new > AttributeIndexWriter(attribute, writeChannel, > getDBFChannel(),cacheSize); > indexWriter.buildIndex(); > } > } > > /** > * Returns the index for specified attribute > * @param attribute attribute to search for > * @return Index reader or null if such attribute doesn't have an > index > */ > public AttributeIndexReader getIndex(String attribute) throws > FileNotFoundException, IOException { > URL url = getIndexURL(attribute); > if (url == null) > return null; > File f = new File(url.getFile()); > if (!f.exists()) > return null; > RandomAccessFile raf = new RandomAccessFile(new > File(url.getFile()), "r"); > return new AttributeIndexReader(attribute, raf.getChannel()); > } > > public boolean hasIndex(String attribute) throws > FileNotFoundException, IOException { > URL url = getIndexURL(attribute); > if (url == null) > return false; > return (new File(url.getFile())).exists(); > } > > /** > * Tests whether an index for this attribute exists. > * @param attribute > * @return > */ > public boolean existsIndex(String attribute) throws > FileNotFoundException, IOException { > URL url = getIndexURL(attribute); > if (url == null) > return false; > File f = new File(url.getFile()); > return f.exists(); > } > > /** > * Returns the index URL for specified attribute > * @param attribute attribute to search for > * @return URL to index file or null if such attribute doesn't > have an index > */ > protected URL getIndexURL(String attribute) throws > FileNotFoundException, IOException { > File f = new File(summaryURL.getFile()); > BufferedReader in = new BufferedReader(new FileReader(f)); > int count = 0; > while (in.ready()) { > String s = in.readLine(); > count++; > if (s.equals(attribute)) { > //index name: filename + number of row in index > summary + extension > return new URL(filename+INDEX_EXT > +SUFFIX.format(count)); > } > } > return null; //index not found > } > > protected synchronized void addIndex(String attribute) throws > FileNotFoundException, IOException { > File f = new File(summaryURL.getFile()); > PrintWriter out = new PrintWriter(new FileWriter(f,true)); > out.println(attribute); > out.flush(); > out.close(); > } > > protected FileChannel getDBFChannel() throws > FileNotFoundException, MalformedURLException { > URL url = new URL(filename+".dbf"); > File f = new File(url.getFile()); > if (!f.exists()) > url = new URL(filename+".DBF"); > f = new File(url.getFile()); > if (!f.exists()) > throw new FileNotFoundException("DBF file not found"); > RandomAccessFile raf = new RandomAccessFile(f, "r"); > return raf.getChannel(); > } > > } > import java.io.DataInputStream; > import java.io.EOFException; > import java.io.File; > import java.io.FileInputStream; > import java.io.IOException; > import java.io.RandomAccessFile; > import java.io.UnsupportedEncodingException; > import java.nio.ByteBuffer; > import java.nio.MappedByteBuffer; > import java.nio.channels.FileChannel; > import java.nio.channels.ReadableByteChannel; > import java.util.ArrayList; > import java.util.Collections; > import java.util.Date; > import java.util.Iterator; > import org.geotools.data.shapefile.StreamLogging; > import org.geotools.data.shapefile.dbf.DbaseFileHeader; > import org.geotools.data.shapefile.dbf.DbaseFileReader; > import org.geotools.resources.NIOUtilities; > > /** > * Class used to create an index for an dbf attribute > * @author Manuele Ventoruzzo > */ > public class AttributeIndexWriter { > > public static final int HEADER_SIZE = 9; > /** Number of bytes to be cached into memory (then it will be > written to temporary file) */ > private int cacheSize; > private int record_size; > private FileChannel writeChannel; > private FileChannel currentChannel; > private DbaseFileReader reader; > private StreamLogging streamLogger = new > StreamLogging("AttributeIndexWriter"); > private String attribute; > private int numRecords; > private int attributeColumn; > private Class attributeClass; > private char attributeType; > private File[] tempFiles; > private ArrayList buffer; > private long current; > private long position; > private int curFile; > private ByteBuffer writeBuffer; > > /** > * Create a new instance of AttributeIndexWriter > * @param attribute Attribute to be indexed > * @param writeChannel Channel used to write the index > * @param readChannel Channel used to read attributes file > */ > public AttributeIndexWriter(String attribute, FileChannel > writeChannel, > ReadableByteChannel readChannel, int > cacheSize) throws IOException { > this.writeChannel = writeChannel; > this.attribute = attribute; > this.cacheSize = cacheSize; > reader = new DbaseFileReader(readChannel, false); > if (!retrieveAttributeInfos()) { > throw new IOException("Attribute " + attribute + " not > found in dbf file"); > } > streamLogger.open(); > tempFiles = new File[getNumFiles()]; > buffer = new ArrayList(getCacheSize()); > current = 0; > curFile = 0; > } > > /** > * Build index, caching data in chucks and sorting it. > */ > public void buildIndex() throws IOException { > while (hasNext()) { > position = 0; > readBuffer(); > saveBuffer(); > } > reader.close(); > merge(); > deleteTempFiles(); > streamLogger.close(); > } > > /** > * Returns the number of attributes indexed > */ > public int getCount() { > return numRecords; > } > > private boolean hasNext() { > return reader.hasNext(); > } > > private void deleteTempFiles() { > for (int i = 0; i < tempFiles.length; i++) { > if (!tempFiles[i].delete()) { > tempFiles[i].deleteOnExit(); > } > } > } > > private void merge() throws IOException { > DataInputStream[] in = new DataInputStream[tempFiles.length]; > try { > IndexRecord[] recs = new IndexRecord[tempFiles.length]; > for (int i = 0; i < tempFiles.length; i++) { > in[i] = new DataInputStream(new > FileInputStream(tempFiles[i])); > recs[i] = null; > } > currentChannel = writeChannel; //to write to the ultimate > destination > allocateBuffers(); > writeBuffer.position(HEADER_SIZE); > position = 0; > int streamsReady; > IndexRecord min; > int mpos; > do { > min = null; > mpos = -1; > streamsReady = recs.length; > for (int j = 0; j < recs.length; j++) { > if (recs[j] == null) { > try { > recs[j] = readRecord(in[j]); > } catch (EOFException e) { > streamsReady--; > continue; > } > } > if (min==null || (min.compareTo(recs[j])>0)) { > min = recs[j]; > mpos = j; > } > } > if (mpos!=-1) > recs[mpos] = null; > write(min); > } while (streamsReady>0); > } finally { > //close input streams > for (int i = 0; i < in.length; i++) { > if (in[i]!=null) > in[i].close(); > } > //close output stream > drain(); > writeHeader(); > close(); > } > } > > /** Loads next part of file into cache */ > private void readBuffer() throws IOException { > buffer.clear(); > int n = getCacheSize(); > Comparable o; > IndexRecord r; > for (int i = 0; hasNext() && i < n; i++) { > o = getAttribute(); > r = new IndexRecord(o,current); > buffer.add(r); > current++; > } > } > > /** Saves buffer on temporary file */ > private void saveBuffer() throws IOException { > RandomAccessFile raf = null; > try { > if (buffer.size() == 0) { > return; > } > try { > Collections.sort(buffer); > } catch (OutOfMemoryError err) { > throw new IOException(err.getMessage()+". Try to > lower memory load parameter."); > } > File file = File.createTempFile("attind", null); > tempFiles[curFile++] = file; > Iterator it = buffer.iterator(); > raf = new RandomAccessFile(file, "rw"); > currentChannel = raf.getChannel(); > currentChannel.lock(); > allocateBuffers(); > writeBuffer.position(0); > while (it.hasNext()) { > write((IndexRecord) it.next()); > } > } finally { > close(); > if (raf != null) { > raf.close(); > } > } > } > > > private int getNumFiles() throws IOException { > int maxRec = getCacheSize(); > int n = (numRecords / maxRec); > return ((numRecords % maxRec)==0) ? n : n+1; > } > > private int getCacheSize() { > return ((numRecords * record_size) > cacheSize) ? cacheSize / > record_size : numRecords; > } > > private Comparable getAttribute() throws IOException { > DbaseFileReader.Row row = reader.readRow(); > Object o = row.read(attributeColumn); > if (o instanceof Date) { > //use ms from 1/1/70 > return new Long(((Date)o).getTime()); > } > return (Comparable)o; > } > > private IndexRecord readRecord(DataInputStream in) throws > IOException { > Comparable obj = null; > switch (attributeType) { > case 'N': > case 'D': > if (attributeClass.isInstance(new Integer(0))) { > obj = new Integer(in.readInt()); > } else { > obj = new Long(in.readLong()); > } > break; > case 'F': > obj = new Double(in.readDouble()); > break; > case 'L': > obj = new Boolean(in.readBoolean()); > break; > case 'C': > default: > byte[] b = new byte[record_size - 8]; > in.read(b); > obj = (new String(b, "ISO-8859-1")).trim(); > } > long id = in.readLong(); > return new IndexRecord(obj, id); > } > > private void write(IndexRecord r) throws IOException { > try { > if (r == null) > return; > if (writeBuffer == null) > allocateBuffers(); > if (writeBuffer.remaining() < record_size) > drain(); > switch (attributeType) { > case 'N': > case 'D': > Object obj = r.getAttribute(); > //sometimes DbaseFileReader reads an attribute as > Integer, even if it's described as Long in the header > if (attributeClass.isInstance(new Integer(0))) { > int i = (obj instanceof Integer) ? ((Number) > obj).intValue() : (int) ((Number) obj).longValue(); > writeBuffer.putInt(i); > } else { > long l = (obj instanceof Integer) ? (long) > ((Number) obj).intValue() : ((Number) obj).longValue(); > writeBuffer.putLong(l); > } > break; > case 'F': > writeBuffer.putDouble(((Double) > r.getAttribute()).doubleValue()); > break; > case 'L': > boolean b = ((Boolean) > r.getAttribute()).booleanValue(); > writeBuffer.put((byte)(b?1:0)); > break; > case 'C': > default: > byte[] btemp = > r.getAttribute().toString().getBytes("ISO-8859-1"); > byte[] bres = new byte[record_size-8]; > for (int i = 0; i < bres.length; i++) { > bres[i] = (i<btemp.length) ? btemp[i] : > (byte)0; > } > writeBuffer.put(bres); > } > writeBuffer.putLong(r.getFeatureID()); > } catch (UnsupportedEncodingException ex) { > throw new IOException(ex.getMessage()); > } > } > > private void writeHeader() throws IOException { > ByteBuffer buf = ByteBuffer.allocate(HEADER_SIZE); > buf.put((byte)attributeType); > buf.putInt(record_size); //record size in buffer > buf.putInt(numRecords); //number of records in this index > buf.flip(); > writeChannel.write(buf, 0); > } > > private void allocateBuffers() throws IOException { > writeBuffer = ByteBuffer.allocateDirect(HEADER_SIZE > +record_size * 1024); > } > > private void drain() throws IOException { > if (writeBuffer==null) > return; > writeBuffer.flip(); > int written = 0; > while (writeBuffer.remaining() > 0) { > written += currentChannel.write(writeBuffer, position); > } > position += written; > writeBuffer.flip().limit(writeBuffer.capacity()); > } > > private boolean retrieveAttributeInfos() { > DbaseFileHeader header = reader.getHeader(); > for (int i = 0; i < header.getNumFields(); i++) { > if (header.getFieldName(i).equals(attribute)) { > attributeColumn = i; > attributeClass = header.getFieldClass(i); > numRecords = header.getNumRecords(); > attributeType = header.getFieldType(i); > switch (attributeType) { > case 'C': //Character > record_size = header.getFieldLength(i); > break; > case 'N': //Numeric > if (attributeClass.isInstance(new Integer(0))) > record_size = 4; > else > record_size = 8; //Long and Double are > represented using 64 bits > break; > case 'F': //Float > record_size = 8; > break; > case 'D': //Date > record_size = 8; //stored in ms from 1/1/70 > break; > case 'L': //Logic > record_size = 1; //of course index on boolean > feature doesn't have any meaning > break; > default: > record_size = header.getFieldLength(i); > } > record_size += 8; //fid index > return true; > } > } > return false; > } > > private void close() throws IOException { > try { > drain(); > } finally { > if (writeBuffer != null) { > if (writeBuffer instanceof MappedByteBuffer) { > NIOUtilities.clean(writeBuffer); > } > } > if (currentChannel!=null && currentChannel.isOpen()) { > currentChannel.close(); > } > } > } > > } > /** > * Record stored in attribute index file > * @author Manuele Ventoruzzo > */ > public class IndexRecord implements Comparable { > > private Comparable attribute; > private long featureID; > > public IndexRecord(Comparable attribute, long featureID) { > this.attribute = attribute; > this.featureID = featureID; > } > > public Object getAttribute() { > return attribute; > } > > public long getFeatureID() { > return featureID; > } > > public int compareTo(Object o) { > if (o instanceof IndexRecord) { > return attribute.compareTo(((IndexRecord) > o).getAttribute()); > } > if (attribute.getClass().isInstance(o)) { > //compare just attribute with o > return attribute.compareTo(o); > } > throw new ClassCastException("Object " + o.toString() + " is > not of Record type"); > } > > public String toString() { > return "(" + attribute.toString() + "," + featureID + ")"; > } > } > ------------------------------------------------------------------------- > This SF.net email is sponsored by: Microsoft > Defy all challenges. Microsoft(R) Visual Studio 2008. > http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/_______________________________________________ > Geotools-gt2-users mailing list > [email protected] > https://lists.sourceforge.net/lists/listinfo/geotools-gt2-users ------------------------------------------------------------------------- This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2008. http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/ _______________________________________________ Geotools-gt2-users mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/geotools-gt2-users
