001    package ps6;
002    
003    import java.io.*;
004    import java.util.*;
005    
006    import ps2.GeoPoint;
007    
008    import ps4.*;
009    import ps6.tigerdb.*;
010    import ps6.tigerdb.DatabaseReader.GeoChain;
011    
012    
013    /**
014     * Functions as an iterator over the set of StreetSegments represented
015     * by the contents of the .zip files in a directory.
016     **/
017    public class StreetSegIterator
018        extends ImmIterator<StreetSegment>
019    {
020        /** When true, zero-length street segments will be filtered out.
021         * Default is true. */
022        private boolean filter_zero_length = true;
023    
024        /** When true, filtering will be performed as determined by the
025         * killfile.  Default is true. */
026        private boolean filter_killfile = true;
027    
028        /** When true, progress messages are sent to System.err as
029         * segments are read.  Default is false. */
030        private boolean mention_progress = false;
031    
032        /** When true, warning messages are sent to System.err if segments
033         * are filtered.  Default is false. */
034        private boolean mention_filter = false;
035    
036        /** When true, warning messages are sent to System.err if numbers
037         * are not disjoint.  Default is false. */
038        private boolean mention_non_disjoint = false;
039    
040        /**
041         * @requires files != null &&
042         *           elements of files are of type java.io.File &&
043         *           elements of files are .zip files
044         * 
045         * @effects creates a new iterator over which produces segments
046         *          read in from the given files
047         **/
048        public StreetSegIterator(Iterator<File> files, StreetSegmentFilter filter){
049            this.files = files;
050            this.filter = filter;
051        }
052    
053        private boolean initialized = false;
054        
055        /** filter which lets caller provide a way to filter results */
056        private StreetSegmentFilter filter; 
057        
058        /** .zip files to be read */
059        private Iterator<File> files;     
060        
061        /** chains from the current file */
062        private Iterator<GeoChain> chains;
063        
064        /** next segment to be returned or null if there are no more */
065        private StreetSegment next; 
066        
067        /** number of segments returned so far */
068        private long total = 0;     
069    
070        public boolean hasNext(){
071            if (!initialized) {
072                initialized = true;
073                next = nextSegment();
074            }
075    
076            return (next != null);
077        }
078    
079        public StreetSegment next(){
080            // standard iterator behavior
081            if (!hasNext()) {
082                throw new NoSuchElementException();
083            }
084    
085            // grab the segment to be returned, then advance to the next one
086            StreetSegment result = next;
087            next = nextSegment();
088    
089            // instrument reading process, because it's a bit slow
090            total++;
091            if (mention_progress && ((total % 10000) == 0)) {
092                System.err.println("Returning "+total+"th StreetSegment");
093                System.err.flush();
094            }
095    
096            return result;
097        }
098    
099        // @return the next segment from the files (post-filtering), or null if none exist
100        private StreetSegment nextSegment(){
101            // grab the next chain from the file
102            GeoChain chain = nextChain();
103            if (chain == null) {
104                return null;
105            }
106    
107            // make a segment from it
108            StreetSegment candidate = makeSegment(chain);
109    
110            // if segment could not be made, try again
111            if (candidate == null) {
112                return nextSegment();
113            }
114    
115            // if segment doesn't pass the filter, try again
116            if (filter_killfile && !filter.apply(candidate)) {
117                if (mention_filter) {
118                    System.err.println("Filtered out: " + candidate);
119                }
120                return nextSegment();
121            }
122    
123            // otherwise, it was a good segment
124            return candidate;
125        }
126    
127        
128        /**
129         * Retrieve the next GeoChain contained in the file(s)
130         * 
131         * @return the next GeoChain contained in the file(s), 
132         * or null if there are no more files left
133         */
134        private GeoChain nextChain(){
135            // return a chain if we have one ...
136            if (chains != null && chains.hasNext()) {
137                return (GeoChain) chains.next();
138            }
139    
140            // else, advance to the next file...
141            if (!files.hasNext()) {
142                return null;
143            }
144            File fileToRead = (File) files.next();
145            if (mention_progress) {
146                System.err.println("Reading from " + fileToRead);
147                System.err.flush();
148            }
149    
150            // ... and open it ...
151            try {
152                DatabaseReader dr = new DatabaseReader();
153                chains = dr.geoChains(fileToRead);
154            } catch (IOException ioe) {
155                throw new RuntimeException("IOException: " + ioe.getMessage());
156            }
157    
158            // ... and try again
159            return nextChain();
160        }
161    
162        /**
163         * Create a Street Segment from a Geo Chain
164         * @param chain
165         * @return a segment created from the chain, 
166         * or null if the segment is not desirable
167         */
168        private StreetSegment makeSegment(GeoChain chain){
169            GeoPoint p1 = chain.getRT1().getStart();
170            GeoPoint p2 = chain.getRT1().getEnd();
171            String name = chain.getRT1().getFeature().fullName();
172    
173            if (filter_zero_length && p1.equals(p2)) {
174                if (mention_filter) {
175                    System.err.println("Filtered out zero-length segment named " + name);
176                    System.err.flush();
177                }
178                return null;
179            }
180    
181            String lftAddr = chain.getLeftAddresses();
182            String rgtAddr = chain.getRightAddresses();
183            if (!chain.sidesDisjoint()) {
184                if (mention_non_disjoint) {
185                    System.err.println("Numbers on " + name + " were not disjoint, so were changed to empty sets");
186                    System.err.flush();
187                }
188                rgtAddr = lftAddr = "";
189            }
190    
191            StreetNumberSet leftSns = makeSNS(lftAddr);
192            StreetNumberSet rightSns = makeSNS(rgtAddr);
193    
194            String leftZip = chain.getRT1().getLeftZip();
195            String rightZip = chain.getRT1().getRightZip();
196    
197            StreetClassification streetClass = getStreetClass(chain);
198            boolean incAddr = areAddressesIncreasing(chain);
199    
200            return new StreetSegment(p1, p2, name.intern(), leftSns, rightSns,
201                                     leftZip, rightZip, streetClass, incAddr);
202        }
203    
204    
205        private static final StreetNumberSet EMPTY_SNS = new StreetNumberSet("");
206        private static StreetNumberSet makeSNS(String s)
207        {
208            if (s.length() == 0) return EMPTY_SNS;
209            return new StreetNumberSet(s);
210        }
211    
212        private static StreetClassification getStreetClass(GeoChain gc) {
213            String s = gc.getRT1().getCfc().toLowerCase();
214            
215            if (s.charAt(0) == 'a' || s.charAt(0) == 'A') {
216                switch (s.charAt(1)) {
217                case '1':
218                case '2':
219                    return StreetClassification.PRIM_HWY;
220                case '3':
221                    return StreetClassification.SEC_HWY;
222                case '4':
223                    return StreetClassification.LOCAL_ROAD;
224                default:
225                    return StreetClassification.UNKNOWN;
226                }
227            } else {
228                return StreetClassification.UNKNOWN;
229            }
230        }
231    
232        private static boolean areAddressesIncreasing(GeoChain gc) {
233            return gc.getRT1().getLeftRange().couldBeLowToHigh();
234        }
235    }