diff --git a/cmd/main.go b/cmd/main.go index 5a48e9a..dcc1961 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -134,6 +134,7 @@ func feedifyVehicles(vehicles []*pb.VehiclePosition, header *pb.FeedHeader, rout rows, err := scheduleDb.Query(` SELECT trips.route_id, + trips.shape_id, trips.trip_headsign, routes.route_type, routes.route_color, @@ -150,6 +151,7 @@ func feedifyVehicles(vehicles []*pb.VehiclePosition, header *pb.FeedHeader, rout } var route_id string + var shape_id string var trip_headsign sql.NullString var route_type int32 var route_color sql.NullString @@ -160,7 +162,7 @@ func feedifyVehicles(vehicles []*pb.VehiclePosition, header *pb.FeedHeader, rout continue } - err = rows.Scan(&route_id, &trip_headsign, &route_type, &route_color, &route_short_name, &route_long_name) + err = rows.Scan(&route_id, &shape_id, &trip_headsign, &route_type, &route_color, &route_short_name, &route_long_name) if err != nil { fmt.Println(err) continue @@ -195,7 +197,8 @@ func feedifyVehicles(vehicles []*pb.VehiclePosition, header *pb.FeedHeader, rout ShortName: route_short_name.String, LongName: route_long_name.String, }, - TripId: *vehicle.Trip.TripId, + TripId: *vehicle.Trip.TripId, + ShapeId: shape_id, }) } @@ -288,6 +291,37 @@ func tripHandler(w http.ResponseWriter, r *http.Request) { w.Write(response) } +func shapeHandler(w http.ResponseWriter, r *http.Request) { + rows, err := scheduleDb.Query(`SELECT shapes.shape_pt_lat, shapes.shape_pt_lon FROM shapes WHERE shapes.shape_id = ? ORDER BY shapes.shape_pt_sequence ASC`, r.PathValue("shape")) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + var points [][]float64 + + for rows.Next() { + var lat float64 + var lon float64 + err = rows.Scan(&lat, &lon) + if err != nil { + break + } + + points = append(points, []float64{lat, lon}) + } + + if len(points) == 0 { + w.WriteHeader(http.StatusNotFound) + return + } + + w.Header().Add("Content-Type", "application/json") + + response, _ := json.Marshal(map[string]any{"shape": points}) + w.Write(response) +} + func main() { fmt.Println("Opening database...") @@ -307,6 +341,7 @@ func main() { http.HandleFunc("/api", vehicleHandler("")) http.HandleFunc("/api.json", vehicleHandler("json")) http.HandleFunc("/api/trips/{trip}", tripHandler) + http.HandleFunc("/api/shapes/{shape}", shapeHandler) port := "3000" if portEnv, ok := os.LookupEnv("PORT"); ok { diff --git a/proto/schema.pb.go b/proto/schema.pb.go index ebb1182..73a46ec 100644 --- a/proto/schema.pb.go +++ b/proto/schema.pb.go @@ -386,6 +386,7 @@ type VehicleFeed_Vehicle struct { Headsign string `protobuf:"bytes,7,opt,name=headsign,proto3" json:"headsign,omitempty"` Route *VehicleFeed_Route `protobuf:"bytes,8,opt,name=route,proto3" json:"route,omitempty"` TripId string `protobuf:"bytes,10,opt,name=trip_id,json=tripId,proto3" json:"trip_id,omitempty"` + ShapeId string `protobuf:"bytes,11,opt,name=shape_id,json=shapeId,proto3" json:"shape_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -490,11 +491,18 @@ func (x *VehicleFeed_Vehicle) GetTripId() string { return "" } +func (x *VehicleFeed_Vehicle) GetShapeId() string { + if x != nil { + return x.ShapeId + } + return "" +} + var File_proto_schema_proto protoreflect.FileDescriptor const file_proto_schema_proto_rawDesc = "" + "\n" + - "\x12proto/schema.proto\"\xe8\x06\n" + + "\x12proto/schema.proto\"\x83\a\n" + "\vVehicleFeed\x120\n" + "\bvehicles\x18\x01 \x03(\v2\x14.VehicleFeed.VehicleR\bvehicles\x12)\n" + "\x04info\x18\x02 \x01(\v2\x15.VehicleFeed.FeedInfoR\x04info\x1a+\n" + @@ -519,7 +527,7 @@ const file_proto_schema_proto_rawDesc = "" + "\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\xba\x02\n" + + "\x03lon\x18\x04 \x01(\x02R\x03lon\x1a\xd5\x02\n" + "\aVehicle\x12\x10\n" + "\x03lat\x18\x01 \x01(\x02R\x03lat\x12\x10\n" + "\x03lon\x18\x02 \x01(\x02R\x03lon\x12\x18\n" + @@ -531,7 +539,8 @@ const file_proto_schema_proto_rawDesc = "" + "\bheadsign\x18\a \x01(\tR\bheadsign\x12(\n" + "\x05route\x18\b \x01(\v2\x12.VehicleFeed.RouteR\x05route\x12\x17\n" + "\atrip_id\x18\n" + - " \x01(\tR\x06tripId\"Z\n" + + " \x01(\tR\x06tripId\x12\x19\n" + + "\bshape_id\x18\v \x01(\tR\ashapeId\"Z\n" + "\x04Line\x12\x14\n" + "\x10LINE_UNSPECIFIED\x10\x00\x12\t\n" + "\x05GREEN\x10\x01\x12\a\n" + diff --git a/proto/schema.proto b/proto/schema.proto index c71fb73..f8cd618 100644 --- a/proto/schema.proto +++ b/proto/schema.proto @@ -1,4 +1,5 @@ syntax = "proto3"; + option go_package = "github.com/cjdenio/uta-trax-api/proto"; message VehicleFeed { @@ -49,6 +50,7 @@ message VehicleFeed { string headsign = 7; Route route = 8; string trip_id = 10; + string shape_id = 11; } repeated Vehicle vehicles = 1; diff --git a/static/index.html b/static/index.html index 309b7e7..07bf4ad 100644 --- a/static/index.html +++ b/static/index.html @@ -300,6 +300,6 @@ - + diff --git a/static/js/main.js b/static/js/main.js index 242c1ec..2ae65e8 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -13,6 +13,13 @@ function displayLastUpdated() { setInterval(displayLastUpdated, 1000) +const BRT_ROUTES = [ + "92235", // OGX + "3686", // UVX + "87711", // MVX + "90585", // OGX Wildcat Shuttle +] + const RouteType = { TRAM: 1, RAIL: 3, @@ -206,7 +213,7 @@ function renderVehicleIcon(vehicle) { className: "", html: `
r.json()); - tripShapeCache.set(tripId, data.shape); + const data = await fetch(`/api/shapes/${shapeId}`).then(r => r.json()); + tripShapeCache.set(shapeId, data.shape); return data.shape; } } @@ -265,8 +272,7 @@ function renderVehicle(vehicle) { : 3000, icon: renderVehicleIcon(vehicle), }).bindPopup(vehiclePopupContent(vehicle)).on("popupopen", () => { - getTripShape(vehicle.trip_id).then(shape => { - console.log(shape) + getTripShape(vehicle.shape_id).then(shape => { for (const layer of railLayer.getLayers()) { layer.setStyle({ opacity: 0.2 }); } @@ -312,7 +318,7 @@ async function reload() { currentVehicles.get(vehicle.id).marker.setPopupContent(vehiclePopupContent(vehicle)) } else { const marker = renderVehicle(vehicle) - const layer = ["92235", "3686", "87711"].includes(vehicle.route.id) + const layer = BRT_ROUTES.includes(vehicle.route.id) ? brtLayer : vehicle.route.type == RouteType.TRAM ? traxLayer