diff --git a/cmd/main.go b/cmd/main.go index 47a6e25..cc29228 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,9 +1,12 @@ package main import ( + "database/sql" "encoding/csv" + "fmt" "io" "log" + "math" "net/http" "os" "slices" @@ -11,8 +14,62 @@ import ( pb "github.com/cjdenio/uta-trax-api/proto" "google.golang.org/protobuf/proto" + + _ "github.com/mattn/go-sqlite3" ) +var scheduleDb *sql.DB + +func distance(lat1 float64, lng1 float64, lat2 float64, lng2 float64) float64 { + radlat1 := float64(math.Pi * lat1 / 180) + radlat2 := float64(math.Pi * lat2 / 180) + + theta := float64(lng1 - lng2) + radtheta := float64(math.Pi * theta / 180) + + dist := math.Sin(radlat1)*math.Sin(radlat2) + math.Cos(radlat1)*math.Cos(radlat2)*math.Cos(radtheta) + if dist > 1 { + dist = 1 + } + + dist = math.Acos(dist) + dist = dist * 180 / math.Pi + dist = dist * 60 * 1.1515 + + return dist +} + +func getStationForVehicle(vehicle *pb.VehiclePosition) *pb.VehicleFeed_Station { + rows, err := scheduleDb.Query(`SELECT stop_lat, stop_lon, stops.stop_id, stop_name FROM stop_times INNER JOIN stops ON stops.stop_id = stop_times.stop_id WHERE stop_times.trip_id = ? AND stop_times.pickup_type != 1 AND stop_times.drop_off_type != 1;`, vehicle.Trip.TripId) + if err != nil { + fmt.Println(err) + } + + nearestStop := new(pb.VehicleFeed_Station) + nearestStopDistance := math.Inf(1) + + for rows.Next() { + var stop_lat float32 + var stop_lon float32 + var stop_id string + var stop_name string + + rows.Scan(&stop_lat, &stop_lon, &stop_id, &stop_name) + + distance := distance(float64(stop_lat), float64(stop_lon), float64(*vehicle.Position.Latitude), float64(*vehicle.Position.Longitude)) + if distance < nearestStopDistance { + nearestStop.Id = stop_id + nearestStop.Name = stop_name + nearestStop.Lat = stop_lat + nearestStop.Lon = stop_lon + + nearestStopDistance = distance + } + } + + return nearestStop +} + func getVehicles() ([]*pb.VehiclePosition, error) { resp, err := http.Get("https://apps.rideuta.com/tms/gtfs/Vehicle") if err != nil { @@ -86,9 +143,9 @@ func loadTrips() error { case "45389": trip_info.Line = pb.VehicleFeed_STREETCAR trips[record[trip_id]] = trip_info - case "41065": - trip_info.Line = pb.VehicleFeed_FRONTRUNNER - trips[record[trip_id]] = trip_info + // case "41065": + // trip_info.Line = pb.VehicleFeed_FRONTRUNNER + // trips[record[trip_id]] = trip_info } } else { break @@ -108,11 +165,12 @@ func feedifyVehicles(vehicles []*pb.VehiclePosition) pb.VehicleFeed { } vehicle_feed = append(vehicle_feed, &pb.VehicleFeed_Vehicle{ - Lat: *vehicle.Position.Latitude, - Lon: *vehicle.Position.Longitude, - Line: trip.Line, - Id: *vehicle.Vehicle.Id, - Direction: trip.Direction, + Lat: *vehicle.Position.Latitude, + Lon: *vehicle.Position.Longitude, + Line: trip.Line, + Id: *vehicle.Vehicle.Id, + Direction: trip.Direction, + NearestStation: getStationForVehicle(vehicle), }) } @@ -126,6 +184,22 @@ func main() { log.Fatalln(err) } + _db, err := sql.Open("sqlite3", "uta-gtfs.db") + if err != nil { + log.Fatal(err) + } + scheduleDb = _db + + // vehicles, err := getVehicles() + // if err != nil { + // log.Fatalln(err) + // } + + // for _, v := range vehicles { + // station := getStationForVehicle(v) + // fmt.Println(station) + // } + http.Handle("/", http.FileServer(http.Dir("./static"))) http.HandleFunc("/schema.proto", func(w http.ResponseWriter, r *http.Request) { @@ -144,5 +218,12 @@ func main() { w.Write(b) }) - http.ListenAndServe(":"+os.Getenv("PORT"), nil) + port := "3000" + if portEnv, ok := os.LookupEnv("PORT"); ok { + port = portEnv + } + + fmt.Printf("Started on port %s\n", port) + + http.ListenAndServe(":"+port, nil) } diff --git a/go.mod b/go.mod index f333d55..c2018e8 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/cjdenio/uta-trax-api go 1.24.2 -require google.golang.org/protobuf v1.36.6 // indirect +require ( + github.com/mattn/go-sqlite3 v1.14.28 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/go.sum b/go.sum index 54d2c0b..cc9af62 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= diff --git a/proto/schema.pb.go b/proto/schema.pb.go index 697514d..45c55ab 100644 --- a/proto/schema.pb.go +++ b/proto/schema.pb.go @@ -123,20 +123,89 @@ func (x *VehicleFeed) GetVehicles() []*VehicleFeed_Vehicle { return nil } -type VehicleFeed_Vehicle struct { +type VehicleFeed_Station struct { state protoimpl.MessageState `protogen:"open.v1"` - Lat float32 `protobuf:"fixed32,1,opt,name=lat,proto3" json:"lat,omitempty"` - Lon float32 `protobuf:"fixed32,2,opt,name=lon,proto3" json:"lon,omitempty"` - Line VehicleFeed_Line `protobuf:"varint,3,opt,name=line,proto3,enum=VehicleFeed_Line" json:"line,omitempty"` - Direction int32 `protobuf:"varint,4,opt,name=direction,proto3" json:"direction,omitempty"` - Id string `protobuf:"bytes,5,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Lat float32 `protobuf:"fixed32,3,opt,name=lat,proto3" json:"lat,omitempty"` + Lon float32 `protobuf:"fixed32,4,opt,name=lon,proto3" json:"lon,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } +func (x *VehicleFeed_Station) Reset() { + *x = VehicleFeed_Station{} + mi := &file_proto_schema_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VehicleFeed_Station) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VehicleFeed_Station) ProtoMessage() {} + +func (x *VehicleFeed_Station) ProtoReflect() protoreflect.Message { + mi := &file_proto_schema_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VehicleFeed_Station.ProtoReflect.Descriptor instead. +func (*VehicleFeed_Station) Descriptor() ([]byte, []int) { + return file_proto_schema_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *VehicleFeed_Station) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *VehicleFeed_Station) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VehicleFeed_Station) GetLat() float32 { + if x != nil { + return x.Lat + } + return 0 +} + +func (x *VehicleFeed_Station) GetLon() float32 { + if x != nil { + return x.Lon + } + return 0 +} + +type VehicleFeed_Vehicle struct { + state protoimpl.MessageState `protogen:"open.v1"` + Lat float32 `protobuf:"fixed32,1,opt,name=lat,proto3" json:"lat,omitempty"` + Lon float32 `protobuf:"fixed32,2,opt,name=lon,proto3" json:"lon,omitempty"` + Line VehicleFeed_Line `protobuf:"varint,3,opt,name=line,proto3,enum=VehicleFeed_Line" json:"line,omitempty"` + Direction int32 `protobuf:"varint,4,opt,name=direction,proto3" json:"direction,omitempty"` + Id string `protobuf:"bytes,5,opt,name=id,proto3" json:"id,omitempty"` + NearestStation *VehicleFeed_Station `protobuf:"bytes,6,opt,name=nearest_station,json=nearestStation,proto3" json:"nearest_station,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + func (x *VehicleFeed_Vehicle) Reset() { *x = VehicleFeed_Vehicle{} - mi := &file_proto_schema_proto_msgTypes[1] + mi := &file_proto_schema_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -148,7 +217,7 @@ func (x *VehicleFeed_Vehicle) String() string { func (*VehicleFeed_Vehicle) ProtoMessage() {} func (x *VehicleFeed_Vehicle) ProtoReflect() protoreflect.Message { - mi := &file_proto_schema_proto_msgTypes[1] + mi := &file_proto_schema_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -161,7 +230,7 @@ func (x *VehicleFeed_Vehicle) ProtoReflect() protoreflect.Message { // Deprecated: Use VehicleFeed_Vehicle.ProtoReflect.Descriptor instead. func (*VehicleFeed_Vehicle) Descriptor() ([]byte, []int) { - return file_proto_schema_proto_rawDescGZIP(), []int{0, 0} + return file_proto_schema_proto_rawDescGZIP(), []int{0, 1} } func (x *VehicleFeed_Vehicle) GetLat() float32 { @@ -199,19 +268,32 @@ func (x *VehicleFeed_Vehicle) GetId() string { return "" } +func (x *VehicleFeed_Vehicle) GetNearestStation() *VehicleFeed_Station { + if x != nil { + return x.NearestStation + } + return nil +} + var File_proto_schema_proto protoreflect.FileDescriptor const file_proto_schema_proto_rawDesc = "" + "\n" + - "\x12proto/schema.proto\"\xa0\x02\n" + + "\x12proto/schema.proto\"\xb2\x03\n" + "\vVehicleFeed\x120\n" + - "\bvehicles\x18\x01 \x03(\v2\x14.VehicleFeed.VehicleR\bvehicles\x1a\x82\x01\n" + + "\bvehicles\x18\x01 \x03(\v2\x14.VehicleFeed.VehicleR\bvehicles\x1aQ\n" + + "\aStation\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x10\n" + + "\x03lat\x18\x03 \x01(\x02R\x03lat\x12\x10\n" + + "\x03lon\x18\x04 \x01(\x02R\x03lon\x1a\xc1\x01\n" + "\aVehicle\x12\x10\n" + "\x03lat\x18\x01 \x01(\x02R\x03lat\x12\x10\n" + "\x03lon\x18\x02 \x01(\x02R\x03lon\x12%\n" + "\x04line\x18\x03 \x01(\x0e2\x11.VehicleFeed.LineR\x04line\x12\x1c\n" + "\tdirection\x18\x04 \x01(\x05R\tdirection\x12\x0e\n" + - "\x02id\x18\x05 \x01(\tR\x02id\"Z\n" + + "\x02id\x18\x05 \x01(\tR\x02id\x12=\n" + + "\x0fnearest_station\x18\x06 \x01(\v2\x14.VehicleFeed.StationR\x0enearestStation\"Z\n" + "\x04Line\x12\x14\n" + "\x10LINE_UNSPECIFIED\x10\x00\x12\t\n" + "\x05GREEN\x10\x01\x12\a\n" + @@ -233,20 +315,22 @@ func file_proto_schema_proto_rawDescGZIP() []byte { } var file_proto_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proto_schema_proto_goTypes = []any{ (VehicleFeed_Line)(0), // 0: VehicleFeed.Line (*VehicleFeed)(nil), // 1: VehicleFeed - (*VehicleFeed_Vehicle)(nil), // 2: VehicleFeed.Vehicle + (*VehicleFeed_Station)(nil), // 2: VehicleFeed.Station + (*VehicleFeed_Vehicle)(nil), // 3: VehicleFeed.Vehicle } var file_proto_schema_proto_depIdxs = []int32{ - 2, // 0: VehicleFeed.vehicles:type_name -> VehicleFeed.Vehicle + 3, // 0: VehicleFeed.vehicles:type_name -> VehicleFeed.Vehicle 0, // 1: VehicleFeed.Vehicle.line:type_name -> VehicleFeed.Line - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 2, // 2: VehicleFeed.Vehicle.nearest_station:type_name -> VehicleFeed.Station + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proto_schema_proto_init() } @@ -260,7 +344,7 @@ func file_proto_schema_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_schema_proto_rawDesc), len(file_proto_schema_proto_rawDesc)), NumEnums: 1, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/schema.proto b/proto/schema.proto index 341fe21..8b6f4e2 100644 --- a/proto/schema.proto +++ b/proto/schema.proto @@ -11,12 +11,20 @@ message VehicleFeed { FRONTRUNNER = 5; } + message Station { + string id = 1; + string name = 2; + float lat = 3; + float lon = 4; + } + message Vehicle { float lat = 1; float lon = 2; Line line = 3; int32 direction = 4; string id = 5; + Station nearest_station = 6; } repeated Vehicle vehicles = 1; diff --git a/static/index.html b/static/index.html index 52c3779..08df094 100644 --- a/static/index.html +++ b/static/index.html @@ -55,7 +55,9 @@ color: COLORS[vehicle.line], fillOpacity: 0.5, radius: 200, - }).addTo(map); + }) + .addTo(map) + .bindPopup(vehicle.nearestStation.name); }); }); diff --git a/uta-gtfs.db b/uta-gtfs.db new file mode 100644 index 0000000..8972739 Binary files /dev/null and b/uta-gtfs.db differ