降低XML文档中值的精度

时间:2014-06-14 21:09:20

标签: python xml xslt awk xml-parsing

我有一个描述地理坐标的大型XML文档(KML,准确无误);下面的代码段可以让您了解它的外观。这里的问题是坐标是双精度(小数点后16位),这会在进一步处理中引起很多问题(此外,最后小数位实际上是十分之一纳米 - 我们的GPS并不精确)。

我一直在寻找将精度降低到给定值的任何方法,例如:小数点后5位给我们一个仪表精度。我尝试在Python中解析XML(使用lxml),更改值并保存新文档,但在流程文档中,格式更改很多,并以某种方式打破了进一步的处理。

因此,我正在寻找一种降低原位精度的方法,以便在原始文件中更改值。我认为AWK应该做到这一点,但遗憾的是我的尝试无济于事。

以下是my XML的示例。

<Document xmlns="http://www.opengis.net/kml/2.2">
    <Folder><name>Export_Output02</name>
        <Placemark>
            <Style><LineStyle><color>ff0000ff</color></LineStyle><PolyStyle><fill>0</fill></PolyStyle></Style>
            <ExtendedData><SchemaData schemaUrl="#Export_Output02">
                <SimpleData name="species">1312</SimpleData>
                <SimpleData name="area">7848012</SimpleData>
                <SimpleData name="irrep_area">0.00000012742</SimpleData>
                <SimpleData name="groupID">2</SimpleData>
            </SchemaData></ExtendedData>
            <MultiGeometry>
                <Polygon>
                    <outerBoundaryIs>
                        <LinearRing>
                            <coordinates>-57.843052746056827,-33.032934004012787 -57.825312079170494,-33.089724736921667 -57.888494029914156,-33.073777852969904 -57.843052746056827,-33.032934004012787</coordinates>
                        </LinearRing>
                    </outerBoundaryIs>
                </Polygon>
                <Polygon>
                    <outerBoundaryIs>
                        <LinearRing>
                            <coordinates>-57.635769389832561,-33.032934004012787 -57.618028722946228,-33.089724736921667 -57.681210673689904,-33.073777852969904 -57.635769389832561,-33.032934004012787</coordinates>
                        </LinearRing>
                    </outerBoundaryIs>
                </Polygon>
            </MultiGeometry>
        </Placemark>
    </Folder>
</Document>

[编辑]

我的Python代码:

import glob
import argparse
from pykml import parser
from pykml.helpers import set_max_decimal_places

arg_parser = argparse.ArgumentParser(description='Script for batch reduction of precision of KML files', prog='KML precision reducer')

arg_parser.add_argument('-p', '--precision', type=int, default=5, help='Desired precision')
arg_parser.add_argument('-d', '--directory', default='./', help='Path to KML files')

args = arg_parser.parse_args()

path_to_kml = glob.glob(args.directory + '*.kml')
precision = args.precision

for kml_file in path_to_kml:
    print 'Processing ' + kml_file
    with open(kml_file) as file_read:
        doc = parser.parse(file_read)

    max_decimals={'longitude': precision, 'latitude': precision,}

    for element in doc.iter("*"):
        set_max_decimal_places(element, max_decimals)

    out_filename = kml_file.replace('.kml', '_out.kml')

    with open(out_filename, 'w') as file_write:
       doc.write(file_write, pretty_print=True, with_tail=True)

2 个答案:

答案 0 :(得分:2)

您可以使用XSLT。下面的样式表使用XSLT 2.0。这也可以使用XSLT 1.0,但它没有我在这里使用的tokenize()函数:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:gis="http://www.opengis.net/kml/2.2"
    version="2.0">

    <!-- This is an identity transform template - it copies all the nodes -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- this template has precedence over the identity template for the `coordinates` nodes -->
    <xsl:template match="gis:coordinates">
        <xsl:copy> <!-- it copies the element --> 
        <xsl:variable name="coords" select="tokenize(.,' ')"/> <!-- saves coordinate pairs in variable -->
        <xsl:for-each select="$coords"> <!-- for each coordinate pair, formats the values before and after the comma -->
            <xsl:value-of select="round(number(substring-before(.,','))*100000) div 100000"/>
            <xsl:text>,</xsl:text> <!-- puts the comma back between the coords -->
            <xsl:value-of select="round(number(substring-after(.,','))*100000) div 100000"/>
            <xsl:if test="position() != last()">
                <xsl:text> </xsl:text> <!-- puts the space back if it's not the last coord -->
            </xsl:if>
        </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

我在上面添加了一些评论,说明它是如何运作的。

如果将其应用于样本文档,则会将坐标截断为五位小数。以下示例显示了转换后的coordinates元素:

<LinearRing>
    <coordinates>-57.84305,-33.03293 -57.82531,-33.08972 -57.88849,-33.07378 -57.84305,-33.03293</coordinates>
</LinearRing>

这是一个带有工作结果的 XSLT Fiddle

我在上面的XML Playground中粘贴了完整XML 并且它有效。我只是无法将你的文件保存在这里,因为文件太大,你可以尝试将其粘贴在那里。转换完整文件大约需要40秒。

我不了解Python中的XSLT 2.0支持,但您可以使用命令行工具(如Saxon)运行转换,或者调用Java或其他支持XSLT 2.0的语言的程序(或者,如果您正在为这个特定问题寻找解决方案,可以使用在线工具解决它。)

答案 1 :(得分:1)

这是可以混合XML和正则表达式并侥幸逃脱的实例之一:

import re

coords = re.compile("([-+]?[0-9]+\.[0-9]{6,}),([-+]?[0-9]+\.[0-9]{6,})")

def five_digits(match):
    return "%.5f,%.5f" % tuple(float(g) for g in match.groups())

with open("source.xml") as source, open("target.xml", "w") as target:
    source_xml = source.read()
    target_xml = re.sub(coords, five_digits, source_xml)
    target.write(target_xml)

模式coords匹配坐标对,每个坐标有六个或更多小数位,函数five_digits返回重新格式化为五个位置的坐标,re.sub调用使用那两个人要做替换。