private relation ConvertRowToOSMRelation(IRow currentRow, IWorkspace featureWorkspace, int tagsFieldIndex, int osmIDFieldIndex, int changesetIDFieldIndex, int osmVersionFieldIndex, int userIDFieldIndex, int userNameFieldIndex, int timeStampFieldIndex, int visibleFieldIndex, int membersFieldIndex, int extensionVersion)
        {

            if (currentRow == null)
                throw new ArgumentNullException("currentRow");

            relation osmRelation = new relation();
            object featureValue = DBNull.Value;
            List<object> relationItems = new List<object>();

            if (membersFieldIndex != -1)
            {
                member[] members = _osmUtility.retrieveMembers(currentRow, membersFieldIndex);
                relationItems.AddRange(members);
            }

            if (osmIDFieldIndex != -1)
            {
                osmRelation.id = Convert.ToString(currentRow.get_Value(osmIDFieldIndex));
            }

            if (changesetIDFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(changesetIDFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.changeset = Convert.ToString(currentRow.get_Value(changesetIDFieldIndex));
                }
            }

            if (osmVersionFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(osmVersionFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.version = Convert.ToString(featureValue);
                }
            }

            if (userIDFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(userIDFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.uid = Convert.ToString(featureValue);
                }
            }

            if (userNameFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(userNameFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.user = Convert.ToString(featureValue);
                }
            }

            if (timeStampFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(timeStampFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.timestamp = Convert.ToDateTime(featureValue).ToUniversalTime().ToString("u");
                }
            }

            if (visibleFieldIndex != -1)
            {
                featureValue = currentRow.get_Value(visibleFieldIndex);

                if (featureValue != DBNull.Value)
                {
                    osmRelation.visible = Convert.ToString(featureValue);
                }
            }

            if (tagsFieldIndex > -1)
            {
                tag[] tags = null;
                tags = _osmUtility.retrieveOSMTags((IRow)currentRow, tagsFieldIndex, featureWorkspace);

                // if the row is of type IFeature and a polygon then add the type=multipolygon tag
                if (currentRow is IFeature)
                {
                    IFeature currentFeature = currentRow as IFeature;

                    if (currentFeature.Shape.GeometryType == esriGeometryType.esriGeometryPolygon)
                    {
                        tag mpTag = new tag();
                        mpTag.k = "type";
                        mpTag.v = "multipolygon";

                        relationItems.Add(mpTag);
                    }
                }

                if (tags.Length != 0)
                {
                    relationItems.AddRange(tags);
                }
            }

            // add all items (member and tags) to the relation element
            osmRelation.Items = relationItems.ToArray();

            return osmRelation;
        }
        /// <summary>
        /// This function assembles a version of the logged relation action from the revision table in the OsmChange format http://wiki.openstreetmap.org/wiki/OsmChange
        /// </summary>
        /// <param name="relationTable"></param>
        /// <param name="action"></param>
        /// <param name="osmID"></param>
        /// <param name="changeSetID"></param>
        /// <param name="osmVersion"></param>
        /// <param name="nodeosmIDLookup"></param>
        /// <param name="wayosmIDLookup"></param>
        /// <param name="relationosmIDLookup"></param>
        /// <param name="extensionVersion"></param>
        /// <returns></returns>
        private ESRI.ArcGIS.OSM.OSMClassExtension.relation CreateRelationRepresentation(ITable relationTable, string action, long osmID, string changeSetID, int osmVersion, Dictionary<long, long> nodeosmIDLookup, Dictionary<long, long> wayosmIDLookup, Dictionary<long, long> relationosmIDLookup, int extensionVersion)
        {
            ESRI.ArcGIS.OSM.OSMClassExtension.relation relationRepresentation = new ESRI.ArcGIS.OSM.OSMClassExtension.relation();

            // let's find all the rows that have a different status than 200 - meaning success
            IQueryFilter queryFilter = new QueryFilterClass();
            queryFilter.WhereClause = relationTable.WhereClauseByExtensionVersion(osmID, "OSMID", extensionVersion);

            using (ComReleaser comReleaser = new ComReleaser())
            {
                ICursor searchCursor = relationTable.Search(queryFilter, false);
                comReleaser.ManageLifetime(searchCursor);

                IRow relationRow = searchCursor.NextRow();

                int osmTagsFieldIndex = relationTable.Fields.FindField("osmTags");
                int osmIDFieldIndex = relationTable.Fields.FindField("osmID");
                int osmUserFieldIndex = relationTable.Fields.FindField("osmuser");
                int osmUIDFieldIndex = relationTable.Fields.FindField("osmuid");
                int osmVisibleFieldIndex = relationTable.Fields.FindField("osmvisible");
                int osmVersionFieldIndex = relationTable.Fields.FindField("osmversion");
                int osmMembersFieldIndex = relationTable.Fields.FindField("osmMembers");

                if (relationRow != null)
                {
                    switch (action)
                    {
                        case "create":
                            // the newly created node needs to carry the changeset info, the coordinate and the tags
                            relationRepresentation.changeset = changeSetID;

                            ESRI.ArcGIS.OSM.OSMClassExtension.tag[] tags = null;
                            if (osmTagsFieldIndex > -1)
                            {
                                tags = _osmUtility.retrieveOSMTags(relationRow, osmTagsFieldIndex, ((IDataset)relationTable).Workspace);
                            }

                            List<tag> valueOnlyTags = new List<tag>();

                            for (int index = 0; index < tags.Length; index++)
                            {
                                if (!String.IsNullOrEmpty(tags[index].v))
                                {
                                    valueOnlyTags.Add(tags[index]);
                                }
                            }

                            if (osmIDFieldIndex > -1)
                            {
                                relationRepresentation.id = osmID.ToString();
                            }

                            ESRI.ArcGIS.OSM.OSMClassExtension.member[] members = null;
                            if (osmMembersFieldIndex > -1)
                            {
                                members = _osmUtility.retrieveMembers(relationRow, osmMembersFieldIndex);

                                // run the member ids through the lookup table
                                for (int memberIndex = 0; memberIndex < members.Length; memberIndex++)
                                {
                                    switch (members[memberIndex].type)
                                    {
                                        case ESRI.ArcGIS.OSM.OSMClassExtension.memberType.way:
                                            if (wayosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(wayosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        case ESRI.ArcGIS.OSM.OSMClassExtension.memberType.node:
                                            if (nodeosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(nodeosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        case ESRI.ArcGIS.OSM.OSMClassExtension.memberType.relation:
                                            if (relationosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(relationosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        default:
                                            break;
                                    }
                                }

                                _osmUtility.insertMembers(osmMembersFieldIndex, relationRow, members);
                            }

                            // add the member and the tags to the relation element
                            List<object> relationItems = new List<object>();
                            relationItems.AddRange(members);
                            relationItems.AddRange(valueOnlyTags.ToArray());

                            relationRepresentation.Items = relationItems.ToArray();

                            break;
                        case "modify":
                            // for an update the complete (full) relation needs to be returned
                            relationRepresentation.changeset = changeSetID;
                            if (osmIDFieldIndex > -1)
                            {
                                relationRepresentation.id = Convert.ToString(relationRow.get_Value(osmIDFieldIndex), new CultureInfo("en-US"));
                            }

                            if (osmUserFieldIndex > -1)
                            {
                                relationRepresentation.user = Convert.ToString(relationRow.get_Value(osmUserFieldIndex));
                            }

                            if (osmUIDFieldIndex > -1)
                            {
                                relationRepresentation.uid = Convert.ToString(relationRow.get_Value(osmUIDFieldIndex), new CultureInfo("en-US"));
                            }

                            if (osmVersionFieldIndex > -1)
                            {
                                relationRepresentation.version = Convert.ToString(relationRow.get_Value(osmVersionFieldIndex));
                            }

                            tags = null;
                            if (osmTagsFieldIndex > -1)
                            {
                                tags = _osmUtility.retrieveOSMTags((IRow)relationRow, osmTagsFieldIndex, ((IDataset)relationTable).Workspace);
                            }

                            valueOnlyTags = new List<tag>();

                            for (int index = 0; index < tags.Length; index++)
                            {
                                if (!String.IsNullOrEmpty(tags[index].v))
                                {
                                    valueOnlyTags.Add(tags[index]);
                                }
                            }

                            members = null;
                            if (osmMembersFieldIndex > -1)
                            {
                                members = _osmUtility.retrieveMembers(relationRow, osmMembersFieldIndex);

                                // run the member ids through the lookup table
                                for (int memberIndex = 0; memberIndex < members.Length; memberIndex++)
                                {
                                    switch (members[memberIndex].type)
                                    {
                                        case memberType.way:
                                            if (wayosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(wayosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        case memberType.node:
                                            if (nodeosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(nodeosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        case memberType.relation:
                                            if (relationosmIDLookup.ContainsKey(Convert.ToInt64(members[memberIndex].@ref)))
                                            {
                                                members[memberIndex].@ref = Convert.ToString(relationosmIDLookup[Convert.ToInt64(members[memberIndex].@ref)]);
                                            }
                                            break;
                                        default:
                                            break;
                                    }
                                }

                                _osmUtility.insertMembers(osmMembersFieldIndex, relationRow, members);
                            }

                            // add the member and the tags to the relation element
                            relationItems = new List<object>();
                            relationItems.AddRange(valueOnlyTags.ToArray());
                            relationItems.AddRange(members);

                            relationRepresentation.Items = relationItems.ToArray();

                            break;
                        case "delete":

                            relationRepresentation.changeset = changeSetID;
                            relationRepresentation.id = Convert.ToString(osmID);
                            relationRepresentation.version = Convert.ToString(osmVersion);

                            break;
                        default:
                            break;
                    }
                }
                else
                {
                    if (action.Equals("delete", StringComparison.InvariantCultureIgnoreCase))
                    {
                        relationRepresentation.changeset = changeSetID;
                        relationRepresentation.id = Convert.ToString(osmID);
                        relationRepresentation.version = Convert.ToString(osmVersion);
                    }
                }
            }

            return relationRepresentation;
        }
        private osmRelationGeometryType walkRelationGeometry(IFeatureClass osmLineFeatureClass, IFeatureClass osmPolygonFeatureClass, ITable relationTable, relation currentRelation)
        {
            osmRelationGeometryType testedGeometry = osmRelationGeometryType.osmUnknownGeometry;

            // we use this dictionary to determine if we can fully walk this relation
            // - the assumption is that we can walk the geometry is all node counts are 2
            Dictionary<int, int> nodeCountDictionary = new Dictionary<int, int>();

            try
            {
                if (currentRelation.Items == null)
                    return testedGeometry;

                foreach (var item in currentRelation.Items)
                {
                    if (item is ESRI.ArcGIS.OSM.OSMClassExtension.member)
                    {
                        member memberItem = item as member;
                        IQueryFilter osmIDQueryFilter = new QueryFilterClass();

                        using (ComReleaser comReleaser = new ComReleaser())
                        {
                            if (osmLineFeatureClass != null)
                            {
                                osmIDQueryFilter.WhereClause = osmLineFeatureClass.WhereClauseByExtensionVersion(memberItem.@ref, "OSMID", 2);
                                IFeatureCursor lineFeatureCursor = osmLineFeatureClass.Search(osmIDQueryFilter, false);
                                comReleaser.ManageLifetime(lineFeatureCursor);

                                IFeature foundLineFeature = lineFeatureCursor.NextFeature();

                                if (foundLineFeature != null)
                                {
                                    IPointCollection pointCollection = foundLineFeature.Shape as IPointCollection;

                                    int firstPointID = pointCollection.get_Point(0).ID;
                                    int lastPointID = pointCollection.get_Point(pointCollection.PointCount - 1).ID;

                                    if (nodeCountDictionary.ContainsKey(firstPointID))
                                    {
                                        nodeCountDictionary[firstPointID] = nodeCountDictionary[firstPointID] + 1;
                                    }
                                    else
                                    {
                                        nodeCountDictionary.Add(firstPointID, 1);
                                    }

                                    if (nodeCountDictionary.ContainsKey(lastPointID))
                                    {
                                        nodeCountDictionary[lastPointID] = nodeCountDictionary[lastPointID] + 1;
                                    }
                                    else
                                    {
                                        nodeCountDictionary.Add(lastPointID, 1);
                                    }
                                }

                                osmIDQueryFilter.WhereClause = osmPolygonFeatureClass.WhereClauseByExtensionVersion(memberItem.@ref, "OSMID", 2);
                                IFeatureCursor polygonFeatureCursor = osmPolygonFeatureClass.Search(osmIDQueryFilter, false);
                                comReleaser.ManageLifetime(polygonFeatureCursor);

                                IFeature foundPolygonFeature = polygonFeatureCursor.NextFeature();

                                if (foundPolygonFeature != null)
                                {
                                    IPointCollection pointCollection = foundPolygonFeature.Shape as IPointCollection;

                                    int firstPointID = pointCollection.get_Point(0).ID;

                                    if (nodeCountDictionary.ContainsKey(firstPointID))
                                    {
                                        nodeCountDictionary[firstPointID] = nodeCountDictionary[firstPointID] + 2;
                                    }
                                    else
                                    {
                                        nodeCountDictionary.Add(firstPointID, 2);
                                    }
                                }

                                osmIDQueryFilter.WhereClause = relationTable.WhereClauseByExtensionVersion(memberItem.@ref, "OSMID", 2);
                                ICursor relationCursor = relationTable.Search(osmIDQueryFilter, false);
                                comReleaser.ManageLifetime(relationCursor);

                                IRow foundRelation = relationCursor.NextRow();

                                if (foundRelation != null)
                                {
                                    // in order to be in the relation table is needs to be a either a super-relation or a mixed type entity (hence hybrid)
                                    testedGeometry = osmRelationGeometryType.osmHybridGeometry;
                                    break;
                                }
                            }
                        }
                    }
                }

                // check if there are any nodes counts other than 2, if there are then we cannot fully "walk" the geometry and it will be considered a line
                if (nodeCountDictionary.Values.Any(e => (e != 2)))
                {
                    testedGeometry = osmRelationGeometryType.osmPolyline;
                }
                else
                {
                    testedGeometry = osmRelationGeometryType.osmPolygon;
                }
            }

            catch
            {
                testedGeometry = osmRelationGeometryType.osmHybridGeometry;
            }

            return testedGeometry;
        }