package main import ( "encoding/json" "encoding/xml" "fmt" "log" "os" "regexp" "strconv" ) type OsmBounds struct { MinLat float64 `xml:"minlat,attr"` MinLon float64 `xml:"minlon,attr"` MaxLat float64 `xml:"maxlat,attr"` MaxLon float64 `xml:"maxlon,attr"` } type OsmNode struct { Id int64 `xml:"id,attr"` Lat float64 `xml:"lat,attr"` Lon float64 `xml:"lon,attr"` Version int64 `xml:"version,attr"` Timestamp string `xml:"timestamp,attr"` Changeset int64 `xml:"changeset,attr"` Uid int64 `xml:"uid,attr"` User string `xml:"user,attr"` } type OsmMember struct { Type string `xml:"type,attr"` Ref int64 `xml:"ref,attr"` Role string `xml:"role,attr"` } type OsmTag struct { K string `xml:"k,attr"` V string `xml:"v,attr"` } type OsmWay struct { Id int64 `xml:"id,attr"` Version int64 `xml:"version,attr"` Timestamp string `xml:"timestamp,attr"` Changeset int64 `xml:"changeset,attr"` Uid int64 `xml:"uid,attr"` User string `xml:"user,attr"` Nds []OsmNd `xml:"nd"` } type OsmNd struct { Ref int64 `xml:"ref,attr"` } type OsmRelation struct { Id int64 `xml:"id,attr"` Version int32 `xml:"version,attr"` Timestamp string `xml:"timestamp,attr"` Changeset int64 `xml:"changeset,attr"` Uid int64 `xml:"uid,attr"` User string `xml:"user,attr"` Members []OsmMember `xml:"member"` Tags []OsmTag `xml:"tag"` } type OsmDocument struct { Note string `xml:"note"` Bounds OsmBounds `xml:"bounds"` Nodes []OsmNode `xml:"node"` Relations []OsmRelation `xml:"relation"` Ways []OsmWay `xml:"way"` } type CombiData struct { // Number that succeedes the Company name. // E.g.: 7 Id int // Name of the operator of the route. // E.g.: "C7 - AqpMasivo" Company string // Name of the route // E.g.: "Combi B: Villa Santa Rosa -> Terminal Terrestre" Name string // Indentifiable name of the route Ref string Color string } // Type to be consumed by the client // Represents a single company type CombiLine struct { Id int `json:"id"` Name string `json:"name"` District string `json:"district"` Color string `json:"color"` } type CombiRouteContainer struct { Routes []CombiRoute } type CombiRoute struct { Name string `json:"name"` Departure []float64 `json:"departure"` Return []float64 `json:"return"` } func main() { log.Println("Begin processing") xmlData, err := os.ReadFile("aqp_map.xml") if err != nil { panic(err) } var osmDocument OsmDocument err = xml.Unmarshal(xmlData, &osmDocument) if err != nil { panic(err) } log.Println("XML unmarshal finished") // Get the relation with id 17642638, // that relation hosts the SIT data var sitId int64 = 17642638 var sitRelation *OsmRelation for _, relation := range osmDocument.Relations { if relation.Id == sitId { sitRelation = &relation } } if sitRelation == nil { panic("SIT relation with id 17642638 not found!") } // (naively) get all the submembers sitMembers := make([]OsmRelation, 0) for _, member := range sitRelation.Members { // search the member // TODO: determine if this is a performance bottleneck, // and requires optimization (insertion sort & binary search) for _, relation := range osmDocument.Relations { if relation.Id == member.Ref { sitMembers = append(sitMembers, relation) } } } // transform sitMembers into CombiData combis := make([]CombiData, 0) for _, member := range sitMembers { combis = append(combis, parseCombiData(member)) } // Create a map from string to CombiLine combiLineMap := make(map[string]*CombiLine) // Create a map for CombiRoute, grouped by CombiLine.Id combiRoutesMap := make(map[int]*CombiRouteContainer) // Populate the map for _, combi := range combis { combiLine := combiLineMap[combi.Company] if combiLine == nil { // create the company in the map combiLine := CombiLine{ Id: combi.Id, Name: combi.Company, District: "", Color: combi.Color, } combiLineMap[combi.Company] = &combiLine // create the route container combiRoutesMap[combi.Id] = &CombiRouteContainer{ Routes: make([]CombiRoute, 0), } } } // Convert each CombiData into a CombiRoute and store it for _, combi := range combis { combiRoute := CombiRoute{ Name: combi.Ref, Departure: make([]float64, 0), Return: make([]float64, 0), } combiLineSlice := combiRoutesMap[combi.Id] combiLineSlice.Routes = append(combiLineSlice.Routes, combiRoute) } writeOutput(combiLineMap, combiRoutesMap) } func writeOutput(lines map[string]*CombiLine, routes map[int]*CombiRouteContainer) { // Create output folder os.MkdirAll("output", os.ModePerm) // // Write the lines JSON file // // convert the map into an array combiLineSlice := make([]*CombiLine, 0) for _, combiLine := range lines { combiLineSlice = append(combiLineSlice, combiLine) } // print JSON jsonBytes, err := json.Marshal(combiLineSlice) if err != nil { panic(err) } os.WriteFile("output/lines.json", jsonBytes, 0644) // // Write each JSON file for the routes // for lineId, routeContainer := range routes { outFilename := fmt.Sprintf("output/routes_%d.json", lineId) jsonBytes, err := json.Marshal(routeContainer.Routes) if err != nil { panic(nil) } // write err = os.WriteFile(outFilename, jsonBytes, 0644) if err != nil { panic(err) } } log.Print("JSON files written to output/") } func parseCombiData(member OsmRelation) CombiData { var operatorTag *OsmTag var nameTag *OsmTag var refTag *OsmTag var colorTag *OsmTag for _, tag := range member.Tags { if tag.K == "operator" { operatorTag = &tag continue } if tag.K == "name" { nameTag = &tag continue } if tag.K == "ref" { refTag = &tag continue } if tag.K == "colour" { colorTag = &tag continue } } if operatorTag == nil { log.Fatalf("Found a SIT member without an operator tag, with id %d\n", member.Id) } if nameTag == nil { log.Fatalf("Found a SIT member without a name tag, with id %d\n", member.Id) } if refTag == nil { log.Fatalf("Found a SIT member without a ref tag, with id %d\n", member.Id) } if colorTag == nil { log.Fatalf("Found a SIT member without a colour tag, with id %d\n", member.Id) } return CombiData{ Id: parseLineId(operatorTag.V), Company: operatorTag.V, Name: nameTag.V, Ref: refTag.V, Color: colorTag.V, } } // Extracts the id from a line name. // E.g.: "C11 - Cotum" -> 11 func parseLineId(lineName string) int { regex := regexp.MustCompile("C(\\d+) - .+") groups := regex.FindStringSubmatch(lineName) if groups == nil { panic(fmt.Sprintf("Found an invalid line name (doesnt match format): %s", lineName)) } number, err := strconv.Atoi(groups[1]) if err != nil { panic(err) } return number }