2024-09-10 15:48:19 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-09-10 17:00:28 +00:00
|
|
|
"encoding/json"
|
2024-09-10 15:48:19 +00:00
|
|
|
"encoding/xml"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
2024-09-10 17:00:28 +00:00
|
|
|
"strconv"
|
2024-09-10 15:48:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2024-09-10 15:48:19 +00:00
|
|
|
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"`
|
2024-09-10 17:00:28 +00:00
|
|
|
Ways []OsmWay `xml:"way"`
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CombiData struct {
|
2024-09-10 17:00:28 +00:00
|
|
|
// Number that succeedes the Company name.
|
|
|
|
// E.g.: 7
|
|
|
|
Id int
|
|
|
|
// Name of the operator of the route.
|
|
|
|
// E.g.: "C7 - AqpMasivo"
|
2024-09-10 15:48:19 +00:00
|
|
|
Company string
|
2024-09-10 17:00:28 +00:00
|
|
|
// Name of the route
|
|
|
|
// E.g.: "Combi B: Villa Santa Rosa -> Terminal Terrestre"
|
2024-09-10 15:48:19 +00:00
|
|
|
Name string
|
2024-09-10 17:00:28 +00:00
|
|
|
// Indentifiable name of the route
|
2024-09-10 22:07:23 +00:00
|
|
|
Ref string
|
|
|
|
Color string
|
|
|
|
Members *[]OsmMember
|
2024-09-10 17:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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"`
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:26:44 +00:00
|
|
|
type CombiRouteContainer struct {
|
|
|
|
Routes []CombiRoute
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
type CombiRoute struct {
|
2024-09-10 22:07:23 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Departure *[][]float64 `json:"departure"`
|
|
|
|
Return *[][]float64 `json:"return"`
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2024-09-10 17:00:28 +00:00
|
|
|
combis = append(combis, parseCombiData(member))
|
|
|
|
}
|
2024-09-10 15:48:19 +00:00
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
// Create a map from string to CombiLine
|
|
|
|
combiLineMap := make(map[string]*CombiLine)
|
2024-09-10 17:26:44 +00:00
|
|
|
// Create a map for CombiRoute, grouped by CombiLine.Id
|
|
|
|
combiRoutesMap := make(map[int]*CombiRouteContainer)
|
2024-09-10 17:00:28 +00:00
|
|
|
|
|
|
|
// 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,
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:00:28 +00:00
|
|
|
combiLineMap[combi.Company] = &combiLine
|
2024-09-10 17:26:44 +00:00
|
|
|
|
|
|
|
// create the route container
|
|
|
|
combiRoutesMap[combi.Id] = &CombiRouteContainer{
|
|
|
|
Routes: make([]CombiRoute, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert each CombiData into a CombiRoute and store it
|
2024-09-10 22:07:23 +00:00
|
|
|
for idx, combi := range combis {
|
|
|
|
if idx > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Processing %s %s", combi.Company, combi.Ref)
|
|
|
|
returnCoord := make([][]float64, 0)
|
2024-09-10 17:26:44 +00:00
|
|
|
combiRoute := CombiRoute{
|
|
|
|
Name: combi.Ref,
|
2024-09-10 22:07:23 +00:00
|
|
|
Departure: getCoordinates(&osmDocument.Ways, &osmDocument.Nodes, combi.Members),
|
|
|
|
Return: &returnCoord,
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:26:44 +00:00
|
|
|
combiLineSlice := combiRoutesMap[combi.Id]
|
|
|
|
combiLineSlice.Routes = append(combiLineSlice.Routes, combiRoute)
|
2024-09-10 17:00:28 +00:00
|
|
|
}
|
2024-09-10 15:48:19 +00:00
|
|
|
|
2024-09-10 17:26:44 +00:00
|
|
|
writeOutput(combiLineMap, combiRoutesMap)
|
|
|
|
}
|
|
|
|
|
2024-09-10 22:07:23 +00:00
|
|
|
func getCoordinates(ways *[]OsmWay, nodes *[]OsmNode, membersSlice *[]OsmMember) *[][]float64 {
|
|
|
|
coordinates := make([][]float64, 0)
|
|
|
|
|
|
|
|
// The coordinate list may be reversed
|
|
|
|
// we shold check that the end of the previous set
|
|
|
|
// matches the beginning of the next set
|
|
|
|
|
|
|
|
// stores all the osmways in order,
|
|
|
|
// without duplicates
|
|
|
|
orderedCoordinates := make([]*OsmNode, 0)
|
|
|
|
var previousNodeId int64 = -1
|
|
|
|
|
|
|
|
// get all coordinates from each
|
|
|
|
for _, member := range *membersSlice {
|
|
|
|
|
|
|
|
// get the way
|
|
|
|
way := findOsmway(member.Ref, ways)
|
|
|
|
|
|
|
|
// get all its coordinates
|
|
|
|
coords := make([]*OsmNode, 0)
|
|
|
|
for _, node := range way.Nds {
|
|
|
|
coordinate := findNode(node.Ref, nodes)
|
|
|
|
coords = append(coords, coordinate)
|
|
|
|
}
|
|
|
|
coordsLen := len(coords)
|
|
|
|
|
|
|
|
if previousNodeId == -1 {
|
|
|
|
// just add all
|
|
|
|
for _, c := range coords {
|
|
|
|
orderedCoordinates = append(orderedCoordinates, c)
|
|
|
|
}
|
|
|
|
// keep track of the last id
|
|
|
|
previousNodeId = coords[coordsLen-1].Id
|
|
|
|
} else if coords[0].Id == previousNodeId {
|
|
|
|
// check if the first coordinate is the same as the previous stored
|
|
|
|
// if so, add them except the first one,
|
|
|
|
// and keep track of the last one
|
|
|
|
for i := 1; i < coordsLen; i += 1 {
|
|
|
|
orderedCoordinates = append(orderedCoordinates, coords[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep track of the last id
|
|
|
|
previousNodeId = coords[coordsLen-1].Id
|
|
|
|
} else if coords[coordsLen-1].Id == previousNodeId {
|
|
|
|
// if not, they are reversed
|
|
|
|
// add all except the last
|
|
|
|
for i := coordsLen - 2; i >= 0; i -= 1 {
|
|
|
|
orderedCoordinates = append(orderedCoordinates, coords[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep track of the last id
|
|
|
|
previousNodeId = coords[0].Id
|
|
|
|
} else {
|
|
|
|
log.Fatalf("Found a way whose ends didn't connect to the previous way. Expected to find %d in way with id %d", previousNodeId, way.Id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now compile all the coordinates
|
|
|
|
for _, coordinate := range orderedCoordinates {
|
|
|
|
coordsSlice := make([]float64, 2)
|
|
|
|
coordsSlice[0] = coordinate.Lat
|
|
|
|
coordsSlice[1] = coordinate.Lon
|
|
|
|
coordinates = append(coordinates, coordsSlice)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &coordinates
|
|
|
|
}
|
|
|
|
|
|
|
|
func findNode(id int64, nodes *[]OsmNode) *OsmNode {
|
|
|
|
var coordinateNode *OsmNode
|
|
|
|
for _, newNode := range *nodes {
|
|
|
|
if newNode.Id == id {
|
|
|
|
coordinateNode = &newNode
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if coordinateNode == nil {
|
|
|
|
log.Fatalf("Node with id %s not found", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return coordinateNode
|
|
|
|
}
|
|
|
|
|
|
|
|
func findOsmway(id int64, ways *[]OsmWay) *OsmWay {
|
|
|
|
var way *OsmWay
|
|
|
|
for _, currentWay := range *ways {
|
|
|
|
if currentWay.Id == id {
|
|
|
|
way = ¤tWay
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if way == nil {
|
|
|
|
log.Fatalf("Way with id %d not found", id)
|
|
|
|
}
|
|
|
|
return way
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:26:44 +00:00
|
|
|
func writeOutput(lines map[string]*CombiLine, routes map[int]*CombiRouteContainer) {
|
|
|
|
// Create output folder
|
|
|
|
os.MkdirAll("output", os.ModePerm)
|
|
|
|
|
|
|
|
//
|
|
|
|
// Write the lines JSON file
|
|
|
|
//
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
// convert the map into an array
|
|
|
|
combiLineSlice := make([]*CombiLine, 0)
|
2024-09-10 17:26:44 +00:00
|
|
|
for _, combiLine := range lines {
|
2024-09-10 17:00:28 +00:00
|
|
|
combiLineSlice = append(combiLineSlice, combiLine)
|
|
|
|
}
|
|
|
|
|
|
|
|
// print JSON
|
2024-09-10 17:26:44 +00:00
|
|
|
jsonBytes, err := json.Marshal(combiLineSlice)
|
2024-09-10 17:00:28 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-09-10 17:26:44 +00:00
|
|
|
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/")
|
2024-09-10 17:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:00:28 +00:00
|
|
|
if tag.K == "name" {
|
|
|
|
nameTag = &tag
|
|
|
|
continue
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:00:28 +00:00
|
|
|
if tag.K == "ref" {
|
|
|
|
refTag = &tag
|
|
|
|
continue
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:00:28 +00:00
|
|
|
if tag.K == "colour" {
|
|
|
|
colorTag = &tag
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 15:48:19 +00:00
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
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)
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
return CombiData{
|
|
|
|
Id: parseLineId(operatorTag.V),
|
|
|
|
Company: operatorTag.V,
|
|
|
|
Name: nameTag.V,
|
|
|
|
Ref: refTag.V,
|
|
|
|
Color: colorTag.V,
|
2024-09-10 22:07:23 +00:00
|
|
|
Members: &member.Members,
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
// 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))
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
2024-09-10 17:00:28 +00:00
|
|
|
|
|
|
|
number, err := strconv.Atoi(groups[1])
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|
|
|
|
|
2024-09-10 17:00:28 +00:00
|
|
|
return number
|
2024-09-10 15:48:19 +00:00
|
|
|
}
|