Repository: tommyjcarpenter/osmtogeojson

Issue:
Relations with LineString and Polygon members have no geometry
If a relation has Polygons and LineStrings way members, the resulting feature has no geometry.

tyrasd/osmtogeojson instead seems to export every member as feature with an @relations property.

osmtogeojson should either behave accordingly or provide all members geometries as a GeometryCollection.

Example for [relation/5576903](https://openstreetmap.org/relation/5576903):
```
{
      "type": "Feature",
      "properties": {
        "@id": "way/376770457",
        "covered": "yes",
        "highway": "service",
        "indoor": "yes",
        "layer": "-2",
        "location": "underground",
        "oneway": "yes",
        "ref": "Ebene B",
        "service": "parking_aisle",
        "sidewalk": "left",
        "@relations": [
          {
            "role": "",
            "rel": 5576903,
            "reltags": {
              "amenity": "parking",
              "fee": "yes",
              "name": "Schillerplatz",
              "opening_hours": "24/7",
              "operator": "APCOA PARKING Deutschland GmbH",
              "parking": "underground",
              "site": "parking",
              "type": "site",
              "url": "https://service.stuttgart.de/lhs-services/ivlz/index.php?uid=35&objectid=108523&objecttype=dept&page=svc&servicetype=parking&serviceid=9356&detailid=65&showservice=1",
              "website": "https://www.apcoa.de/parken-in/stuttgart/schillerplatz.html"
            }
          }
        ]
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              9.178458,
              48.7770338
            ],
           ...
          ]]
      }
    }
```
           




Comments:

Patch:
diff --git a/Changelog.md b/Changelog.md
index 60d3034..0be1e85 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,7 +2,12 @@
 All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
-and this project adheres to [Semantic Versioning](http://semver.org/).
+and this project will later on adhere to [Semantic Versioning](http://semver.org/). 
+
+Note: A change of a minor/patch version still may result in changed GeoJSON export results!  
 
 ## [0.0.1]
     * Project creation
+## [0.0.2]
+    * Prefix osm "id" property by an "at"-sign (fixes #5)
+    * Create GeometryCollection for relations with mixed member types (fixes #3)
diff --git a/osmtogeojson/osmtogeojson.py b/osmtogeojson/osmtogeojson.py
index 36eb032..6a05cd3 100644
--- a/osmtogeojson/osmtogeojson.py
+++ b/osmtogeojson/osmtogeojson.py
@@ -1,5 +1,8 @@
+import logging
 from osmtogeojson import merge
 
+logger = logging.getLogger(__name__)
+
 def _determine_feature_type(way_nodes):
     # get more advanced???
     if way_nodes[0] == way_nodes[-1]:
@@ -65,6 +68,7 @@ def _process_relations(resulting_geojson, relation_storage, way_storage, node_st
 
         way_types = []
         way_coordinate_blocks = []
+        only_way_members = True
         for mem in r["members"]:
             if mem["type"] == "way":
                 way_id = mem["ref"]
@@ -73,7 +77,7 @@ def _process_relations(resulting_geojson, relation_storage, way_storage, node_st
                 way_coordinate_blocks.append(processed["geometry"]["coordinates"])
                 ways_used_in_relations[way_id] = 1
             else:
-                print(mem["type"])
+                only_way_members = False
 
         rel["geometry"] = {}
 
@@ -86,8 +90,31 @@ def _process_relations(resulting_geojson, relation_storage, way_storage, node_st
             rel["geometry"]["coordinates"] = [x for x in way_coordinate_blocks]
             merge.merge_line_string(rel)
         else:
-            print(way_types)
-
+            # relation does not consist of Polygons or LineStrings only... 
+            # In this case, overpass reports every individual member with its relation reference
+            # Another option would be to export such a relation as GeometryCollection
+           
+            rel["geometry"]["type"] = "GeometryCollection"
+            member_geometries = []
+            for mem in r["members"]:
+                if mem["type"] == "way":
+                    way_id = mem["ref"]
+                    processed = _process_single_way(way_id, way_storage[way_id], node_storage, nodes_used_in_ways)
+                    member_geometries.append(processed["geometry"])
+                elif mem["type"] == "node":
+                    node_id = mem["ref"]
+                    node = node_storage[node_id]
+                    geometry = {}
+                    geometry["type"] = "Point"
+                    geometry["coordinates"] = [node["lon"], node["lat"]]
+                    member_geometries.append(geometry)
+                    # Well, used_in_rels, but we want to ignore it as well, don't we?
+                    nodes_used_in_ways[node_id] = 1
+                else:
+                    logger.warn("Relations members not yet handled (%s)", rel_id)
+                
+            rel["geometry"]["geometries"] = member_geometries
+            
         resulting_geojson["features"].append(rel)
     return ways_used_in_relations
 
diff --git a/setup.py b/setup.py
index 78fbb6b..ef27f82 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='osmtogeojson',
-    version='0.0.1',
+    version='0.0.2',
     packages=find_packages(exclude=["tests.*", "tests"]),
     author="Tommy Carpenter",
     author_email="",


Test patch:
diff --git a/tests/fixtures/geomcollection_geojson.json b/tests/fixtures/geomcollection_geojson.json
new file mode 100644
index 0000000..a7e435f
--- /dev/null
+++ b/tests/fixtures/geomcollection_geojson.json
@@ -0,0 +1,82 @@
+{
+  "type": "FeatureCollection",
+  "features": [
+    {
+      "type": "Feature",
+      "id": "relation/5576903",
+      "properties": {
+        "@id": "relation/5576903",
+        "amenity": "parking",
+        "fee": "yes",
+        "name": "Schillerplatz",
+        "opening_hours": "24/7",
+        "operator": "APCOA PARKING Deutschland GmbH",
+        "parking": "underground",
+        "site": "parking",
+        "type": "site",
+        "url": "https://service.stuttgart.de/lhs-services/ivlz/index.php?uid=35&objectid=108523&objecttype=dept&page=svc&servicetype=parking&serviceid=9356&detailid=65&showservice=1",
+        "website": "https://www.apcoa.de/parken-in/stuttgart/schillerplatz.html"
+      },
+      "geometry": {
+        "geometries": [
+          {
+            "coordinates": [
+              [
+                9.1785771,
+                48.7769778
+              ],
+              [
+                9.1785466,
+                48.7769462
+              ]
+            ],
+            "type": "LineString"
+          },
+          {
+            "coordinates": [
+              [
+                [
+                  9.178599,
+                  48.7769776
+                ],
+                [
+                  9.1785505,
+                  48.7769354
+                ],
+                [
+                  9.1785521,
+                  48.7769096
+                ],
+                [
+                  9.1785628,
+                  48.776876
+                ],
+                [
+                  9.1786147,
+                  48.7768176
+                ],
+                [
+                  9.1786676,
+                  48.7767825
+                ],
+                [
+                  9.178599,
+                  48.7769776
+                ]
+              ]
+            ],
+            "type": "Polygon"
+          },
+          {
+            "coordinates": [
+              9.1786676,
+              48.7767825
+            ],
+            "type": "Point"
+          }
+        ],
+        "type": "GeometryCollection"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/fixtures/geomcollection_overpass.json b/tests/fixtures/geomcollection_overpass.json
new file mode 100644
index 0000000..c6e803f
--- /dev/null
+++ b/tests/fixtures/geomcollection_overpass.json
@@ -0,0 +1,144 @@
+{
+  "version": 0.6,
+  "generator": "Overpass API 0.7.55.6 486819c8",
+  "osm3s": {
+    "timestamp_osm_base": "2019-04-25T18:26:02Z",
+    "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
+  },
+  "notes": "This is a shortened test fixture, the real feature has some more members...",
+  "elements": [
+{
+  "type": "node",
+  "id": 20850150,
+  "lat": 48.7767825,
+  "lon": 9.1786676,
+  "tags": {
+    "amenity": "parking_entrance",
+    "foot": "yes",
+    "maxheight": "2",
+    "parking": "underground"
+  }
+},
+{
+  "type": "node",
+  "id": 3801571628,
+  "lat": 48.7768176,
+  "lon": 9.1786147
+},
+{
+  "type": "node",
+  "id": 3801571632,
+  "lat": 48.7768760,
+  "lon": 9.1785628
+},
+{
+  "type": "node",
+  "id": 3817566170,
+  "lat": 48.7769096,
+  "lon": 9.1785521,
+  "tags": {
+    "barrier": "lift_gate",
+    "layer": "-1",
+    "location": "underground"
+  }
+},
+{
+  "type": "node",
+  "id": 3801571640,
+  "lat": 48.7769354,
+  "lon": 9.1785505
+},
+{
+  "type": "node",
+  "id": 3801569051,
+  "lat": 48.7769776,
+  "lon": 9.1785990
+},
+{
+  "type": "node",
+  "id": 3801571641,
+  "lat": 48.7769462,
+  "lon": 9.1785466
+},
+{
+  "type": "node",
+  "id": 3801571647,
+  "lat": 48.7769778,
+  "lon": 9.1785771
+},
+{
+  "type": "way",
+  "id": 376770460,
+  "nodes": [
+    3801571647,
+    3801571641
+  ],
+  "tags": {
+    "covered": "yes",
+    "highway": "footway",
+    "indoor": "yes",
+    "layer": "-2",
+    "location": "underground",
+    "ref": "Ebene B"
+  }
+},
+{
+  "type": "way",
+  "id": 376770534,
+  "nodes": [
+    3801569051,
+    3801571640,
+    3817566170,
+    3801571632,
+    3801571628,
+    20850150,
+    3801569051
+  ],
+  "tags": {
+    "highway": "service",
+    "incline": "up",
+    "layer": "-1",
+    "location": "underground",
+    "oneway": "yes",
+    "service": "driveway",
+    "sidewalk": "right",
+    "tunnel": "yes"
+  }
+},
+{
+  "type": "relation",
+  "id": 5576903,
+  "members": [
+    {
+      "type": "way",
+      "ref": 376770460,
+      "role": ""
+    },
+    {
+      "type": "way",
+      "ref": 376770534,
+      "role": ""
+    },
+    {
+      "type": "node",
+      "ref": 20850150,
+      "role": "exit"
+    }
+  ],
+  "tags": {
+    "amenity": "parking",
+    "fee": "yes",
+    "name": "Schillerplatz",
+    "opening_hours": "24/7",
+    "operator": "APCOA PARKING Deutschland GmbH",
+    "parking": "underground",
+    "site": "parking",
+    "type": "site",
+    "url": "https://service.stuttgart.de/lhs-services/ivlz/index.php?uid=35&objectid=108523&objecttype=dept&page=svc&servicetype=parking&serviceid=9356&detailid=65&showservice=1",
+    "website": "https://www.apcoa.de/parken-in/stuttgart/schillerplatz.html"
+  }
+}
+
+  ]
+}
+
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
new file mode 100644
index 0000000..69eafb8
--- /dev/null
+++ b/tests/test_conversion.py
@@ -0,0 +1,31 @@
+import json
+import unittest
+
+from osmtogeojson import osmtogeojson
+
+class ConversionTest(unittest.TestCase):
+
+    # We want to see the differences
+    maxDiff = None
+
+    def test_relation_with_different_member_types_becomes_GeometryCollection(self):
+        self.compare_files("geomcollection_overpass.json", "geomcollection_geojson.json")
+        
+    def not_yet_test_np(self):
+        self.compare_files("np_overpass.json", "np_geojson.json")
+        
+    def not_yet_test_summitschool(self):
+        self.compare_files("summitschool_overpass.json", "summitschool_geojson.json")
+     
+    def compare_files(self, inputfile, geojsonfile):
+        with open("tests/fixtures/" + inputfile, "r") as f:
+            osm_json = json.loads(f.read())
+
+        with open("tests/fixtures/" + geojsonfile, "r") as f:
+            expected_geojson = json.loads(f.read())
+        
+        actual_geojson = osmtogeojson.process_osm_json(osm_json)
+        self.assertEqual(actual_geojson, expected_geojson)
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file