Repository URL to install this package:
Version:
1.10.1 ▾
|
"""fio-collect"""
from functools import partial
import json
import logging
import click
import cligj
from fiona.fio import helpers, options, with_context_env
from fiona.model import Geometry, ObjectEncoder
from fiona.transform import transform_geom
@click.command(short_help="Collect a sequence of features.")
@cligj.precision_opt
@cligj.indent_opt
@cligj.compact_opt
@click.option(
"--record-buffered/--no-record-buffered",
default=False,
help="Economical buffering of writes at record, not collection "
"(default), level.",
)
@click.option(
"--ignore-errors/--no-ignore-errors",
default=False,
help="log errors but do not stop serialization.",
)
@options.src_crs_opt
@click.option(
"--with-ld-context/--without-ld-context",
default=False,
help="add a JSON-LD context to JSON output.",
)
@click.option(
"--add-ld-context-item",
multiple=True,
help="map a term to a URI and add it to the output's JSON LD " "context.",
)
@click.option(
"--parse/--no-parse",
default=True,
help="load and dump the geojson feature (default is True)",
)
@click.pass_context
@with_context_env
def collect(
ctx,
precision,
indent,
compact,
record_buffered,
ignore_errors,
src_crs,
with_ld_context,
add_ld_context_item,
parse,
):
"""Make a GeoJSON feature collection from a sequence of GeoJSON
features and print it."""
logger = logging.getLogger(__name__)
stdin = click.get_text_stream("stdin")
sink = click.get_text_stream("stdout")
dump_kwds = {"sort_keys": True}
if indent:
dump_kwds["indent"] = indent
if compact:
dump_kwds["separators"] = (",", ":")
item_sep = compact and "," or ", "
if src_crs:
if not parse:
raise click.UsageError("Can't specify --src-crs with --no-parse")
transformer = partial(
transform_geom,
src_crs,
"EPSG:4326",
antimeridian_cutting=True,
precision=precision,
)
else:
def transformer(x):
return x
first_line = next(stdin)
# If parsing geojson
if parse:
# If input is RS-delimited JSON sequence.
if first_line.startswith("\x1e"):
def feature_text_gen():
buffer = first_line.strip("\x1e")
for line in stdin:
if line.startswith("\x1e"):
if buffer:
feat = json.loads(buffer)
feat["geometry"] = transformer(
Geometry.from_dict(**feat["geometry"])
)
yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds)
buffer = line.strip("\x1e")
else:
buffer += line
else:
feat = json.loads(buffer)
feat["geometry"] = transformer(
Geometry.from_dict(**feat["geometry"])
)
yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds)
else:
def feature_text_gen():
feat = json.loads(first_line)
feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"]))
yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds)
for line in stdin:
feat = json.loads(line)
feat["geometry"] = transformer(
Geometry.from_dict(**feat["geometry"])
)
yield json.dumps(feat, cls=ObjectEncoder, **dump_kwds)
# If *not* parsing geojson
else:
# If input is RS-delimited JSON sequence.
if first_line.startswith("\x1e"):
def feature_text_gen():
buffer = first_line.strip("\x1e")
for line in stdin:
if line.startswith("\x1e"):
if buffer:
yield buffer
buffer = line.strip("\x1e")
else:
buffer += line
else:
yield buffer
else:
def feature_text_gen():
yield first_line
yield from stdin
source = feature_text_gen()
if record_buffered:
# Buffer GeoJSON data at the feature level for smaller
# memory footprint.
indented = bool(indent)
rec_indent = "\n" + " " * (2 * (indent or 0))
collection = {"type": "FeatureCollection", "features": []}
if with_ld_context:
collection["@context"] = helpers.make_ld_context(add_ld_context_item)
head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
sink.write(head)
sink.write("[")
# Try the first record.
try:
i, first = 0, next(source)
if with_ld_context:
first = helpers.id_record(first)
if indented:
sink.write(rec_indent)
sink.write(first.replace("\n", rec_indent))
except StopIteration:
pass
except Exception as exc:
# Ignoring errors is *not* the default.
if ignore_errors:
logger.error(
"failed to serialize file record %d (%s), " "continuing", i, exc
)
else:
# Log error and close up the GeoJSON, leaving it
# more or less valid no matter what happens above.
logger.critical(
"failed to serialize file record %d (%s), " "quitting", i, exc
)
sink.write("]")
sink.write(tail)
if indented:
sink.write("\n")
raise
# Because trailing commas aren't valid in JSON arrays
# we'll write the item separator before each of the
# remaining features.
for i, rec in enumerate(source, 1):
try:
if with_ld_context:
rec = helpers.id_record(rec)
if indented:
sink.write(rec_indent)
sink.write(item_sep)
sink.write(rec.replace("\n", rec_indent))
except Exception as exc:
if ignore_errors:
logger.error(
"failed to serialize file record %d (%s), " "continuing",
i,
exc,
)
else:
logger.critical(
"failed to serialize file record %d (%s), " "quitting",
i,
exc,
)
sink.write("]")
sink.write(tail)
if indented:
sink.write("\n")
raise
# Close up the GeoJSON after writing all features.
sink.write("]")
sink.write(tail)
if indented:
sink.write("\n")
else:
# Buffer GeoJSON data at the collection level. The default.
collection = {"type": "FeatureCollection", "features": []}
if with_ld_context:
collection["@context"] = helpers.make_ld_context(add_ld_context_item)
head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
sink.write(head)
sink.write("[")
sink.write(",".join(source))
sink.write("]")
sink.write(tail)
sink.write("\n")