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-11 03:33:40 +00:00
"sort"
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
}
2024-09-11 03:33:40 +00:00
type CombiPair struct {
Departure * OsmRelation
Return * OsmRelation
}
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-11 03:33:40 +00:00
Ref string
Color string
Members * [ ] OsmMember
ReturnMembers * [ ] 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" )
2024-09-10 22:40:32 +00:00
// Create a map of IDs to Nodes
nodesMap := make ( map [ int64 ] * OsmNode )
for _ , node := range osmDocument . Nodes {
nodesMap [ node . Id ] = & node
}
2024-09-11 03:33:40 +00:00
// Contains a list of pairs of relations
sitMembers := getSITRelations ( & osmDocument )
2024-09-10 15:48:19 +00:00
// transform sitMembers into CombiData
combis := make ( [ ] CombiData , 0 )
2024-09-11 03:33:40 +00:00
for _ , memberPair := range sitMembers {
combis = append ( combis , parseCombiData ( memberPair ) )
2024-09-10 17:00:28 +00:00
}
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 23:06:38 +00:00
for _ , combi := range combis {
2024-09-10 22:07:23 +00:00
log . Printf ( "Processing %s %s" , combi . Company , combi . Ref )
2024-09-10 17:26:44 +00:00
combiRoute := CombiRoute {
Name : combi . Ref ,
2024-09-10 22:40:32 +00:00
Departure : getCoordinates ( & osmDocument . Ways , nodesMap , combi . Members ) ,
2024-09-11 03:33:40 +00:00
Return : getCoordinates ( & osmDocument . Ways , nodesMap , combi . ReturnMembers ) ,
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:40:32 +00:00
func getCoordinates ( ways * [ ] OsmWay , nodes map [ int64 ] * OsmNode , membersSlice * [ ] OsmMember ) * [ ] [ ] float64 {
2024-09-10 22:07:23 +00:00
coordinates := make ( [ ] [ ] float64 , 0 )
2024-09-10 23:06:38 +00:00
// filter members, retain only those with type="way"
newMembers := make ( [ ] * OsmMember , 0 )
for _ , m := range * membersSlice {
if m . Type == "way" {
newMembers = append ( newMembers , & m )
}
}
2024-09-10 22:07:23 +00:00
// 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
2024-09-10 22:40:32 +00:00
var previousWayId int64 = - 1
2024-09-10 22:07:23 +00:00
// get all coordinates from each
2024-09-10 23:06:38 +00:00
for _ , member := range newMembers {
2024-09-10 22:07:23 +00:00
// 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
2024-09-10 23:06:38 +00:00
secondWay := findOsmway ( newMembers [ 1 ] . Ref , ways )
2024-09-10 22:40:32 +00:00
coords = findFirstCoord ( way , secondWay , nodes )
2024-09-10 22:07:23 +00:00
for _ , c := range coords {
orderedCoordinates = append ( orderedCoordinates , c )
}
2024-09-10 22:40:32 +00:00
2024-09-10 22:07:23 +00:00
// keep track of the last id
previousNodeId = coords [ coordsLen - 1 ] . Id
2024-09-10 22:40:32 +00:00
previousWayId = way . Id
2024-09-10 22:07:23 +00:00
} 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
2024-09-10 22:40:32 +00:00
previousWayId = way . Id
2024-09-10 22:07:23 +00:00
} 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
2024-09-10 22:40:32 +00:00
previousWayId = way . Id
2024-09-10 22:07:23 +00:00
} else {
2024-09-10 22:40:32 +00:00
log . Fatalf ( "Found a way that didn't connect to the previous way. Expected to find node(%d) in way(%d), the previous way id is (%d)" , previousNodeId , way . Id , previousWayId )
2024-09-10 22:07:23 +00:00
}
}
// 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
}
2024-09-10 22:40:32 +00:00
func findFirstCoord ( firstWay , secondWay * OsmWay , nodes map [ int64 ] * OsmNode ) [ ] * OsmNode {
// find the common point
first := make ( [ ] * OsmNode , 0 )
for _ , node := range firstWay . Nds {
coordinate := findNode ( node . Ref , nodes )
first = append ( first , coordinate )
}
firstLast := len ( first ) - 1
second := make ( [ ] * OsmNode , 0 )
for _ , node := range secondWay . Nds {
coordinate := findNode ( node . Ref , nodes )
second = append ( second , coordinate )
}
secondLast := len ( second ) - 1
if first [ firstLast ] . Id == second [ 0 ] . Id || first [ firstLast ] . Id == second [ secondLast ] . Id {
return first
} else if first [ 0 ] . Id == second [ 0 ] . Id || first [ 0 ] . Id == second [ secondLast ] . Id {
// reverse the slice
newFirst := make ( [ ] * OsmNode , len ( first ) )
for i := 0 ; i < len ( first ) ; i += 1 {
newFirst [ i ] = first [ firstLast - i ]
2024-09-10 22:07:23 +00:00
}
2024-09-10 22:40:32 +00:00
return newFirst
} else {
log . Fatalf ( "Could not find 2 points in common between 2 ways with ids %d & %d" , firstWay . Id , secondWay . Id )
panic ( "" )
2024-09-10 22:07:23 +00:00
}
2024-09-10 22:40:32 +00:00
}
func findNode ( id int64 , nodes map [ int64 ] * OsmNode ) * OsmNode {
node := nodes [ id ]
if node == nil {
2024-09-10 22:07:23 +00:00
log . Fatalf ( "Node with id %s not found" , id )
}
2024-09-10 22:40:32 +00:00
return node
2024-09-10 22:07:23 +00:00
}
func findOsmway ( id int64 , ways * [ ] OsmWay ) * OsmWay {
var way * OsmWay
for _ , currentWay := range * ways {
if currentWay . Id == id {
way = & currentWay
}
}
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 )
}
2024-09-11 03:33:40 +00:00
// sort the map
sort . Slice ( combiLineSlice , func ( i , j int ) bool {
return combiLineSlice [ i ] . Id < combiLineSlice [ j ] . Id
} )
2024-09-10 17:00:28 +00:00
// 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 )
2024-09-11 03:33:40 +00:00
sort . Slice ( routeContainer . Routes , func ( i , j int ) bool {
return routeContainer . Routes [ i ] . Name < routeContainer . Routes [ j ] . Name
} )
2024-09-10 17:26:44 +00:00
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
}
2024-09-11 03:33:40 +00:00
func parseCombiData ( combiPair * CombiPair ) CombiData {
member := combiPair . Departure
returnMember := combiPair . Return
2024-09-10 17:00:28 +00:00
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 {
2024-09-11 03:33:40 +00:00
Id : parseLineId ( operatorTag . V ) ,
Company : operatorTag . V ,
Name : nameTag . V ,
Ref : refTag . V ,
Color : colorTag . V ,
Members : & member . Members ,
ReturnMembers : & returnMember . Members ,
}
}
// Finds all the relations that are related to the SIT
func getSITRelations ( osmDocument * OsmDocument ) [ ] * CombiPair {
// a route is identified by the pair operator,ref
// how to know which route is the departure,
// and which is the return?
// each district (route, color) is assigned to a district/area
// in arequipa,
// so to know which way is each route, we compare the first
// coordinate against a coordinate located at that district
// TODO: implement that
// map of maps
// map of `operator` to (map of `ref` to OsmRelation)
operators := make ( map [ string ] * ( map [ string ] * [ ] * OsmRelation ) )
// search, filter and store
for _ , r := range osmDocument . Relations {
if r . Tag ( "type" , "route" ) && r . Tag ( "route" , "bus" ) && r . Tag ( "network" , "SIT" ) {
operator := r . GetTag ( "operator" )
ref := r . GetTag ( "ref" )
// check if operator map exists, create if not
operatorMap := operators [ operator ]
if operatorMap == nil {
newMap := make ( map [ string ] * [ ] * OsmRelation )
operatorMap = & newMap
operators [ operator ] = & newMap
}
// check if ref exists, create if not
refs := ( * operatorMap ) [ ref ]
if refs == nil {
refsArr := make ( [ ] * OsmRelation , 0 )
refs = & refsArr
( * operatorMap ) [ ref ] = & refsArr
}
// insert
* refs = append ( * refs , & r )
}
}
// map and filter
pairs := make ( [ ] * CombiPair , 0 )
for operatorKey , operator := range operators {
for refKey , relationSlice := range * operator {
if len ( * relationSlice ) != 2 {
log . Printf ( "operator(%s) ref(%s): expected 2 ref elements, found %d" , operatorKey , refKey , len ( * relationSlice ) )
continue
}
// AYAYA!
newPair := CombiPair {
Departure : ( * relationSlice ) [ 0 ] ,
Return : ( * relationSlice ) [ 1 ] ,
}
pairs = append ( pairs , & newPair )
}
}
return pairs
}
func ( r * OsmRelation ) HasTag ( tagName string ) bool {
for _ , tag := range r . Tags {
if tag . K == tagName {
return true
}
}
return false
}
func ( r * OsmRelation ) Tag ( tagName string , tagValue string ) bool {
for _ , tag := range r . Tags {
if tag . K == tagName && tag . V == tagValue {
return true
}
}
return false
}
func ( r * OsmRelation ) GetTag ( tagName string ) string {
for _ , tag := range r . Tags {
if tag . K == tagName {
return tag . V
}
2024-09-10 15:48:19 +00:00
}
2024-09-11 03:33:40 +00:00
panic ( fmt . Sprintf ( "Tried to get, from relation(%d), tag %s, but it was not found" , r . Id , tagName ) )
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
}