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 }