package main import ( "bufio" "compress/gzip" "fmt" "io" "math" "os" "strconv" "time" xmlparser "github.com/tamerh/xml-stream-parser" // Dá se získat pomocí "go get -u github.com/tamerh/xml-stream-parser" ) type node struct { id int lat float64 lon float64 } func haversineDistance(point1, point2 *node) float64 { R := float64(6378137) // Poloměr Země v metrech (na rovníku) // Převedeme na radiány (vynásobením pi/180) phi1 := point1.lat * math.Pi / 180 phi2 := point2.lat * math.Pi / 180 dphi := (point2.lat - point1.lat) * math.Pi / 180 dlambda := (point2.lon - point1.lon) * math.Pi / 180 a := math.Pow(math.Sin(dphi/2), 2) + math.Cos(phi1)*math.Cos(phi2)*math.Pow(math.Sin(dlambda/2), 2) return 2 * R * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) } // Funkce na kontrolu jestli má tag elementu danou hodnotu func hasTagValue(el *xmlparser.XMLElement, name string, value string) bool { for _, tag := range el.Childs["tag"] { if tag.Attrs["k"] == name { return tag.Attrs["v"] == value } } return false } // Pomocná funkce vykonávající pravidelně jinou funkci func doEvery(d time.Duration, f func()) { for range time.Tick(d) { f() } } func die(exitcode int, format string, a ...interface{}) { fmt.Fprintf(os.Stderr, format, a...) os.Exit(1) } func main() { if len(os.Args) != 2 { die(1, "Usage: %s \n", os.Args[0]) } // Otevřeme soubor file, err := os.Open(os.Args[1]) if err != nil { die(1, "Cannot open file: %v\n", err) } // Připravíme si funkci na resetování readeru, protože budeme muset XML soubor projít vícekrát var archiveReader io.Reader var parser *xmlparser.XMLParser resetReader := func(elements ...string) { if _, err := file.Seek(0, 0); err != nil { die(1, "Cannot seek to beginning of the file") } // Pořídíme si gzip reader if archiveReader, err = gzip.NewReader(file); err != nil { die(1, "Cannot open the gzip reader: %v", err) } // Dopustíme se triku - odzipování dat spustíme v samostatném vlákně // a pošleme si výsledek skrz rouru (pipe), zrychlí nás to v případě // gzipu o zhruba 10-20%. r, w := io.Pipe() go func() { io.Copy(w, archiveReader) w.Close() }() parser = xmlparser.NewXMLParser(bufio.NewReaderSize(r, 1024*1024*256), elements...) } rails := [][]*node{} railNodes := map[int]*node{} var foundNodesCounter int start := time.Now() phaseStart := start // Funkce na vypisování statistiky, spustíme ji tentokrát na pozadí printStat := func() { megabytes := float64(parser.TotalReadSize) / 1024 / 1024 elapsed := time.Now().Sub(phaseStart) fmt.Fprintf(os.Stderr, "Elapsed: %-20s Read: %10.2fMB (%.2fMB/s)\tRails: %10d\tRail nodes: %10d / %-10d\r", elapsed, megabytes, megabytes/elapsed.Seconds(), len(rails), foundNodesCounter, len(railNodes), ) } go doEvery(500*time.Millisecond, printStat) //////////////////////////////////////////////////////////////////////// // PRVNÍ PRŮCHOD - nalezení všech kolejí /////////////////////////////// fmt.Fprintf(os.Stderr, "[Phase 1] Getting all rails\n") // 1. Pořídíme si reader od začátku souboru a budeme chtít teď jen tagy cest resetReader("way") parser.SkipElements([]string{"node", "relation"}).SkipOuterElements() for xml := range parser.Stream() { switch xml.Name { case "way": if hasTagValue(xml, "railway", "rail") { nodes := []*node{} for _, nd := range xml.Childs["nd"] { nodeID, _ := strconv.Atoi(nd.Attrs["ref"]) // Každý node si pamatujeme jenom jednou a z cesty na něj odkazujeme // (to nám poté pomůže při plnění vlastností nodů) if _, found := railNodes[nodeID]; !found { railNodes[nodeID] = &node{id: nodeID} } nodes = append(nodes, railNodes[nodeID]) } rails = append(rails, nodes) } } } printStat() fmt.Fprintf(os.Stderr, "\nFound %d rails\n", len(rails)) //////////////////////////////////////////////////////////////////////// // DRUHÝ PRŮCHOD /////////////////////////////////////////////////////// fmt.Fprintf(os.Stderr, "[Phase 2] Getting all nodes of the rails\n") // 1. Resetujeme se na začátek souboru a připravíme se na další průchod resetReader("node") parser.SkipElements([]string{"way", "relation"}).SkipOuterElements() // 2. Projdeme všechny nodes a zaznamenáme si ty, které chceme phaseStart = time.Now() for xml := range parser.Stream() { switch xml.Name { case "node": id, _ := strconv.Atoi(xml.Attrs["id"]) if nd, found := railNodes[id]; found { nd.lat, _ = strconv.ParseFloat(xml.Attrs["lat"], 64) nd.lon, _ = strconv.ParseFloat(xml.Attrs["lon"], 64) foundNodesCounter++ if foundNodesCounter == len(railNodes) { break } } } } printStat() fmt.Fprintf(os.Stderr, "\nCoordinates of %d nodes found\n", foundNodesCounter) // Spočítání vzdáleností fmt.Fprintf(os.Stderr, "[Phase 3] Computing length\n") var length float64 var lastNode *node for _, rail := range rails { lastNode = nil for _, nd := range rail { if lastNode != nil { length += haversineDistance(lastNode, nd) } lastNode = nd } } fmt.Fprintf(os.Stderr, "\nFound %d rails with total length %fkm\n", len(rails), length/1000) }