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 }