Repository: mapbox/cligj

Issue:
Geometry inputs
some programs output geojson geometries (not features). See mapshaper as mentioned in https://github.com/perrygeo/python-rasterstats/issues/81 for one such use case

**Question** Do we support a stream of geojson geometries which would be exposed as an iterable of features? Or should the user rely on an external tool (maybe even a job for `jq`?)


Comments:

Patch:
diff --git a/README.rst b/README.rst
index c532ee3..160c057 100755
--- a/README.rst
+++ b/README.rst
@@ -101,7 +101,7 @@ a delimiter, use the ``--rs`` option
         if sequence:
             for feature in process_features(features):
                 if use_rs:
-                    click.echo(b'\x1e', nl=False)
+                    click.echo(u'\x1e', nl=False)
                 click.echo(json.dumps(feature))
         else:
             click.echo(json.dumps(
diff --git a/cligj/features.py b/cligj/features.py
index efca280..3b23c16 100644
--- a/cligj/features.py
+++ b/cligj/features.py
@@ -65,7 +65,10 @@ def iter_features(geojsonfile, func=None):
         for line in geojsonfile:
             if line.startswith(u'\x1e'):
                 if text_buffer:
-                    newfeat = func(json.loads(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')
@@ -73,7 +76,10 @@ def iter_features(geojsonfile, func=None):
                 text_buffer += line
         # complete our parsing with a for-else clause.
         else:
-            newfeat = func(json.loads(text_buffer))
+            obj = json.loads(text_buffer)
+            if 'coordinates' in obj:
+                obj = to_feature(obj)
+            newfeat = func(obj)
             if newfeat:
                 yield newfeat
 
@@ -97,9 +103,17 @@ def iter_features(geojsonfile, func=None):
                     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 
+        # 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.
@@ -115,6 +129,26 @@ def iter_features(geojsonfile, func=None):
                     newfeat = func(feat)
                     if newfeat:
                         yield newfeat
+            elif 'coordinates' in obj:
+                newfeat = func(to_feature(obj))
+                if newfeat:
+                    yield newfeat
+
+
+def to_feature(obj):
+    """Takes a feature or a geometry
+    returns feature verbatim or
+    wraps geom in a feature with empty properties
+    """
+    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):


Test patch:
diff --git a/tests/point_pretty_geom.txt b/tests/point_pretty_geom.txt
new file mode 100644
index 0000000..0b8b33d
--- /dev/null
+++ b/tests/point_pretty_geom.txt
@@ -0,0 +1,4 @@
+{
+   "coordinates": [-122.7282, 45.5801],
+   "type": "Point"
+}
diff --git a/tests/test_features.py b/tests/test_features.py
index d8e240e..43eea90 100644
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -4,7 +4,7 @@ import sys
 import pytest
 
 from cligj.features import \
-    coords_from_query, iter_query, \
+    coords_from_query, iter_query, to_feature, \
     normalize_feature_inputs, normalize_feature_objects
 
 
@@ -118,6 +118,20 @@ def test_coordpairs_space(expected_features):
     assert _geoms(features) == _geoms(expected_features)
 
 
+def test_geometrysequence(expected_features):
+    features = normalize_feature_inputs(None, 'features', ["tests/twopoints_geom_seq.txt"])
+    assert _geoms(features) == _geoms(expected_features)
+
+
+def test_geometrysequencers(expected_features):
+    features = normalize_feature_inputs(None, 'features', ["tests/twopoints_geom_seqrs.txt"])
+    assert _geoms(features) == _geoms(expected_features)
+
+
+def test_geometrypretty(expected_features):
+    features = normalize_feature_inputs(None, 'features', ["tests/point_pretty_geom.txt"])
+    assert _geoms(features)[0] == _geoms(expected_features)[0]
+
 class MockGeo(object):
     def __init__(self, feature):
         self.__geo_interface__ = feature
@@ -134,3 +148,10 @@ def test_normalize_feature_objects_bad(expected_features):
     objs.append(MockGeo(dict()))
     with pytest.raises(ValueError):
         list(normalize_feature_objects(objs))
+
+def test_to_feature(expected_features):
+    geom = expected_features[0]['geometry']
+    feat = {'type': 'Feature', 'properties': {}, 'geometry': geom}
+    assert to_feature(feat) == to_feature(geom)
+    with pytest.raises(ValueError):
+        assert to_feature({'type': 'foo'})
diff --git a/tests/twopoints_geom_seq.txt b/tests/twopoints_geom_seq.txt
new file mode 100644
index 0000000..a545e4d
--- /dev/null
+++ b/tests/twopoints_geom_seq.txt
@@ -0,0 +1,2 @@
+{"coordinates": [-122.7282, 45.5801], "type": "Point"}
+{"coordinates": [-121.3153, 44.0582], "type": "Point"}
diff --git a/tests/twopoints_geom_seqrs.txt b/tests/twopoints_geom_seqrs.txt
new file mode 100644
index 0000000..e5f40bd
--- /dev/null
+++ b/tests/twopoints_geom_seqrs.txt
@@ -0,0 +1,8 @@
+{
+   "coordinates": [-122.7282, 45.5801],
+   "type": "Point"
+  }
+{
+   "coordinates": [-121.3153, 44.0582],
+   "type": "Point"
+  }