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 removed.
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 that produces all segments
046         *          from the given files that are accepted by the filter
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        private StreetSegmentFilter filter;
056    
057        /** .zip files to be read */
058        private Iterator<File> files;
059    
060        /** chains from the current file */
061        private Iterator<GeoChain> chains;
062    
063        /** next segment to be returned or null if there are no more */
064        private /*@Nullable*/ StreetSegment next;
065    
066        /** number of segments returned so far */
067        private long total = 0;
068    
069        /*@AssertNonNullIfTrue("next")*/
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        /**
100         * @return the next segment from the files (post-filtering), or null if none exist
101         */
102        private /*@Nullable*/ StreetSegment nextSegment() {
103            // grab the next chain from the file
104            GeoChain chain = nextChain();
105            if (chain == null) {
106                return null;
107            }
108    
109            // make a segment from it
110            StreetSegment candidate = makeSegment(chain);
111    
112            // if segment could not be made, try again
113            if (candidate == null) {
114                return nextSegment();
115            }
116    
117            // if segment isn't accepted by the filter, try again
118            if (filter_killfile && !filter.accept(candidate)) {
119                if (mention_filter) {
120                    System.err.println("Filtered out: " + candidate);
121                }
122                return nextSegment();
123            }
124    
125            // otherwise, it was a good segment
126            return candidate;
127        }
128    
129    
130        /**
131         * Retrieve the next GeoChain contained in the file(s)
132         *
133         * @return the next GeoChain contained in the file(s),
134         * or null if there are no more files left
135         */
136        private /*@Nullable*/ GeoChain nextChain() {
137            // return a chain if we have one ...
138            if (chains != null && chains.hasNext()) {
139                return chains.next();
140            }
141    
142            // else, advance to the next file...
143            if (!files.hasNext()) {
144                return null;
145            }
146            File fileToRead = files.next();
147            if (mention_progress) {
148                System.err.println("Reading from " + fileToRead);
149                System.err.flush();
150            }
151    
152            // ... and open it ...
153            try {
154                DatabaseReader dr = new DatabaseReader();
155                chains = dr.geoChains(fileToRead);
156            } catch (IOException ioe) {
157                throw new RuntimeException("IOException: " + ioe.getMessage());
158            }
159    
160            // ... and try again
161            return nextChain();
162        }
163    
164        /**
165         * Create a Street Segment from a Geo Chain
166         * @param chain
167         * @return a segment created from the chain,
168         * or null if the segment is not desirable
169         */
170        private /*@Nullable*/ StreetSegment makeSegment(GeoChain chain) {
171            GeoPoint p1 = chain.getRT1().getStart();
172            GeoPoint p2 = chain.getRT1().getEnd();
173            String name = chain.getRT1().getFeature().fullName();
174    
175            if (filter_zero_length && p1.equals(p2)) {
176                if (mention_filter) {
177                    System.err.println("Filtered out zero-length segment named " + name);
178                    System.err.flush();
179                }
180                return null;
181            }
182    
183            String lftAddr = chain.getLeftAddresses();
184            String rgtAddr = chain.getRightAddresses();
185            if (!chain.sidesDisjoint()) {
186                if (mention_non_disjoint) {
187                    System.err.println("Numbers on " + name + " were not disjoint, so were changed to empty sets");
188                    System.err.flush();
189                }
190                rgtAddr = lftAddr = "";
191            }
192    
193            StreetNumberSet leftSns = makeSNS(lftAddr);
194            StreetNumberSet rightSns = makeSNS(rgtAddr);
195    
196            String leftZip = chain.getRT1().getLeftZip();
197            String rightZip = chain.getRT1().getRightZip();
198    
199            StreetClassification streetClass = getStreetClass(chain);
200            boolean incAddr = areAddressesIncreasing(chain);
201    
202            return new StreetSegment(p1, p2, name.intern(), leftSns, rightSns,
203                                     leftZip, rightZip, streetClass, incAddr);
204        }
205    
206    
207        private static final StreetNumberSet EMPTY_SNS = new StreetNumberSet("");
208        private static StreetNumberSet makeSNS(String s)
209        {
210            if (s.length() == 0) return EMPTY_SNS;
211            return new StreetNumberSet(s);
212        }
213    
214        private static StreetClassification getStreetClass(GeoChain gc) {
215            String s = gc.getRT1().getCfc().toLowerCase();
216    
217            if (s.charAt(0) == 'a' || s.charAt(0) == 'A') {
218                switch (s.charAt(1)) {
219                case '1':
220                case '2':
221                    return StreetClassification.PRIM_HWY;
222                case '3':
223                    return StreetClassification.SEC_HWY;
224                case '4':
225                    return StreetClassification.LOCAL_ROAD;
226                default:
227                    return StreetClassification.UNKNOWN;
228                }
229            } else {
230                return StreetClassification.UNKNOWN;
231            }
232        }
233    
234        private static boolean areAddressesIncreasing(GeoChain gc) {
235            return gc.getRT1().getLeftRange().couldBeLowToHigh();
236        }
237    }