使用jq处理巨大的GEOJson文件

时间:2017-04-18 10:00:37

标签: json stream geojson jq bigdata

给出GEOJson文件如下: -

{
  "type": "FeatureCollection",
  "features": [
   {
     "type": "Feature",
     "properties": {
     "FEATCODE": 15014
  },
  "geometry": {
    "type": "Polygon",
    "coordinates": [
     .....

我想最终得到以下结论: -

{
  "type": "FeatureCollection",
  "features": [
   {
     "tippecanoe : {"minzoom" : 13},
     "type": "Feature",
     "properties": {
     "FEATCODE": 15014
  },
  "geometry": {
    "type": "Polygon",
    "coordinates": [
     .....

即。我已将tippecanoe对象添加到数组功能

中的每个功能

我可以使用以下方法: -

 jq '.features[].tippecanoe.minzoom = 13' <GEOJSON FILE> > <OUTPUT FILE>

哪个适用于小文件。但处理一个414Mb的大文件似乎永远需要处理器最大化并且没有任何内容被写入OUTPUT FILE

进一步阅读jq似乎 - stream 命令行参数可能会有所帮助,但我对如何将其用于我的目的感到困惑。

我将非常感谢能够满足我的目的的示例命令行以及有关--stream正在做什么的解释。

4 个答案:

答案 0 :(得分:2)

一次性jq-only方法可能需要比可用RAM更多的RAM。如果是这种情况,那么下面将显示一个简单的all-jq方法,以及基于使用jq和awk的更经济的方法。

除了将对象流重构为单个JSON文档之外,这两种方法是相同的。使用awk可以非常经济地完成这一步骤。

在这两种情况下,假定具有所需形式对象的大型JSON输入文件名为input.json。

JQ-仅

jq -c  '.features[]' input.json |
    jq -c '.tippecanoe.minzoom = 13' |
    jq -c -s '{type: "FeatureCollection", features: .}'

jq和awk

jq -c '.features[]' input.json |
   jq -c '.tippecanoe.minzoom = 13' | awk '
     BEGIN {print "{\"type\": \"FeatureCollection\", \"features\": ["; }
     NR==1 { print; next }
           {print ","; print}
     END   {print "] }";}'

性能比较

为了进行比较,使用了.features []中包含10,000,000个对象的输入文件。它的大小约为1GB。

U + S:

jq-only:              15m 15s
jq-awk:                7m 40s
jq one-pass using map: 6m 53s

答案 1 :(得分:0)

另一种解决方案可能是:

jq '.features |= map_values(.tippecanoe.minzoom = 13)'

为了测试这个,我创建了一个样本JSON为

d = {'features': [{"type":"Feature", "properties":{"FEATCODE": 15014}} for i in range(0,N)]}

并将执行时间视为N的函数。有趣的是,虽然map_values方法似乎在N中具有线性复杂性,但.features[].tippecanoe.minzoom = 13表现出二次行为(已经为N = 50000,前一种方法在大约0.8秒内完成,而后者需要大约47秒)

或者,人们可以手动使用例如Python:

import json
import sys

data = {}
with open(sys.argv[1], 'r') as F:
    data = json.load(F)

extra_item = {"minzoom" : 13}
for feature in data['features']:
    feature["tippecanoe"] = extra_item

with open(sys.argv[2], 'w') as F:
    F.write(json.dumps(data))

答案 2 :(得分:0)

在这种情况下,map而不是map_values要快得多(*):

.features |= map(.tippecanoe.minzoom = 13)

但是,使用这种方法仍然需要足够的RAM。

P.S。如果要使用jq生成大文件以进行计时,请考虑:

def N: 1000000;

def data:
   {"features": [range(0;N) | {"type":"Feature", "properties": {"FEATCODE": 15014}}] };

(*)使用map,20s表示100MB,近似线性。

答案 3 :(得分:0)

这里,基于GitHub上@nicowilliams的工作,是一个使用jq可用的流解析器的解决方案。该解决方案在内存方面非常经济,但如果输入很大,目前速度很慢。

该解决方案包含两部分:一个用于将更新注入到使用--stream命令行选项生成的流中的函数;以及以原始形式将流转换回JSON的功能。

调用:

jq -cnr --stream -f program.jq input.json

program.jq

# inject the given object into the stream produced from "inputs" with the --stream option
def inject(object):
  [object|tostream] as $object
  | 2
  | truncate_stream(inputs)
  | if (.[0]|length == 1) and length == 1
    then $object[]
    else .
    end ;

# Input: the object to be added
# Output: text
def output:
  . as $object
  | ( "[",
      foreach fromstream( inject($object) ) as $o
        (0;
         if .==0 then 1 else 2 end;
         if .==1 then $o else ",", $o end),
      "]" ) ;

{}
| .tippecanoe.minzoom = 13
| output

生成测试数据

def data(N):
 {"features":
  [range(0;2) | {"type":"Feature", "properties": {"FEATCODE": 15014}}] };

示例输出

N = 2:

[
{"type":"Feature","properties":{"FEATCODE":15014},"tippecanoe":{"minzoom":13}}
,
{"type":"Feature","properties":{"FEATCODE":15014},"tippecanoe":{"minzoom":13}}
]