del env py
This commit is contained in:
@@ -1,154 +0,0 @@
|
||||
"""cligj
|
||||
|
||||
A package of arguments, options, and parsers for the Python GeoJSON
|
||||
ecosystem.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from warnings import warn
|
||||
|
||||
import click
|
||||
|
||||
from .features import normalize_feature_inputs
|
||||
|
||||
__version__ = "0.7.2"
|
||||
|
||||
if sys.version_info < (3, 6):
|
||||
warn("cligj 1.0.0 will require Python >= 3.6", FutureWarning)
|
||||
|
||||
|
||||
# Multiple input files.
|
||||
files_in_arg = click.argument(
|
||||
'files',
|
||||
nargs=-1,
|
||||
type=click.Path(resolve_path=True),
|
||||
required=True,
|
||||
metavar="INPUTS...")
|
||||
|
||||
|
||||
# Multiple files, last of which is an output file.
|
||||
files_inout_arg = click.argument(
|
||||
'files',
|
||||
nargs=-1,
|
||||
type=click.Path(resolve_path=True),
|
||||
required=True,
|
||||
metavar="INPUTS... OUTPUT")
|
||||
|
||||
|
||||
# Features from files, command line args, or stdin.
|
||||
# Returns the input data as an iterable of GeoJSON Feature-like
|
||||
# dictionaries.
|
||||
features_in_arg = click.argument(
|
||||
'features',
|
||||
nargs=-1,
|
||||
callback=normalize_feature_inputs,
|
||||
metavar="FEATURES...")
|
||||
|
||||
|
||||
# Options.
|
||||
verbose_opt = click.option(
|
||||
'--verbose', '-v',
|
||||
count=True,
|
||||
help="Increase verbosity.")
|
||||
|
||||
quiet_opt = click.option(
|
||||
'--quiet', '-q',
|
||||
count=True,
|
||||
help="Decrease verbosity.")
|
||||
|
||||
# Format driver option.
|
||||
format_opt = click.option(
|
||||
'-f', '--format', '--driver', 'driver',
|
||||
default='GTiff',
|
||||
help="Output format driver")
|
||||
|
||||
# JSON formatting options.
|
||||
indent_opt = click.option(
|
||||
'--indent',
|
||||
type=int,
|
||||
default=None,
|
||||
help="Indentation level for JSON output")
|
||||
|
||||
compact_opt = click.option(
|
||||
'--compact/--not-compact',
|
||||
default=False,
|
||||
help="Use compact separators (',', ':').")
|
||||
|
||||
# Coordinate precision option.
|
||||
precision_opt = click.option(
|
||||
'--precision',
|
||||
type=int,
|
||||
default=-1,
|
||||
help="Decimal precision of coordinates.")
|
||||
|
||||
# Geographic (default), projected, or Mercator switch.
|
||||
projection_geographic_opt = click.option(
|
||||
'--geographic',
|
||||
'projection',
|
||||
flag_value='geographic',
|
||||
default=True,
|
||||
help="Output in geographic coordinates (the default).")
|
||||
|
||||
projection_projected_opt = click.option(
|
||||
'--projected',
|
||||
'projection',
|
||||
flag_value='projected',
|
||||
help="Output in dataset's own, projected coordinates.")
|
||||
|
||||
projection_mercator_opt = click.option(
|
||||
'--mercator',
|
||||
'projection',
|
||||
flag_value='mercator',
|
||||
help="Output in Web Mercator coordinates.")
|
||||
|
||||
# Feature collection or feature sequence switch.
|
||||
sequence_opt = click.option(
|
||||
'--sequence/--no-sequence',
|
||||
default=False,
|
||||
help="Write a LF-delimited sequence of texts containing individual "
|
||||
"objects or write a single JSON text containing a feature "
|
||||
"collection object (the default).",
|
||||
callback=lambda ctx, param, value: warn(
|
||||
"Sequences of Features, not FeatureCollections, will be the default in version 1.0.0",
|
||||
FutureWarning,
|
||||
)
|
||||
or value,
|
||||
)
|
||||
|
||||
use_rs_opt = click.option(
|
||||
'--rs/--no-rs',
|
||||
'use_rs',
|
||||
default=False,
|
||||
help="Use RS (0x1E) as a prefix for individual texts in a sequence "
|
||||
"as per http://tools.ietf.org/html/draft-ietf-json-text-sequence-13 "
|
||||
"(default is False).")
|
||||
|
||||
|
||||
def geojson_type_collection_opt(default=False):
|
||||
"""GeoJSON FeatureCollection output mode"""
|
||||
return click.option(
|
||||
'--collection',
|
||||
'geojson_type',
|
||||
flag_value='collection',
|
||||
default=default,
|
||||
help="Output as GeoJSON feature collection(s).")
|
||||
|
||||
|
||||
def geojson_type_feature_opt(default=False):
|
||||
"""GeoJSON Feature or Feature sequence output mode"""
|
||||
return click.option(
|
||||
'--feature',
|
||||
'geojson_type',
|
||||
flag_value='feature',
|
||||
default=default,
|
||||
help="Output as GeoJSON feature(s).")
|
||||
|
||||
|
||||
def geojson_type_bbox_opt(default=False):
|
||||
"""GeoJSON bbox output mode"""
|
||||
return click.option(
|
||||
'--bbox',
|
||||
'geojson_type',
|
||||
flag_value='bbox',
|
||||
default=default,
|
||||
help="Output as GeoJSON bounding box array(s).")
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,214 +0,0 @@
|
||||
"""Feature parsing and normalization"""
|
||||
|
||||
from itertools import chain
|
||||
import json
|
||||
import re
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def normalize_feature_inputs(ctx, param, value):
|
||||
"""Click callback that normalizes feature input values.
|
||||
|
||||
Returns a generator over features from the input value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx: a Click context
|
||||
param: the name of the argument or option
|
||||
value: object
|
||||
The value argument may be one of the following:
|
||||
|
||||
1. A list of paths to files containing GeoJSON feature
|
||||
collections or feature sequences.
|
||||
2. A list of string-encoded coordinate pairs of the form
|
||||
"[lng, lat]", or "lng, lat", or "lng lat".
|
||||
|
||||
If no value is provided, features will be read from stdin.
|
||||
|
||||
Yields
|
||||
------
|
||||
Mapping
|
||||
A GeoJSON Feature represented by a Python mapping
|
||||
|
||||
"""
|
||||
for feature_like in value or ('-',):
|
||||
try:
|
||||
with click.open_file(feature_like, encoding="utf-8") as src:
|
||||
for feature in iter_features(iter(src)):
|
||||
yield feature
|
||||
except IOError:
|
||||
coords = list(coords_from_query(feature_like))
|
||||
yield {
|
||||
'type': 'Feature',
|
||||
'properties': {},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': coords}}
|
||||
|
||||
|
||||
def iter_features(geojsonfile, func=None):
|
||||
"""Extract GeoJSON features from a text file object.
|
||||
|
||||
Given a file-like object containing a single GeoJSON feature
|
||||
collection text or a sequence of GeoJSON features, iter_features()
|
||||
iterates over lines of the file and yields GeoJSON features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geojsonfile: a file-like object
|
||||
The geojsonfile implements the iterator protocol and yields
|
||||
lines of JSON text.
|
||||
func: function, optional
|
||||
A function that will be applied to each extracted feature. It
|
||||
takes a feature object and may return a replacement feature or
|
||||
None -- in which case iter_features does not yield.
|
||||
|
||||
Yields
|
||||
------
|
||||
Mapping
|
||||
A GeoJSON Feature represented by a Python mapping
|
||||
|
||||
"""
|
||||
func = func or (lambda x: x)
|
||||
first_line = next(geojsonfile)
|
||||
|
||||
# Does the geojsonfile contain RS-delimited JSON sequences?
|
||||
if first_line.startswith(u'\x1e'):
|
||||
text_buffer = first_line.strip(u'\x1e')
|
||||
for line in geojsonfile:
|
||||
if line.startswith(u'\x1e'):
|
||||
if text_buffer:
|
||||
obj = json.loads(text_buffer)
|
||||
if 'coordinates' in obj:
|
||||
obj = to_feature(obj)
|
||||
newfeat = func(obj)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
text_buffer = line.strip(u'\x1e')
|
||||
else:
|
||||
text_buffer += line
|
||||
# complete our parsing with a for-else clause.
|
||||
else:
|
||||
obj = json.loads(text_buffer)
|
||||
if 'coordinates' in obj:
|
||||
obj = to_feature(obj)
|
||||
newfeat = func(obj)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
|
||||
# If not, it may contains LF-delimited GeoJSON objects or a single
|
||||
# multi-line pretty-printed GeoJSON object.
|
||||
else:
|
||||
# Try to parse LF-delimited sequences of features or feature
|
||||
# collections produced by, e.g., `jq -c ...`.
|
||||
try:
|
||||
obj = json.loads(first_line)
|
||||
if obj['type'] == 'Feature':
|
||||
newfeat = func(obj)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
for line in geojsonfile:
|
||||
newfeat = func(json.loads(line))
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
elif obj['type'] == 'FeatureCollection':
|
||||
for feat in obj['features']:
|
||||
newfeat = func(feat)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
elif 'coordinates' in obj:
|
||||
newfeat = func(to_feature(obj))
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
for line in geojsonfile:
|
||||
newfeat = func(to_feature(json.loads(line)))
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
|
||||
# Indented or pretty-printed GeoJSON features or feature
|
||||
# collections will fail out of the try clause above since
|
||||
# they'll have no complete JSON object on their first line.
|
||||
# To handle these, we slurp in the entire file and parse its
|
||||
# text.
|
||||
except ValueError:
|
||||
text = "".join(chain([first_line], geojsonfile))
|
||||
obj = json.loads(text)
|
||||
if obj['type'] == 'Feature':
|
||||
newfeat = func(obj)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
elif obj['type'] == 'FeatureCollection':
|
||||
for feat in obj['features']:
|
||||
newfeat = func(feat)
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
elif 'coordinates' in obj:
|
||||
newfeat = func(to_feature(obj))
|
||||
if newfeat:
|
||||
yield newfeat
|
||||
|
||||
|
||||
def to_feature(obj):
|
||||
"""Converts an object to a GeoJSON Feature
|
||||
|
||||
Returns feature verbatim or wraps geom in a feature with empty
|
||||
properties.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
|
||||
Returns
|
||||
-------
|
||||
Mapping
|
||||
A GeoJSON Feature represented by a Python mapping
|
||||
|
||||
"""
|
||||
if obj['type'] == 'Feature':
|
||||
return obj
|
||||
elif 'coordinates' in obj:
|
||||
return {
|
||||
'type': 'Feature',
|
||||
'properties': {},
|
||||
'geometry': obj}
|
||||
else:
|
||||
raise ValueError("Object is not a feature or geometry")
|
||||
|
||||
|
||||
def iter_query(query):
|
||||
"""Accept a filename, stream, or string.
|
||||
Returns an iterator over lines of the query."""
|
||||
try:
|
||||
itr = click.open_file(query).readlines()
|
||||
except IOError:
|
||||
itr = [query]
|
||||
return itr
|
||||
|
||||
|
||||
def coords_from_query(query):
|
||||
"""Transform a query line into a (lng, lat) pair of coordinates."""
|
||||
try:
|
||||
coords = json.loads(query)
|
||||
except ValueError:
|
||||
query = query.replace(',', ' ')
|
||||
vals = query.split()
|
||||
coords = [float(v) for v in vals]
|
||||
return tuple(coords[:2])
|
||||
|
||||
|
||||
def normalize_feature_objects(feature_objs):
|
||||
"""Takes an iterable of GeoJSON-like Feature mappings or
|
||||
an iterable of objects with a geo interface and
|
||||
normalizes it to the former."""
|
||||
for obj in feature_objs:
|
||||
if (
|
||||
hasattr(obj, "__geo_interface__")
|
||||
and "type" in obj.__geo_interface__.keys()
|
||||
and obj.__geo_interface__["type"] == "Feature"
|
||||
):
|
||||
yield obj.__geo_interface__
|
||||
elif isinstance(obj, dict) and "type" in obj and obj["type"] == "Feature":
|
||||
yield obj
|
||||
else:
|
||||
raise ValueError("Did not recognize object as GeoJSON Feature")
|
||||
Reference in New Issue
Block a user