【问题标题】:Reduce precision of values in XML document降低 XML 文档中值的精度
【发布时间】:2014-08-05 03:13:50
【问题描述】:

我有一个描述地理坐标的大型 XML 文档(准确地说是 KML);下面的 sn-p 应该让您了解它的外观。这里的问题是坐标是双精度的(小数点后 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)

【问题讨论】:

  • XML 文档的“格式”应该无关紧要,只要唯一的区别是不重要的空格和空标签(单个或成对)。当您使用 lxml 时,下游会发生什么“中断”?
  • @JimGarrison 我也是这么想的。遗憾的是,我不知道到底出了什么问题。转换为最终目标的 Google Fusion Table 没有错误,尽管最终结果缺少几何图形(所以我得到了一张没有任何点的地图)。我应该补充一点,Google Fusion Tables 施加了很多限制,所以很可能是行数太大或类似的情况。
  • 您可以使用 XSLT 来转换坐标。
  • 如果您需要帮助,您应该发布 Python 代码和您获得的输出示例。
  • @helderdarocha 由于我对 XSLT 完全不熟悉,您能否举出更具体的例子,我可以效仿吗?谢谢!

标签: python xml xslt awk xml-parsing


【解决方案1】:

您可以使用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>

我在上面添加了一些 cmets 来解释它是如何工作的。

如果将其应用于示例文档,它会将坐标截断为五位小数。下面是一个显示转换后的coordinates 元素的示例:

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

这是一个 XSLT Fiddle 的工作结果。

我将您的 full XML 粘贴到上面的 XML Playground 中,并且成功了。我只是无法保存您的文件以将其链接到此处,因为文件太大,但您可以尝试将其粘贴到此处。转换完整文件需要大约 40 秒。

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

【讨论】:

  • 非常感谢!它正是我需要的。事实上,我使用 Saxon 工具来应用转换。
【解决方案2】:

这是您可以混合 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 调用使用这两个进行替换.

【讨论】:

  • 谢谢!但是,使用您的解决方案,我最终仍然会编写新的 XML,因此会“打破”神奇的 Google Fusion Tables 限制。这就是为什么我一直在寻找一些就地的东西,这样我就不需要编写新文件了。此外,您的方法不会改变任何数字,而不仅仅是坐标?
  • @LukaszTracewski 既然您建议使用 AWK,我认为您可以修改实际文件。没错,我的答案的先前版本将它找到的所有 6 位以上的小数四舍五入;我现在已将其修改为仅圆形坐标对。
  • 我猜 'too_precise' 应该是 'coords',对吧?很抱歉这个愚蠢的问题,但我已经有 10 年没有看到正则表达式了:)。当我替换它时,我得到 TypeError: 'builtin_function_or_method' object is not iterable on return "%.5f,%.5f" % tuple(float(g) for g in match.groups)
  • @LukaszTracewski 啊,编辑太仓促了 - 现已修复。是的,too_precise 应该是coords(我忘了实际调用match.groups)。
  • 现在它就像一个魅力!您和helderdarocha 的答案都将适用于我正在准备将 shapefile 转换为 Google 融合表的小工具包。 GIS 社区将不胜感激 - 谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 2019-05-21
  • 2018-12-28
  • 2022-11-02
  • 2015-05-15
相关资源
最近更新 更多