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