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 }