使用Golang更新KML文件中的节点

huangapple go评论75阅读模式
英文:

Updating a node within a KML file using Golang

问题

我正在使用一个KML文件来在Google Earth中绘制LineString。我从USB适配器接收GPS数据,并将坐标传递给Go通道。我试图从通道中读取数据并更新KML文件中的一个节点,以添加到LineString中(从而绘制我的移动轨迹)。

这是KML文件的结构:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Folder>
	<name>Foo</name>
	<open>1</open>
	<Document>
		<name>Bar</name>
		<Placemark>
			<Style>
				<LineStyle>
					<color>ff00ff00</color>
					<width>5</width>
				</LineStyle>
				<PolyStyle>
					<color>ff4080ff</color>
					<fill>1</fill>
					<outline>1</outline>
				</PolyStyle>
			</Style>
			<LineString>
				<tessellate>1</tessellate>
				<coordinates>
					1.23411166666667,10.12345678901234,0 
					1.23421166666667,10.12345678901234,0 
					1.23431166666667,10.12345678901234,0 
					1.23431166666667,10.32345678901234,0 
				</coordinates>
			</LineString>
		</Placemark>
	</Document>
</Folder>
</kml>

我想要追加到coordinates节点中。

我考虑了两种方法。首先解析文件,使用正则表达式查找</coordinates>并在其之前插入数据。其次,解析XML并尝试更新节点中的值。后者似乎是更明智的选择,但我在谷歌上搜索到的所有内容都是如何向XML树添加新节点,而不是追加到现有条目。

到目前为止,我尝试的方法感觉很混乱,每次从通道读取时都会低效地打开文件,并且最终无法正常工作。

type LineString struct {
	coordinates string `xml:"coordinates"`
}    

func plotLocation(c chan data.GpsPos) {
	/*
		不断从通道中读取数据
		使用位置数据绘制一个面包屑路径
	*/

	defer wg.Done()

	for currentCoords := range c {

		file, err := os.Open("/Users/me/foo.kml")
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()

		var buf bytes.Buffer
		decoder := xml.NewDecoder(file)
		encoder := xml.NewEncoder(&buf)

		for {
			token, err := decoder.Token()
			if err == io.EOF {
				break
			}
			if err != nil {
				log.Printf("error getting token: %v\n", err)
				break
			}
			switch v := token.(type) {
			case xml.StartElement:
				if v.Name.Local == "LineString" {

					var coords LineString
					if err = decoder.DecodeElement(&coords, &v); err != nil {
						log.Fatal(err)
					}
					coords.coordinates += fmt.Sprintf("%f,%f,%d\n", currentCoords.Lat, currentCoords.Long, 0)
					if err = encoder.EncodeElement(coords, v); err != nil {
						log.Fatal(err)
					}
					continue
				}
			}
			if err := encoder.EncodeToken(xml.CopyToken(token)); err != nil {
				log.Fatal(err)
			}
		}
	}
}

我是否做错了什么,还有没有更好的方法来将这些数据写入文件(大约每秒一次)?

英文:

I'm working with a KML file which I'm using to plot a LineString within Google Earth. I'm receiving GPS data from a USB adapter and feeding the coordinates to a Go channel. I'm attempting to read off the channel and update a node within the KML file to add to the LineString (thus plotting my movements).

Here is the KML structure:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;kml xmlns=&quot;http://www.opengis.net/kml/2.2&quot; xmlns:gx=&quot;http://www.google.com/kml/ext/2.2&quot; xmlns:kml=&quot;http://www.opengis.net/kml/2.2&quot; xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;&gt;
&lt;Folder&gt;
&lt;name&gt;Foo&lt;/name&gt;
&lt;open&gt;1&lt;/open&gt;
&lt;Document&gt;
&lt;name&gt;Bar&lt;/name&gt;
&lt;Placemark&gt;
&lt;Style&gt;
&lt;LineStyle&gt;
&lt;color&gt;ff00ff00&lt;/color&gt;
&lt;width&gt;5&lt;/width&gt;
&lt;/LineStyle&gt;
&lt;PolyStyle&gt;
&lt;color&gt;ff4080ff&lt;/color&gt;
&lt;fill&gt;1&lt;/fill&gt;
&lt;outline&gt;1&lt;/outline&gt;
&lt;/PolyStyle&gt;
&lt;/Style&gt;
&lt;LineString&gt;
&lt;tessellate&gt;1&lt;/tessellate&gt;
&lt;coordinates&gt;
1.23411166666667,10.12345678901234,0 
1.23421166666667,10.12345678901234,0 
1.23431166666667,10.12345678901234,0 
1.23431166666667,10.32345678901234,0 
&lt;/coordinates&gt;
&lt;/LineString&gt;
&lt;/Placemark&gt;
&lt;/Document&gt;
&lt;/Folder&gt;
&lt;/kml&gt;

I'm looking to append to the coordinates node.

I was thinking one of two approaches. Firstly parse the file and using Regex look for &lt;/coordinates&gt; and insert the data before it. Secondly, parse the XML and look to update the value in the node. The latter seems the more sensible option, but everything I've Googled shows me how to add a new node to an XML tree, as opposed to append to an existing entry.

What I've attempted so far feels like a real mess, inefficiently opens the file on every read from the channel, and ultimately doesn't work.

type LineString struct {
coordinates string `xml:&quot;coordinates&quot;`
}    
func plotLocation(c chan data.GpsPos) {
/*
continuously read from the channel
use the location data to plot a breadcrumb trail
*/
defer wg.Done()
for currentCoords := range c {
file, err := os.Open(&quot;/Users/me/foo.kml&quot;)
if err != nil {
log.Fatal(err)
}
defer file.Close()
var buf bytes.Buffer
decoder := xml.NewDecoder(file)
encoder := xml.NewEncoder(&amp;buf)
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
log.Printf(&quot;error getting token: %v\n&quot;, err)
break
}
switch v := token.(type) {
case xml.StartElement:
if v.Name.Local == &quot;LineString&quot; {
var coords LineString
if err = decoder.DecodeElement(&amp;coords, &amp;v); err != nil {
log.Fatal(err)
}
coords.coordinates += fmt.Sprintf(&quot;%f,%f,%d\n&quot;, currentCoords.Lat, currentCoords.Long, 0)
if err = encoder.EncodeElement(coords, v); err != nil {
log.Fatal(err)
}
continue
}
}
if err := encoder.EncodeToken(xml.CopyToken(token)); err != nil {
log.Fatal(err)
}
}
}
}

Am I doing something obviously wrong and also is there a better approach to writing this data to the file (which will occur once every second or so)?

答案1

得分: 2

文件在应用程序外部发生变化吗?如果没有,那么你可以在循环之前解析文件一次,维护一个坐标列表,并在每次更改时将其写出,以便外部应用程序可以看到中间结果。如果你计划进行更多的转换,或者想在开始时从头生成整个文件,这也会很有用。

首先,你需要一个带有适当标签的结构体(参见xml.Unmarshal)。我通常会使用在线生成器来生成这类代码:

// type definition adapted from https://www.onlinetool.io/xmltogo/

type KML struct {
	XMLName xml.Name `xml:"kml"`
	Text    string   `xml:",chardata"`
	XMLNS   string   `xml:"xmlns,attr"`
	GX      string   `xml:"gx,attr"`
	KML     string   `xml:"kml,attr"`
	Atom    string   `xml:"atom,attr"`
	Folder  struct {
		Text     string `xml:",chardata"`
		Name     string `xml:"name"`
		Open     string `xml:"open"`
		Document struct {
			Text      string `xml:",chardata"`
			Name      string `xml:"name"`
			Placemark struct {
				Text  string `xml:",chardata"`
				Style struct {
					Text      string `xml:",chardata"`
					LineStyle struct {
						Text  string `xml:",chardata"`
						Color string `xml:"color"`
						Width string `xml:"width"`
					} `xml:"LineStyle"`
					PolyStyle struct {
						Text    string `xml:",chardata"`
						Color   string `xml:"color"`
						Fill    string `xml:"fill"`
						Outline string `xml:"outline"`
					} `xml:"PolyStyle"`
				} `xml:"Style"`
				LineString struct {
					Text        string `xml:",chardata"`
					Tessellate  string `xml:"tessellate"`
					Coordinates string `xml:"coordinates"`
				} `xml:"LineString"`
			} `xml:"Placemark"`
		} `xml:"Document"`
	} `xml:"Folder"`
} 

我会为此编写一些辅助函数:

func readKML(filename string) (*KML, error) {
  f, err := os.Open(filename)
  if err != nil {
    return nil, fmt.Errorf("打开 KML 文件时出错:%w", err) // 包含文件名
  }
  defer f.Close() // 读取,忽略错误是可以接受的
  var kml KML
  if err := xml.NewDecoder(f).Decode(&kml); err != nil {
    return nil, fmt.Errorf("将 %q 的 XML 解码为 KML 时出错:%w", filename, err)
  }
  return &kml, nil
}

func writeKML(filename string, kml *KML) error {
  f, err := os.Create(filename)
  if err != nil {
    return fmt.Errorf("创建 KML 文件时出错:%w", err) // 包含文件名
  }
  defer f.Close() // 对 *os.File 来说,双重关闭是可以的
  enc := xml.NewEncoder(f)
  enc.Indent("", "    ")
  if err := enc.Encode(kml); err != nil {
    return fmt.Errorf("将 KML 编码为 %q 时出错:%w", filename, err)
  }
  return nil
}

然后,你的循环可以像这样:

kml, err := readKML(filename)
if err != nil {
  return err // 包含上下文
}

coordinates := strings.Fields(kml.Folder.Document.Placemark.LineString.Coordinates)

for coord := range incoming {
  line := fmt.Sprintf("%f,%f,%d\n", coord.Lat, coord.Long, 0)
  coordinates = append(coordinates, coord)
  
  kml.Folder.Document.Placemark.LineString.Coordinates = strings.Join(coordinates, "\n")
  if err := writeKML(filename, kml); err != nil {
    log.Printf("警告:无法更新 %q:%s", filename, err)
  }
}

当查看你的代码时,我怀疑问题在于你延迟了文件关闭操作,这将在函数返回时执行,而不是在循环继续时执行。你可能也可以使这种方法起作用,为此我建议将逻辑分解为函数,以便每个部分都可以独立测试,这也可能意味着你的延迟操作现在在函数内部具有正确的作用域。

英文:

Does the file change outside of your application? If not, then you could parse the file once before the loop, maintain a list of coordinates, and write it out each time it changes so external applications can see the intermediate results. This will also be useful if you plan to do any more transformations, or if you want to generate the whole file from scratch at the beginning.


First, you will want a struct with the appropriate tags (see xml.Unmarshal). I usually start with an online generator for these sorts of things:

// type definition adapted from https://www.onlinetool.io/xmltogo/

type KML struct {
	XMLName xml.Name `xml:&quot;kml&quot;`
	Text    string   `xml:&quot;,chardata&quot;`
	XMLNS   string   `xml:&quot;xmlns,attr&quot;`
	GX      string   `xml:&quot;gx,attr&quot;`
	KML     string   `xml:&quot;kml,attr&quot;`
	Atom    string   `xml:&quot;atom,attr&quot;`
	Folder  struct {
		Text     string `xml:&quot;,chardata&quot;`
		Name     string `xml:&quot;name&quot;`
		Open     string `xml:&quot;open&quot;`
		Document struct {
			Text      string `xml:&quot;,chardata&quot;`
			Name      string `xml:&quot;name&quot;`
			Placemark struct {
				Text  string `xml:&quot;,chardata&quot;`
				Style struct {
					Text      string `xml:&quot;,chardata&quot;`
					LineStyle struct {
						Text  string `xml:&quot;,chardata&quot;`
						Color string `xml:&quot;color&quot;`
						Width string `xml:&quot;width&quot;`
					} `xml:&quot;LineStyle&quot;`
					PolyStyle struct {
						Text    string `xml:&quot;,chardata&quot;`
						Color   string `xml:&quot;color&quot;`
						Fill    string `xml:&quot;fill&quot;`
						Outline string `xml:&quot;outline&quot;`
					} `xml:&quot;PolyStyle&quot;`
				} `xml:&quot;Style&quot;`
				LineString struct {
					Text        string `xml:&quot;,chardata&quot;`
					Tessellate  string `xml:&quot;tessellate&quot;`
					Coordinates string `xml:&quot;coordinates&quot;`
				} `xml:&quot;LineString&quot;`
			} `xml:&quot;Placemark&quot;`
		} `xml:&quot;Document&quot;`
	} `xml:&quot;Folder&quot;`
} 

I would make some helpers for this:

func readKML(filename string) (*KML, error) {
  f, err := os.Open(filename)
  if err != nil {
    return nil, fmt.Errorf(&quot;opening KML file: %w&quot;, err) // contains filename
  }
  defer f.Close() // reading, ignoring error is acceptable
  var kml KML
  if err := xml.NewDecoder(f).Decode(&amp;kml); err != nil {
    return nil, fmt.Errorf(&quot;decoding XML from %q as KML: %w&quot;, filename, err)
  }
  return &amp;kml, nil
}

func writeKML(filename string, kml *KML) error {
  f, err := os.Create(filename)
  if err != nil {
    return fmt.Errorf(&quot;creating KML file: %w&quot;, err) // contains filename
  }
  defer f.Close() // double close is OK for *os.File
  enc := xml.NewEncoder(f)
  enc.Indent(&quot;&quot;, &quot;    &quot;)
  if err := enc.Encode(kml); err != nil {
    return nil, fmt.Errorf(&quot;encoding KML to %q: %w&quot;, filename, err)
  }
  return nil
}

And then your loop can look something like this:

kml, err := readKML(filename)
if err != nil {
  return err // contains context
}

coordinates := strings.Fields(kml.Folder.Document.Placemark.LineString.Coordinates)

for coord := range incoming {
  line := fmt.Sprintf(&quot;%f,%f,%d\n&quot;, coord.Lat, coord.Long, 0)
  coordinates = append(coordinates, coord)
  
  kml.Folder.Document.Placemark.LineString.Coordinates = strings.Join(coordinates, &quot;\n&quot;)
  if err := writeKML(filename, kml); err != nil {
    log.Printf(&quot;Warning: failed to update %q: %s&quot;, filename, err)
  }
}

When looking at the code you have, I suspect that the issue is that you are deferring your file close, which will execute when the function returns, not when the loop continues. You may be able to make this approach work as well, and to do so I would recommend breaking your logic into functions so that each piece can be tested independently, which also will likely mean that your defer is now scoped properly within a function.

huangapple
  • 本文由 发表于 2021年6月26日 01:41:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/68135170.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定