def search_nodes(map): if len(map.nodeList) > 0: valid, lat, lon = parse_gps() if not valid: return dlist = map.generate_dlist(lat, lon) dlist_sorted = sorted(dlist) show_type = input("Show (1) nodes closer than a certain distance, or (2) given number of closest nodes: ") show_num = 0 if show_type == '1': keepTrying = 'y' while keepTrying == 'y': dist_str = input("Show nodes closer than the following distance (in feet): ") try: dist_num = int(dist_str) show_num = sum([CvtdUtil.coord_to_ft(y) < dist_num for y in dlist]) show = input(f"Show {show_num} entries (y/n): ") if show == 'y': print(f"Showing {show_num} nodes within {dist_num} feet of {lat}, {lon}") for i in range(show_num): dlist_ix = dlist.index(dlist_sorted[i]) e = round(CvtdUtil.coord_to_ft(dlist_sorted[i]), 3) print(f"{i+1}: {map.nodeList[dlist_ix].lat}, {map.nodeList[dlist_ix].lon} ({e} feet)") keepTrying = 'n' else: keepTrying = input("Try again (y/n): ") except ValueError: keepTrying = input(f"Error: Could not convert ${show_str} to an integer. Try again (y/n): ") elif show_type == '2': nnodes = len(map.nodeList) show_str = input(f"Show how many entries (max {nnodes}): ") try: show_num = min(int(show_str), nnodes) print(f"Showing {show_num} nodes close to {lat}, {lon}") for i in range(show_num): dlist_ix = dlist.index(dlist_sorted[i]) e = round(dlist_sorted[i]*70*5280, 3) print(f"{i+1}: {map.nodeList[dlist_ix].lat}, {map.nodeList[dlist_ix].lon} ({e} feet)") except ValueError: print(f"Error: Could not convert ${show_str} to an integer") # Now, let them choose one of these nodes if show_num > 0: while True: try: node_info = int(input(f"Get more info about a node (1-{show_num}) or (n)o: ")) if 1 <= node_info <= show_num: plist = map.get_node_usage(dlist.index(dlist_sorted[node_info - 1])) if len(plist) > 0: print("The following roads use this node: ") for p in plist: print(p.addr_repr(map.roadList)) else: break except ValueError: break else: print("No nodes have been added, you might want to read roads.txt (key 'r')")
def test_split_double_quotes(self): s = "1,2,3,'Once, upon a time, something happened',6" self.assertListEqual([ "1", "2", "3", "'Once", " upon a time", " something happened'", "6" ], CvtdUtil.split_double_quotes(s, ',')) s2 = '"3,3","4,4","5,5"\n' self.assertListEqual(['"3,3"', '"4,4"', '"5,5"\n'], CvtdUtil.split_double_quotes(s2, ','))
def compute_addr(self, lat, lon, nodeDict): point_ix, proj_ratio, error = self.compute_proj_ratio( lat, lon, nodeDict) vaddr_begin = self.points[point_ix].addr vaddr_end = self.points[point_ix + 1].addr address = round(proj_ratio * (vaddr_end - vaddr_begin) + vaddr_begin) addrRepr = CvtdUtil.addr_repr(address, self.dir, self.name) return addrRepr, CvtdUtil.coord_to_ft(error)
def edit(self, nodeDict): while True: print("n: Name: " + self.name) print("d: Direction: " + self.dir) for pointIx, point in enumerate(self.points): print( f"{pointIx+1}: {point.addr} at {nodeDict[point.node].lat}, {nodeDict[point.node].lon}" ) action = CvtdUtil.input_int( f"What do you want to edit or delete? Enter 'q' when done: ", 1, len(self.points), validAnswers=['q', 'n', 'd']) if type(action) is int: realPointIx = action - 1 pointAction = CvtdUtil.input_int( f"Choose one of the following: (1) edit addr, (2) edit node, (3) delete point: ", 1, 3) if pointAction == 1: # If we have more than one point, increasing/decreasing is defined and we can maintain the constraint # Else, we just need to set neAddr to the current if len(self.points) > 1: if self.increasing: minAddr = self.points[ realPointIx - 1].addr + 1 if realPointIx > 0 else None maxAddr = self.points[ realPointIx + 1].addr - 1 if realPointIx < ( len(self.points) - 1) else None else: maxAddr = self.points[ realPointIx - 1].addr - 1 if realPointIx > 0 else None minAddr = self.points[ realPointIx + 1].addr + 1 if realPointIx < ( len(self.points) - 1) else None else: minAddr = None maxAddr = None self.points[realPointIx].addr = CvtdUtil.input_int( f"Enter a new address (min {minAddr}, max {maxAddr}): ", minAddr, maxAddr) elif pointAction == 2: print("Not Implemented Error. Sorry") elif pointAction == 3: del self.points[realPointIx] elif action == 'n': self.name = input("Enter name for this road: ") elif action == 'd': self.dir = input( f"Enter a direction for {self.name} ('N/S', 'E/W', or other'): " ) else: break
def add_to_print_list(myMap, locator, print_list, rid, ix): try: position = locator.pos[rid][ix] t = position.timestamp lat = position.lat lon = position.lon dir = position.direction except IndexError: return # Determine route try: route = myMap.routeDict[rid] routeName = route.routeShortName # validStreets = route.get_street_list() except KeyError: routeName = "Unknown Route" # validStreets = None validStreets = None # Compute address # roadIx, addr, error = myMap.compute_addr_repr(lat, lon, validStreets) roadIx, addr, error = myMap.compute_addr_repr(lat, lon, None) if validStreets is not None and error > 250: roadIx, addr_off_route, error_off_route = myMap.compute_addr_repr( lat, lon, None) if error_off_route < 200: addr = "!" + addr_off_route error = error_off_route error = round(error) # Determine if direction is actual or if it should be X try: lat_to_lat_diff = CvtdUtil.coord_to_ft( abs(locator.pos[rid][ix - 1].lat - lat)) lon_to_lon_diff = CvtdUtil.coord_to_ft( abs(locator.pos[rid][ix - 1].lon - lon)) pos_to_pos_diff = math.sqrt(lat_to_lat_diff**2 + lon_to_lon_diff**2) if (pos_to_pos_diff < 40): dir = Direction.X else: dir = direction.get_direction(dir) except IndexError: dir = direction.get_direction(dir) # Add to list to print later try: print_list[rid].append([t, addr, error, dir]) except KeyError: print_list[rid] = [routeName] print_list[rid].append([t, addr, error, dir])
def import_calendar(dirPath, myMap): print("Importing calendar...") calendarDict = {} calendar = CvtdUtil.parse_csv(dirPath + "/calendar.txt") strFormat = '%Y%m%d' try: for ix, sid in enumerate(calendar['service_id']): if sid not in calendarDict: calendarDict[sid] = GtfsCalendarEntry() monday = int(calendar['monday'][ix]) tuesday = int(calendar['tuesday'][ix]) wednesday = int(calendar['wednesday'][ix]) thursday = int(calendar['thursday'][ix]) friday = int(calendar['friday'][ix]) saturday = int(calendar['saturday'][ix]) sunday = int(calendar['sunday'][ix]) calendarDict[sid].generateDaysOfWeek(monday, tuesday, wednesday, thursday, friday, saturday, sunday) calendarDict[sid].startDate = datetime.datetime.strptime( calendar['start_date'][ix], strFormat) calendarDict[sid].endDate = datetime.datetime.strptime( calendar['end_date'][ix], strFormat) except (KeyError, IndexError, ValueError): print(f"Error: import_calendar, ix {ix}, sid {sid}") pass myMap.calendarDict = calendarDict
def import_stops(dirPath, myMap): print("Importing stops...") stopDict = {} stops = CvtdUtil.parse_csv(dirPath + "/stops.txt") try: for ix, sid in enumerate(stops['stop_id']): if sid not in stopDict: stopDict[sid] = GtfsStop() if stops['stop_code'][ix] is not '': stopDict[sid].stopCode = int(stops['stop_code'][ix]) stopDict[sid].stopName = stops['stop_name'][ix] stopDict[sid].lat = float(stops['stop_lat'][ix]) stopDict[sid].lon = float(stops['stop_lon'][ix]) if stops['location_type'][ix] is not '': stopDict[sid].locationType = int( stops['location_type'][ix]) if stops['parent_station'][ix] is not '': stopDict[sid].parentStation = int( stops['parent_station'][ix]) except (KeyError, IndexError, ValueError): print(f"Error: import_stops, ix {ix}, sid {sid}") pass myMap.stopDict = stopDict
def import_shapes(dirPath, myMap): print("Importing shapes...") shapeDict = {} shapes = CvtdUtil.parse_csv(dirPath + "/shapes.txt") try: for ix, sid in enumerate(shapes['shape_id']): if sid not in shapeDict: shapeDict[sid] = GtfsShape() lat = float(shapes['shape_pt_lat'][ix]) lon = float(shapes['shape_pt_lon'][ix]) shapeDict[sid].pointList.append(CvtdNode(lat, lon)) except (KeyError, IndexError, ValueError): print(f"Error: import_shapes, ix {ix}, sid {sid}") pass for shapeId in shapeDict: for cmpShapeId in shapeDict: if shapeId != cmpShapeId: if shapeDict[shapeId].copyOfId is None and shapeDict[ cmpShapeId].copyOfId is None: if shapeDict[shapeId].compare_point_list( shapeDict[cmpShapeId].pointList): shapeDict[cmpShapeId].pointList = [] shapeDict[cmpShapeId].copyOfId = shapeId print(f"Shape {cmpShapeId} is a copy of {shapeId}") myMap.shapeDict = shapeDict
def find_intersecting_roads(self, road_ix, tol): intersections = [] # Find roads that intersect with each segment individually road = self.roadList[road_ix] for point_ix, point in enumerate(road.points[:-1]): n1s = self.nodeDict[point.node] n1e = self.nodeDict[road.points[point_ix + 1].node] # Loop through all roads to see if they intersect with this segment for check_ix, check_road in enumerate(self.roadList): # Don't check to see if a road intersects with itself if check_ix != road_ix: for check_point_ix, check_point in enumerate( check_road.points[:-1]): n2s = self.nodeDict[check_point.node] n2e = self.nodeDict[check_road.points[check_point_ix + 1].node] cross = CvtdUtil.get_line_intersection( n1s, n1e, n2s, n2e) if cross: addr1 = self.compute_addr(cross[0], cross[1], [road_ix])[1] addr2 = self.compute_addr(cross[0], cross[1], [check_ix])[1] p1 = ContainedRoadPoint(addr1, road_ix) p2 = ContainedRoadPoint(addr2, check_ix) intersections.append([p1, p2]) return intersections
def compute_proj_ratio(self, lat, lon, nodeDict): min_point_ix = 0 min_proj_ratio = 0 min_pos_to_proj = 0x7FFFFFFF for point_ix, point in enumerate(self.points[:-1]): blon = nodeDict[point.node].lon blat = nodeDict[point.node].lat elon = nodeDict[self.points[point_ix + 1].node].lon elat = nodeDict[self.points[point_ix + 1].node].lat street_sc, pos_sc, projection_sc, begin_or_end = CvtdUtil.sub_proj_ratio( lat, lon, blat, blon, elat, elon) if projection_sc != 0.0: proj_sc_fixed = min(projection_sc, street_sc) try: pos_to_proj = math.sqrt((pos_sc * pos_sc) - (proj_sc_fixed * proj_sc_fixed)) except ValueError: pos_to_proj = 0 if pos_to_proj < min_pos_to_proj: min_point_ix = point_ix min_proj_ratio = projection_sc / street_sc min_pos_to_proj = pos_to_proj if begin_or_end == 0: min_proj_ratio = 1 - min_proj_ratio else: print( f"Error [compute_proj_ratio] on road called {self.name}: two adjacent points with same node" ) return min_point_ix, min_proj_ratio, min_pos_to_proj
def choose_gps(map): valid, lat, lon = parse_gps() if not valid: return False node_ix = len(map.nodeList) dlist = map.generate_dlist(lat, lon) dlist_sorted = sorted(dlist) node_count = sum([CvtdUtil.coord_to_ft(y) < 150 for y in dlist]) if node_count > 0: print(f"The following {node_count} nodes already exist close to the location you entered:") for i in range(node_count): dlist_ix = dlist.index(dlist_sorted[i]) print(f"{i+1}: {map.nodeList[dlist_ix].lat}, {map.nodeList[dlist_ix].lon} ({CvtdUtil.coord_to_ft(dlist_sorted[i])} feet)") usenode = input("Use instead any of these nodes? Enter 'n' or number: ") try: usenode = int(usenode) if 1 <= usenode <= node_count: plist = map.get_node_usage(dlist_ix) if len(plist) > 0: print("The following roads use this node: ") for point in plist: print(p.addr_repr(map.roadList)) okay = input("Okay (y/n): ") if okay.lower() != 'y': return None else: lat = map.nodeList[dlist_ix].lat lon = map.nodeList[dlist_ix].lon node_ix = dlist_ix except ValueError: # node_ix, lat and lot already have their desired values pass return node_ix, lat, lon
def compute_addr_repr(self, lat, lon, valid_streets): ix, address, error = self.compute_addr(lat, lon, valid_streets) if ix != -1: addrRepr = CvtdUtil.addr_repr(address, self.roadList[ix].dir, self.roadList[ix].name) else: addrRepr = "N/A" return ix, addrRepr, error
def test_parse_csv(self): with open('tmpTest1.csv', 'w') as f: f.write('a,b,c\n') f.write('5,,6\n') f.write('1,2,\n') f.write(',,\n') f.write('x,y,z\n') f.write('"3,3","4,4","5,5"\n') result = CvtdUtil.parse_csv('tmpTest1.csv') os.remove('tmpTest1.csv') self.assertListEqual(['5', '1', '', 'x', '"3,3"'], result['a']) self.assertListEqual(['', '2', '', 'y', '"4,4"'], result['b']) self.assertListEqual(['6', '', '', 'z', '"5,5"'], result['c'])
def compute_addr(self, lat, lon, valid_streets): ix, seg_ix, proj_ratio, error = self.compute_proj_ratio( lat, lon, valid_streets) if ix != -1: vaddr_begin = self.roadList[ix].points[seg_ix].addr vaddr_end = self.roadList[ix].points[seg_ix + 1].addr address = round(proj_ratio * (vaddr_end - vaddr_begin) + vaddr_begin) else: address = 0 return ix, address, CvtdUtil.coord_to_ft(error)
def get_min_point_error(self, lat, lon): min_pos_to_proj = 0x7FFFFFFF for point_ix, point in enumerate(self.pointList[:-1]): blon = point.lon blat = point.lat elon = self.pointList[point_ix + 1].lon elat = self.pointList[point_ix + 1].lat street_sc, pos_sc, projection_sc, begin_or_end = CvtdUtil.sub_proj_ratio(lat, lon, blat, blon, elat, elon) if projection_sc != 0.0: proj_sc_fixed = min(projection_sc, street_sc) try: pos_to_proj = math.sqrt((pos_sc * pos_sc) - (proj_sc_fixed * proj_sc_fixed)) except ValueError: pos_to_proj = 0 if pos_to_proj < min_pos_to_proj: min_pos_to_proj = pos_to_proj else: print(f"Error [get_min_point_error] on shape: two adjacent points with same node") return CvtdUtil.coord_to_ft(min_pos_to_proj)
def import_trips(dirPath, myMap): print("Importing trips...") tripDict = {} trips = CvtdUtil.parse_csv(dirPath + "/trips.txt") try: for ix, tid in enumerate(trips['trip_id']): if tid not in tripDict: tripDict[tid] = GtfsTrip() tripDict[tid].routeId = trips['route_id'][ix] tripDict[tid].serviceId = trips['service_id'][ix] tripDict[tid].directionId = int(trips['direction_id'][ix]) tripDict[tid].blockId = trips['block_id'][ix].replace('"', '') tripDict[tid].shapeId = int(trips['shape_id'][ix]) except (KeyError, IndexError, ValueError): print(f"Error: import_trips, ix {ix}, tid {tid}") pass myMap.tripDict = tripDict
def import_routes(dirPath, myMap): print("Importing routes...") routeDict = {} routes = CvtdUtil.parse_csv(dirPath + "/routes.txt") try: for ix, rid in enumerate(routes['route_id']): if rid not in routeDict: routeDict[rid] = GtfsStop() routeDict[rid].agencyId = routes['agency_id'][ix] routeDict[rid].routeShortName = routes['route_short_name'][ix] routeDict[rid].routeLongName = routes['route_long_name'][ix] routeDict[rid].routeDesc = routes['route_desc'][ix] routeDict[rid].routeType = int(routes['route_type'][ix]) except (KeyError, IndexError, ValueError): print(f"Error: import_routes, ix {ix}, rid {rid}") pass myMap.routeDict = routeDict
def import_stop_times(dirPath, myMap): print("Importing stop times...") stopTimeDict = {} stopTimes = CvtdUtil.parse_csv(dirPath + "/stop_times.txt") try: for ix, tid in enumerate(stopTimes['trip_id']): sid = stopTimes['stop_id'][ix] if (tid, sid) not in stopTimeDict: stopTimeDict[(tid, sid)] = GtfsStopTime() stopTimeDict[(tid, sid)].departure = datetime.datetime.strptime( stopTimes['departure_time'][ix], "%H:%M:%S").time() stopTimeDict[(tid, sid)].arrival = datetime.datetime.strptime( stopTimes['arrival_time'][ix], "%H:%M:%S").time() stopTimeDict[( tid, sid)].timepoint = stopTimes['timepoint'][ix] == '1' except (KeyError, IndexError, ValueError): print(f"Error: import_stop_times, ix {ix}, tid {tid}, sid {sid}") pass myMap.stopTimeDict = stopTimeDict
def search_road(self): search = input("Search for a road with the following text: ") roadIxList = [i for i in range(len(self.roadList)) if search in self.roadList[i].name] ways = [self.roadList[way] for way in roadIxList] # ways = [way for way in self.roadList if search in way.name] if len(roadIxList) == 0: print("No ways found.") return # Print information about each of the matching ways for ix, way in enumerate(ways): try: print(f"[{ix+1}]: {way.name} has {len(way.points)} nodes") print(" " + ", ".join([f"({self.nodeDict[p.node].lat}, {self.nodeDict[p.node].lon})" for p in way.points])) except KeyError: print(f"Unknown node") # Here, compare with what is currently in the file to see if we want to add onto a road, modify a road, or do nothing # For the present, we'll just add a new road whichWay = CvtdUtil.input_int("Adding a new road. Start with which way? ", 1, len(ways)) if type(whichWay) is int: self.add_road(roadIxList[whichWay - 1], None)
def test_split_quotes_empty_field(self): s = "2,3,'boo',,\"hello\",,7" self.assertListEqual(["2", "3", "'boo'", "", "\"hello\"", "", "7"], CvtdUtil.split_quotes(s, ','))
def addr_repr(self, roadList): return CvtdUtil.addr_repr(self.addr, roadList[self.roadIx].dir, roadList[self.roadIx].name)
def addr_dir(self, roadList): return CvtdUtil.addr_dir(self.addr, roadList[self.roadIx].dir)
def add_road(self, wayIx, baseRoadIx): way = self.roadList[wayIx] addedRoads = [wayIx] newRoad = CvtdRoad() if baseRoadIx is not None: # If we're appending onto a road, add all the nodes from the main map into this map baseRoad = self.mainMap.roadList[baseRoadIx] newRoad.name = baseRoad.name newRoad.dir = baseRoad.dir for point in baseRoad.points: self.nodeDict[point.node] = self.mainMap.nodeDict[point.node] newRoad.points.append(CvtdRoadPoint(point.node, point.addr)) print(f"{way.name} goes from {self.nodeDict[way.points[0].node]} to {self.nodeDict[way.points[-1].node]} with {len(way.points)} points") newName = input(f'Enter a name for this road (leave blank for "{way.name}"): ') if newName: newRoad.name = newName else: newRoad.name = way.name newRoad.dir = input(f"Enter a direction for {newRoad.name} ('N/S', 'E/W', or other): ") minValue = None maxValue = None lastValue = None # Merge check, only once, at the end. Reset to True if they accept the merge mergeCheck = True # For each point in this way pointList = way.points # Let them come back to some points if desired while len(pointList) > 0: returnPointList = [] for point in pointList: # Don't let the same point get added twice if point.node in [p.node for p in newRoad.points]: continue # Store the node in question node = self.nodeDict[point.node] # Look for ways in the OSM file that also use this node newIntersections = [] for checkWay in self.roadList: if point.node in [checkPoint.node for checkPoint in checkWay.points]: newIntersections.append(checkWay) # Calculate the "best guess" for the address at this point interpolateAddress = newRoad.estimate_addr(node, self.nodeDict) print(f"\nNext point is ({node.lat}, {node.lon}), best guess {interpolateAddress}") if len(newIntersections) > 0: print(f"The following {len(newIntersections)} roads from the OSM file intersect at this node: ") for way in newIntersections: print(" - " + way.name + ': ' + str(way.tags)) # Determine a max and a min value for this point if len(newRoad.points) == 0: minValue = None maxValue = None neValue = None elif len(newRoad.points) == 1: minValue = None maxValue = None neValue = newRoad.points[0].addr else: point_ix, proj_ratio, error = newRoad.compute_proj_ratio(node.lat, node.lon, self.nodeDict) inc = newRoad.increasing() if (proj_ratio < 0 and inc) or (proj_ratio > 1 and not inc): # Address must be less than the first address minValue = None maxValue = newRoad.points[0].addr - 1 neValue = None elif (proj_ratio > 1 and inc) or (proj_ratio < 0 and not inc): # Address must be greater than the final address minValue = newRoad.points[-1].addr + 1 maxValue = None neValue = None else: # Address must be between point #point_ix and the following point minValue = newRoad.points[point_ix].addr + 1 if inc else newRoad.points[point_ix + 1].addr - 1 maxValue = newRoad.points[point_ix + 1].addr - 1 if inc else newRoad.points[point_ix].addr + 1 neValue = None # Now let them choose an address, or 'skip' (ignore this point), or 'return' (come back to this point later) repeatThisAddr = True while repeatThisAddr: repeatThisAddr = False addr = CvtdUtil.input_int("Enter the address for this point, or 'skip', 'return' or 'edit': ", minValue, maxValue, neValue, ['skip', 'return', 'edit', 'quit', 's', 'r', 'e', 'q']) if addr is not None: if addr in ["skip", 's']: pass elif addr in ["return", 'r']: returnPointList.append(point) elif addr in ["edit", 'e']: newRoad.edit(self.nodeDict) repeatThisAddr = True elif addr in ["quit", 'q']: return else: newRoad.insert(CvtdRoadPoint(point.node, addr)) newRoad.describe(self.nodeDict) else: return if len(returnPointList) > 0: pointList = returnPointList elif mergeCheck: pointList = [] mergeCheck = False print("\nYou've defined the following road:") newRoad.describe(self.nodeDict) print('') # They successfully added all the points. See if they want to merge with another road possibleMerges = [way for way in self.roadList if newRoad.extendable(way)] if len(possibleMerges) > 0: print(f"There are {len(possibleMerges)} ways that you could merge into this road. ") for ix, way in enumerate(possibleMerges): print(f"[{ix+1}]: {way.name} has {len(way.points)} nodes") print(" " + ", ".join([f"({self.nodeDict[p.node].lat}, {self.nodeDict[p.node].lon})" for p in way.points])) mergeWay = CvtdUtil.input_int(f"Merge another road into this road? (1-{len(possibleMerges)}) or (n)o: ", 1, len(possibleMerges), validAnswers=['n', 'no']) if type(mergeWay) is int: mergeWay = mergeWay - 1 pointList = possibleMerges[mergeWay].points addedRoads.append(self.roadList.index(possibleMerges[mergeWay])) mergeCheck = True else: # They were offered a merge check but they rejected it pointList = [] confirm = 'e' while confirm == 'e': confirm = input("Add this new road to your map? (y/n/[e]dit): ") if confirm == 'e': newRoad.edit(self.nodeDict) print("\nYou've defined the following road:") newRoad.describe(self.nodeDict) print('') elif confirm == 'y': # If we are replacing a street (adding onto one), replace. Else add import pdb; pdb.set_trace() if baseRoadIx is not None: self.mainMap.replace_street_with_nodes(newRoad, self.nodeDict, baseRoadIx) else: self.mainMap.add_street_with_nodes(newRoad, self.nodeDict) print(f"You now have {len(self.mainMap.roadList)} roads.") # Remove each road, adjusting other indices for each road removed for ixOfIx, ixToRemove in enumerate(addedRoads): # Delete the ixToRemove'th index in self.roadList del self.roadList[ixToRemove] # Decrement all successive indices that are greater than ixToRemove for ixOfAdjustIx, ixToAdjust in enumerate(addedRoads[ixOfIx+1:]): if ixToAdjust > ixToRemove: addedRoads[ixOfAdjustIx] = addedRoads[ixOfAdjustIx] - 1 return addedRoads
def test_coord_to_ft(self): self.assertEqual(CvtdUtil.coord_to_ft(1), 369600)