osm-parser-aqp-combis/main.go

298 lines
6.8 KiB
Go
Raw Normal View History

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
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
}
2024-09-10 17:00:28 +00:00
type CombiRoute struct {
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)
// 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
// 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),
2024-09-10 15:48:19 +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
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
//
2024-09-10 17:00:28 +00:00
// convert the map into an array
combiLineSlice := make([]*CombiLine, 0)
for _, combiLine := range lines {
2024-09-10 17:00:28 +00:00
combiLineSlice = append(combiLineSlice, combiLine)
}
// print JSON
jsonBytes, err := json.Marshal(combiLineSlice)
2024-09-10 17:00:28 +00:00
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/")
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 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
}