github.com/omniscale/go-osm@v0.3.1/parser/diff/parser.go (about) 1 package diff 2 3 import ( 4 "compress/gzip" 5 "context" 6 "encoding/xml" 7 "fmt" 8 "io" 9 "strconv" 10 "time" 11 12 "github.com/omniscale/go-osm" 13 ) 14 15 // Parser is a stream based parser for OSM diff files (.osc). 16 type Parser struct { 17 reader io.Reader 18 conf Config 19 err error 20 } 21 22 type Config struct { 23 // IncludeMetadata indicates whether metadata like timestamps, versions and 24 // user names should be parsed. 25 IncludeMetadata bool 26 27 // Diffs specifies the destination for parsed diff elements. 28 Diffs chan osm.Diff 29 30 // KeepOpen specifies whether the destination channel should be keept open 31 // after Parse(). By default, the Elements channel is closed after Parse(). 32 KeepOpen bool 33 } 34 35 // New creates a new parser for the provided input. Config specifies the destinations for the parsed elements. 36 func New(r io.Reader, conf Config) *Parser { 37 return &Parser{reader: r, conf: conf} 38 } 39 40 // NewGZIP returns a parser from a GZIP compressed io.Reader 41 func NewGZIP(r io.Reader, conf Config) (*Parser, error) { 42 r, err := gzip.NewReader(r) 43 if err != nil { 44 return nil, err 45 } 46 return New(r, conf), nil 47 } 48 49 // Error returns the first error that occurred during Parse calls. 50 func (p *Parser) Error() error { 51 return p.err 52 } 53 54 func (p *Parser) Parse(ctx context.Context) (err error) { 55 if p.err != nil { 56 return err 57 } 58 59 defer func() { 60 if err != nil { 61 p.err = err 62 } 63 }() 64 65 if !p.conf.KeepOpen { 66 defer func() { 67 if p.conf.Diffs != nil { 68 close(p.conf.Diffs) 69 } 70 }() 71 } 72 decoder := xml.NewDecoder(p.reader) 73 74 add := false 75 mod := false 76 del := false 77 tags := make(map[string]string) 78 newElem := false 79 80 node := &osm.Node{} 81 way := &osm.Way{} 82 rel := &osm.Relation{} 83 84 NextToken: 85 for { 86 token, err := decoder.Token() 87 if err != nil { 88 return fmt.Errorf("decoding next XML token: %w", err) 89 } 90 91 switch tok := token.(type) { 92 case xml.StartElement: 93 switch tok.Name.Local { 94 case "create": 95 add = true 96 mod = false 97 del = false 98 case "modify": 99 add = false 100 mod = true 101 del = false 102 case "delete": 103 add = false 104 mod = false 105 del = true 106 case "node": 107 for _, attr := range tok.Attr { 108 switch attr.Name.Local { 109 case "id": 110 node.ID, _ = strconv.ParseInt(attr.Value, 10, 64) 111 case "lat": 112 node.Lat, _ = strconv.ParseFloat(attr.Value, 64) 113 case "lon": 114 node.Long, _ = strconv.ParseFloat(attr.Value, 64) 115 } 116 } 117 if p.conf.IncludeMetadata { 118 setElemMetadata(tok.Attr, &node.Element) 119 } 120 case "way": 121 for _, attr := range tok.Attr { 122 if attr.Name.Local == "id" { 123 way.ID, _ = strconv.ParseInt(attr.Value, 10, 64) 124 } 125 } 126 if p.conf.IncludeMetadata { 127 setElemMetadata(tok.Attr, &way.Element) 128 } 129 case "relation": 130 for _, attr := range tok.Attr { 131 if attr.Name.Local == "id" { 132 rel.ID, _ = strconv.ParseInt(attr.Value, 10, 64) 133 } 134 } 135 if p.conf.IncludeMetadata { 136 setElemMetadata(tok.Attr, &rel.Element) 137 } 138 case "nd": 139 for _, attr := range tok.Attr { 140 if attr.Name.Local == "ref" { 141 ref, _ := strconv.ParseInt(attr.Value, 10, 64) 142 way.Refs = append(way.Refs, ref) 143 } 144 } 145 case "member": 146 member := osm.Member{} 147 for _, attr := range tok.Attr { 148 switch attr.Name.Local { 149 case "type": 150 var ok bool 151 member.Type, ok = memberTypeValues[attr.Value] 152 if !ok { 153 // ignore unknown member types 154 continue NextToken 155 } 156 case "role": 157 member.Role = attr.Value 158 case "ref": 159 var err error 160 member.ID, err = strconv.ParseInt(attr.Value, 10, 64) 161 if err != nil { 162 // ignore invalid ref 163 continue NextToken 164 } 165 } 166 } 167 rel.Members = append(rel.Members, member) 168 case "tag": 169 var k, v string 170 for _, attr := range tok.Attr { 171 if attr.Name.Local == "k" { 172 k = attr.Value 173 } else if attr.Name.Local == "v" { 174 v = attr.Value 175 } 176 } 177 tags[k] = v 178 case "osmChange": 179 // pass 180 default: 181 // unhandled XML tag, pass 182 } 183 case xml.EndElement: 184 var e osm.Diff 185 switch tok.Name.Local { 186 case "node": 187 if len(tags) > 0 { 188 node.Tags = tags 189 } 190 e.Node = node 191 node = &osm.Node{} 192 newElem = true 193 case "way": 194 if len(tags) > 0 { 195 way.Tags = tags 196 } 197 e.Way = way 198 way = &osm.Way{} 199 newElem = true 200 case "relation": 201 if len(tags) > 0 { 202 rel.Tags = tags 203 } 204 e.Rel = rel 205 rel = &osm.Relation{} 206 newElem = true 207 case "osmChange": 208 // EOF 209 return nil 210 } 211 212 if newElem { 213 e.Create = add 214 e.Delete = del 215 e.Modify = mod 216 if len(tags) > 0 { 217 tags = make(map[string]string) 218 } 219 newElem = false 220 select { 221 case <-ctx.Done(): 222 case p.conf.Diffs <- e: 223 } 224 } 225 } 226 } 227 228 return nil 229 } 230 231 func setElemMetadata(attrs []xml.Attr, elem *osm.Element) { 232 elem.Metadata = &osm.Metadata{} 233 for _, attr := range attrs { 234 switch attr.Name.Local { 235 case "version": 236 v, _ := strconv.ParseInt(attr.Value, 10, 64) 237 elem.Metadata.Version = int32(v) 238 case "uid": 239 v, _ := strconv.ParseInt(attr.Value, 10, 64) 240 elem.Metadata.UserID = int32(v) 241 case "user": 242 elem.Metadata.UserName = attr.Value 243 case "changeset": 244 v, _ := strconv.ParseInt(attr.Value, 10, 64) 245 elem.Metadata.Changeset = v 246 case "timestamp": 247 elem.Metadata.Timestamp, _ = time.Parse(time.RFC3339, attr.Value) 248 } 249 } 250 } 251 252 var memberTypeValues = map[string]osm.MemberType{ 253 "node": osm.NodeMember, 254 "way": osm.WayMember, 255 "relation": osm.RelationMember, 256 }