├── .gitignore ├── LICENSE ├── README.md ├── osm.go └── osm_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Ignoring test data in project directory 27 | *.osm 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Lukas Rist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-osm 2 | 3 | OpenStreetMap XML Data parser in golang 4 | 5 | ## Install 6 | 7 | `go get github.com/glaslos/go-osm` 8 | 9 | ## Usage 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "github.com/glaslos/go-osm" 17 | ) 18 | 19 | var data = ` 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ` 29 | 30 | func main() { 31 | m, _ := osm.DecodeString(data) 32 | // 64.0912791 -21.9271369 33 | fmt.Println(m.Nodes[0].Lat, m.Nodes[0].Lng) 34 | } 35 | ``` -------------------------------------------------------------------------------- /osm.go: -------------------------------------------------------------------------------- 1 | package osm 2 | 3 | import ( 4 | "encoding/xml" 5 | "io" 6 | "os" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Osm struct 12 | type Map struct { 13 | Bounds Bounds 14 | Nodes []Node 15 | Ways []Way 16 | Relations []Relation 17 | } 18 | 19 | // Bounds struct 20 | type Bounds struct { 21 | XMLName xml.Name `xml:"bounds"` 22 | Minlat float64 `xml:"minlat,attr"` 23 | Minlon float64 `xml:"minlon,attr"` 24 | Maxlat float64 `xml:"maxlat,attr"` 25 | Maxlon float64 `xml:"maxlon,attr"` 26 | } 27 | 28 | // Location struct 29 | type Location struct { 30 | Type string 31 | Coordinates []float64 32 | } 33 | 34 | // Tag struct 35 | type Tag struct { 36 | XMLName xml.Name `xml:"tag"` 37 | Key string `xml:"k,attr"` 38 | Value string `xml:"v,attr"` 39 | } 40 | 41 | // Elem is a OSM base element 42 | type Elem struct { 43 | ID int64 `xml:"id,attr"` 44 | Loc Location 45 | Version int `xml:"version,attr"` 46 | Ts time.Time `xml:"timestamp,attr"` 47 | UID int64 `xml:"uid,attr"` 48 | User string `xml:"user,attr"` 49 | ChangeSet int64 `xml:"changeset,attr"` 50 | } 51 | 52 | // Node structure 53 | type Node struct { 54 | Elem 55 | XMLName xml.Name `xml:"node"` 56 | Lat float64 `xml:"lat,attr"` 57 | Lng float64 `xml:"lon,attr"` 58 | Tag []Tag `xml:"tag"` 59 | } 60 | 61 | // Way struct 62 | type Way struct { 63 | Elem 64 | XMLName xml.Name `xml:"way"` 65 | Tags map[string]string 66 | RTags []Tag `xml:"tag"` 67 | Nds []struct { 68 | ID int64 `xml:"ref,attr"` 69 | } `xml:"nd"` 70 | } 71 | 72 | // Member struct 73 | type Member struct { 74 | Type string `xml:"type,attr"` 75 | Ref int64 `xml:"ref,attr"` 76 | Role string `xml:"role,attr"` 77 | } 78 | 79 | // Relation struct 80 | type Relation struct { 81 | Elem 82 | Visible bool `xml:"visible,attr"` 83 | Version string `xml:"version,attr"` 84 | Members []Member `xml:"member"` 85 | Tags []Tag `xml:"tag"` 86 | } 87 | 88 | // DecodeFile an OSM file 89 | func DecodeFile(fileName string) (*Map, error) { 90 | 91 | file, err := os.Open(fileName) 92 | if err != nil { 93 | return nil, err 94 | } 95 | defer file.Close() 96 | 97 | return Decode(file) 98 | } 99 | 100 | func DecodeString(data string) (*Map, error) { 101 | return Decode(strings.NewReader(data)) 102 | } 103 | 104 | // Decode an reader 105 | func Decode(reader io.Reader) (*Map, error) { 106 | var ( 107 | o = new(Map) 108 | err error 109 | ) 110 | 111 | decoder := xml.NewDecoder(reader) 112 | for { 113 | token, _ := decoder.Token() 114 | if token == nil { 115 | break 116 | } 117 | 118 | switch typedToken := token.(type) { 119 | case xml.StartElement: 120 | switch typedToken.Name.Local { 121 | case "bounds": 122 | var b Bounds 123 | err = decoder.DecodeElement(&b, &typedToken) 124 | if err != nil { 125 | return nil, err 126 | } 127 | o.Bounds = b 128 | 129 | case "node": 130 | var n Node 131 | err = decoder.DecodeElement(&n, &typedToken) 132 | if err != nil { 133 | return nil, err 134 | } 135 | o.Nodes = append(o.Nodes, n) 136 | 137 | case "way": 138 | var w Way 139 | err = decoder.DecodeElement(&w, &typedToken) 140 | if err != nil { 141 | return nil, err 142 | } 143 | o.Ways = append(o.Ways, w) 144 | 145 | case "relation": 146 | var r Relation 147 | err = decoder.DecodeElement(&r, &typedToken) 148 | if err != nil { 149 | return nil, err 150 | } 151 | o.Relations = append(o.Relations, r) 152 | } 153 | } 154 | } 155 | return o, nil 156 | } 157 | -------------------------------------------------------------------------------- /osm_test.go: -------------------------------------------------------------------------------- 1 | package osm 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestUnmarshalFile(t *testing.T) { 11 | testFileName := "testfile.osm" 12 | err := ioutil.WriteFile(testFileName, testData, 0600) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | defer func() { 17 | os.Remove(testFileName) 18 | }() 19 | 20 | o, err := DecodeFile(testFileName) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | if len(o.Nodes) != 1 { 25 | t.Fatal("Map has only one node") 26 | } 27 | if len(o.Ways) != 1 { 28 | t.Fatal("Map has only one way") 29 | } 30 | if len(o.Relations) != 1 { 31 | t.Fatal("Map has only one relation") 32 | } 33 | } 34 | 35 | func TestUnmarshalData(t *testing.T) { 36 | o, e := Decode(bytes.NewReader(testData)) 37 | if e != nil { 38 | t.Fatal(e) 39 | } 40 | if len(o.Nodes) != 1 { 41 | t.Fatal("Map has only one node") 42 | } 43 | if len(o.Ways) != 1 { 44 | t.Fatal("Map has only one way") 45 | } 46 | if len(o.Relations) != 1 { 47 | t.Fatal("Map has only one relation") 48 | } 49 | } 50 | 51 | var testData = []byte(` 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | `) 82 | --------------------------------------------------------------------------------