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  }