英文:
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:
<?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>
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 </coordinates>
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:"coordinates"`
}
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("/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)
}
}
}
}
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:"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"`
}
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("opening KML file: %w", err) // contains filename
}
defer f.Close() // reading, ignoring error is acceptable
var kml KML
if err := xml.NewDecoder(f).Decode(&kml); err != nil {
return nil, fmt.Errorf("decoding XML from %q as 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("creating KML file: %w", err) // contains filename
}
defer f.Close() // double close is OK for *os.File
enc := xml.NewEncoder(f)
enc.Indent("", " ")
if err := enc.Encode(kml); err != nil {
return nil, fmt.Errorf("encoding KML to %q: %w", 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("%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("Warning: failed to update %q: %s", 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论