Skip to content

The Longmont Pothole Project

The city of Longmont, Colorado has a pothole problem and it’s so bad that I’ve decided to try to do something about it.

The city has very thoughtfully provided a pothole reporting page but it takes quite a while to fill out the page every time that you need to report a single pothole.

Initially, I tried to generate some python code to auto-submit pothole filling requests, but the server-side requires a unique token from the initial visit that’s in javascript only and carried with the user throughout the form filling process which was such a big pain in the ass that I decided to just punt and go a different direction.

I called up the City and finally was able to talk with the guy who’s in charge of the team that fills the potholes in the city. He was nice enough to take my call, and I was afraid that he’d be all defensive if I started to rant about how bad the pothole situation in the city was right away. He and I talked for about 15 minutes. I asked tons of questions about how the process currently works, how many people he has working under him, are they filled by-hand or is there an automated truck that does the work, etc.

I felt kinda bad for the guy after talking to him. He is understaffed and only gets a handful of pothole reports a week, so he has to track down and make notes of the potholes himself as he’s driving around the city. I told him about my attempt at creating a system to at least report the potholes on the major streets in the city, and he was very receptive. I asked him if I could submit a map of where I found the potholes, and he said, “sure”.

Attempt #1: android app

I first wrote an android app which I could attach to my car and bring up when I was driving around the city and just hit a button on the app to save and report an email. The app had a second button to send the locations to me in an email once I stopped driving.

RESULTS?

Well, it turns out that the locations were all over the place due to my phone’s GPS accuracy (The Google Pixel 6 Pro GPS is apparently not great). Some potholes were in ditches and tens of feet or tens of yards away from where they actually were located. The phone was also inside the cab of my car so the GPS was affected by noise in the car and the frame of the car itself, etc. Also, when I stopped, I noticed that the GPS drifted quite a bit, so only moving GPS data was even semi-trustworthy.

Attempt #2: raspberry pi zero w/ USB accessories

I had fashioned myself a little gadget made from a USB keyboard with 3 keys, a USB GPS that magnetically sticks to the roof of my car and a raspberry pi zero W with the battery hat. The battery gives me about 2.5 hours of driving time (and I can optionally charge it as I drive). The roof-mounted GPS gives me much more accurate location readings at 1-second intervals and the USB 3-button keyboard is small enough that I can hold it in my hand while I’m driving and not take up too much space.

Price all-in was around $40

Here’s the initial version of the code (I’m still kind of tinkering with it though). One thread reads GPS data, one thread reads keyboard input. One file is used to record pothole locations and one file is just the GPS trail. The 2nd and 3rd keys on the keyboard can be used as an “undo” button in case I want to un-flag a false-positive as I drive over the pothole. The files are opened, written to, then closed in case of a power outage, the files won’t have been left open hopefully to avoid any file corruption.

Feel free to heckle me on my coding style. :)

#!/usr/bin/env python3  
from gps3 import gps3  
import json  
import threading  
import time  
import keyboard  
from datetime import datetime  

logtime = time.strftime("%Y%m%d-%H%M%S")  
lat = 0.0  
lon = 0.0  
gps_time = None  
lat_old = 0.0  
lon_old = 0.0  
gps_old_time = None  
enter_time = None  
new_pothole = False  

def latlondiff(lat1, lat2, amount):  
    if (lat1 > lat2):  
        return ((lat1-lat2)*amount)+lat2  
    else:  
        return ((lat2-lat1)*amount)+lat1  

def gps_real(name):  
    global lat, lon, gps_time, lat_old, lon_old, gps_old_time, new_pothole, logtime, enter_time  
    gps_socket = gps3.GPSDSocket()  
    data_stream = gps3.DataStream()  
    gps_socket.connect()  
    gps_socket.watch()  
    for new_data in gps_socket:  
        if new_data:  
            nowtime = datetime.now()  
            data_stream.unpack(new_data)  
            print("Loc: ", data_stream.TPV['lat'], data_stream.TPV['lon'])  
            lat_old = lat  
            lon_old = lon  
            gps_old_time = gps_time  
            lat = data_stream.TPV['lat']  
            lon = data_stream.TPV['lon']  
            g = open('track-'+logtime+'.txt', 'a')  
            g.write(str(lat)+","+str(lon)+","+str(nowtime)+"\n")  
            g.close()  
            gps_time = nowtime  
            if new_pothole:  
                timediff = (enter_time - gps_old_time).total_seconds()  
                print("TimeDiff (in seconds, should be less than 1):",timediff)  
                try:  
                    accurate_lat = latlondiff(float(lat),float(lat_old),timediff)  
                    accurate_lon = latlondiff(float(lon),float(lon_old),timediff)  
                    print("pothole at:",accurate_lat,accurate_lon,entertime)  
                    f = open('holes-'+logtime+'.txt', 'a')  
                    f.write(str(accurate_lat)+","+str(accurate_lon)+","+entertime+"\n")  
                    f.close()  
                except:  
                    pass  
                new_pothole = False  
def get_input(name):  
    global enter_time,new_pothole,keyboard,logtime  
    while True:  
        keyboard.wait()  
        if keyboard.is_pressed('a'):  
            enter_time = datetime.now()  
            new_pothole = True  
        if (keyboard.is_pressed('b') or keyboard.is_pressed('c'):  
            print("UNDO")  
            f = open('holes-'+logtime+'.txt', 'a')  
            f.write("UNDO\n")  
            f.close()  

if __name__ == "__main__":  
    x = threading.Thread(target=gps_real, args=(1,))  
    x.start()  
    y = threading.Thread(target=get_input, args=(1,))  
    y.start()

Driving around was pretty inconspicuous

I was able to locate about 250-ish potholes in my first two 40-minute drives around the city. Longmont has about 340 miles of road in total. I’d like to start with most of the primary roads and then if I have time and the nerves to do it, I’ll hit all the residential roads too.

I’m going to continue to use this method of pothole detection for a month or two and see how it goes.

Here’s the live map of the detected potholes around the city if you’re interested in following my progress:

My idea for version #3: seriously over-engineered

If the “click a button every time I drive over a pothole” version doesn’t seem to work out, I’ve got in mind an even nerdier solution.

I bought an XBOX Kinect v2 (the one with higher resolution) and I’ll strap it to the front of my car and drive around the city and literally scan every inch of road. The Kinect has a depth sensor on it and I should be able to generate 3D relief maps of every road that I traverse.

Once I have depth data for every road, I can do some offline processing of the data and generate an image and a 3D profile of each 1-second’s worth of GPS data. Then I could identify the largest potholes and report them as the highest priority to the city leaving the smaller potholes as a lower priority. I could provide a website with each picture and 3D relief map of the pothole. Technically I could even provide the amount of filler that would be needed to fill each hole as well, but I might not actually bother with that.

There are a couple of downsides to this solution though:

  1. The sun. The sun generates enough IR light that it drowns out the IR from the Kinect, so the driving would have to happen after sundown.
  2. This would require a (bunch) of OpenCL code to stitch together the point cloud data and images and then analyse the pothole data to detect the size of each pothole. Also, if the pothole spanned more than one-second’s worth of GPS ‘length’, I’d have to stitch together a much larger area.
  3. I’m sure that curved or bowed roads (roads that are higher in the middle than on the sides) would make the pothole detection more tricky 3D-wise. Also writing some code to ignore normal road features (curbs, etc.) might be difficult.
  4. noise in the sensor or bugs flying around or a dirty lens, all of these real world problems could cause this version to be a huge headache.

I have all the hardware that I’d need to build v3 of the pothole detector, but it seems like it would be a lot of work, so I’m hoping that v2 of the pothole detector will be enough to improve the state of the city’s roads. I’d like to keep it as simple as possible for my own sanity. :)

to be continued …