From d67c5b4688f986eab87de2b6f25dd89a32e3e57f Mon Sep 17 00:00:00 2001 From: Brian Hatchl Date: Fri, 26 May 2023 10:47:26 -0400 Subject: [PATCH] Exception when merging positive id entity into negative id entity (#5655) * exception when merging positive id entity into negative id entity * Fix assertion failure for element merger * Update element merge server * add failing test using positive id merged into negative expects the hoot:merge:target tag to be removed and the hoot:status=3 tag to be added * Update merge functions to return the correct ElementId of the remaining element --------- Co-authored-by: Ben Marchant <13385275+bmarchant@users.noreply.github.com> --- .../core/conflate/polygon/BuildingMerger.cpp | 26 ++++++---- .../core/conflate/polygon/BuildingMerger.h | 4 +- .../hoot/js/conflate/merging/AreaMergerJs.cpp | 26 +++++++--- .../hoot/js/conflate/merging/AreaMergerJs.h | 6 +-- .../js/conflate/merging/ElementMergerJs.cpp | 47 +++++++---------- .../hoot/js/conflate/merging/PoiMergerJs.cpp | 17 +++++-- .../hoot/js/conflate/merging/PoiMergerJs.h | 6 +-- .../js/conflate/merging/RailwayMergerJs.cpp | 21 ++++++-- .../js/conflate/merging/RailwayMergerJs.h | 4 +- translations/test/ElementMergeServer.js | 50 ++++++++++++++++--- 10 files changed, 136 insertions(+), 71 deletions(-) diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp index a995bc2cf2..c4248d78e9 100644 --- a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp +++ b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.cpp @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2015-2023 Maxar (http://www.maxar.com/) */ #include "BuildingMerger.h" @@ -39,8 +39,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -308,8 +308,7 @@ ElementId BuildingMerger::_getIdOfMoreComplexBuilding(const ElementPtr& building if (building1.get()) { LOG_VART(building1); - nodeCount1 = - (int)FilteredVisitor::getStat(std::make_shared(), std::make_shared(), map, building1); + nodeCount1 = (int)FilteredVisitor::getStat(std::make_shared(), std::make_shared(), map, building1); } LOG_VART(nodeCount1); @@ -317,8 +316,7 @@ ElementId BuildingMerger::_getIdOfMoreComplexBuilding(const ElementPtr& building if (building2.get()) { LOG_VART(building2); - nodeCount2 = - (int)FilteredVisitor::getStat(std::make_shared(), std::make_shared(), map, building2); + nodeCount2 = (int)FilteredVisitor::getStat(std::make_shared(), std::make_shared(), map, building2); } LOG_VART(nodeCount2); @@ -690,7 +688,7 @@ void BuildingMerger::_fixStatuses(OsmMapPtr map) } } -void BuildingMerger::merge(OsmMapPtr map, const ElementId& mergeTargetId) +ElementId BuildingMerger::merge(OsmMapPtr map, const ElementId& mergeTargetId) { LOG_INFO("Merging buildings..."); @@ -716,6 +714,7 @@ void BuildingMerger::merge(OsmMapPtr map, const ElementId& mergeTargetId) int buildingsMerged = 0; BuildingCriterion buildingCrit; + std::vector> replacedElements; // Copy the way map so the original can be modified const WayMap ways = map->getWays(); for (auto wayItr = ways.begin(); wayItr != ways.end(); ++wayItr) @@ -728,7 +727,6 @@ void BuildingMerger::merge(OsmMapPtr map, const ElementId& mergeTargetId) pairs.emplace(mergeTargetId, way->getElementId()); BuildingMerger merger(pairs); LOG_VART(pairs.size()); - std::vector> replacedElements; merger.apply(map, replacedElements); buildingsMerged++; } @@ -745,13 +743,23 @@ void BuildingMerger::merge(OsmMapPtr map, const ElementId& mergeTargetId) pairs.emplace(mergeTargetId, relation->getElementId()); BuildingMerger merger(pairs); LOG_VART(pairs.size()); - std::vector> replacedElements; merger.apply(map, replacedElements); buildingsMerged++; } } LOG_INFO("Merged " << buildingsMerged << " buildings."); + + // Check if the merge target wasn't changed + if (map->containsElement(mergeTargetId)) + return mergeTargetId; + // Find the correct object that was merged + for (const auto& p : replacedElements) + { + if (p.first == mergeTargetId && map->containsElement(p.second)) + return p.second; + } + return mergeTargetId; } QString BuildingMerger::toString() const diff --git a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.h b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.h index ab66917dd2..08e53ae7fb 100644 --- a/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.h +++ b/hoot-core/src/main/cpp/hoot/core/conflate/polygon/BuildingMerger.h @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2015, 2017, 2018, 2019, 2020, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2015-2023 Maxar (http://www.maxar.com/) */ #ifndef BUILDINGMERGER_H #define BUILDINGMERGER_H @@ -76,7 +76,7 @@ class BuildingMerger : public MergerBase * @param map a map containing the buildings to be merged * @param mergeTargetId the ID of the building which all other buildings should be merged into */ - static void merge(OsmMapPtr map, const ElementId& mergeTargetId); + static ElementId merge(OsmMapPtr map, const ElementId& mergeTargetId); /** * Adds multiple buildings to the same relation diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.cpp b/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.cpp index bcb1ff29b6..f8f9bf1a2b 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.cpp +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.cpp @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2016, 2018, 2019, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2016-2023 Maxar (http://www.maxar.com/) */ #include "AreaMergerJs.h" @@ -41,7 +41,7 @@ using namespace v8; namespace hoot { -void AreaMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) +ElementId AreaMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) { LOG_INFO("Merging areas..."); @@ -63,6 +63,7 @@ void AreaMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* int areasMerged = 0; NonBuildingAreaCriterion nonBuildingAreaCrit; + std::vector> replacedElements; // Make a copy of the way map so that the mergers can modify the original const WayMap ways = map->getWays(); for (auto it = ways.begin(); it != ways.end(); ++it) @@ -78,9 +79,8 @@ void AreaMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* matches.emplace(mergeTargetId, ElementId::way(way->getId())); // apply script merging ScriptMerger merger(script, plugin, matches); - std::vector> replacedWays; - merger.apply(map, replacedWays); - LOG_VART(replacedWays.size()); + merger.apply(map, replacedElements); + LOG_VART(replacedElements.size()); areasMerged++; } @@ -100,15 +100,25 @@ void AreaMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* matches.emplace(mergeTargetId, ElementId::relation(relation->getId())); // apply script merging ScriptMerger merger(script, plugin, matches); - std::vector> replacedRelations; - merger.apply(map, replacedRelations); - LOG_VART(replacedRelations.size()); + merger.apply(map, replacedElements); + LOG_VART(replacedElements.size()); areasMerged++; } } LOG_INFO("Merged " << areasMerged << " areas."); + + // Check if the merge target wasn't changed + if (map->containsElement(mergeTargetId)) + return mergeTargetId; + // Find the correct object that was merged + for (const auto& p : replacedElements) + { + if (p.first == mergeTargetId && map->containsElement(p.second)) + return p.second; + } + return mergeTargetId; } } diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.h b/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.h index 013992353d..49f1fbb91c 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.h +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/AreaMergerJs.h @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2016, 2018, 2019, 2021 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2016-2023 Maxar (http://www.maxar.com/) */ #ifndef AREAMERGERJS_H @@ -32,8 +32,8 @@ #include #include -#include #include +#include namespace hoot { @@ -57,7 +57,7 @@ class AreaMergerJs * @param mergeTargetId the ID of the area which all other areas should be merged into * @param current the context this method should run under */ - static void merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); + static ElementId merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); }; } diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/ElementMergerJs.cpp b/hoot-js/src/main/cpp/hoot/js/conflate/merging/ElementMergerJs.cpp index e1da85d464..106c38d87c 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/ElementMergerJs.cpp +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/ElementMergerJs.cpp @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2018, 2019, 2020, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2018-2023 Maxar (http://www.maxar.com/) */ #include "ElementMergerJs.h" @@ -77,8 +77,7 @@ void ElementMergerJs::Init(Local exports) Isolate* current = exports->GetIsolate(); HandleScope scope(current); Local context = current->GetCurrentContext(); - Maybe success = exports->Set(context, toV8("merge"), - FunctionTemplate::New(current, merge)->GetFunction(context).ToLocalChecked()); + Maybe success = exports->Set(context, toV8("merge"), FunctionTemplate::New(current, merge)->GetFunction(context).ToLocalChecked()); (void) success; // unused variable } @@ -93,8 +92,7 @@ void ElementMergerJs::merge(const FunctionCallbackInfo& args) { if (args.Length() != 1) { - args.GetReturnValue().Set( - current->ThrowException(HootExceptionJs::create(IllegalArgumentException("Expected one argument passed to 'merge'.")))); + args.GetReturnValue().Set(current->ThrowException(HootExceptionJs::create(IllegalArgumentException("Expected one argument passed to 'merge'.")))); return; } @@ -149,26 +147,24 @@ void ElementMergerJs::_merge(OsmMapPtr map, Isolate* current) } // We're using a mix of generic conflation scripts and custom built classes to merge features - // here, depending on the feature type. + // here, depending on the feature type, let the merger determine the merge target ID. switch (mergeType) { case MergeType::Building: - BuildingMerger::merge(map, mergeTargetId); + mergeTargetId = BuildingMerger::merge(map, mergeTargetId); break; case MergeType::PoiToPolygon: - // POI/Poly always merges into the polygon and there's only one of them, so we let the - // routine determine the merge target ID. mergeTargetId = PoiPolygonMerger::mergeOnePoiAndOnePolygon(map); break; case MergeType::Poi: - PoiMergerJs::merge(map, mergeTargetId, current); + mergeTargetId = PoiMergerJs::merge(map, mergeTargetId, current); break; case MergeType::Area: - AreaMergerJs::merge(map, mergeTargetId, current); + mergeTargetId = AreaMergerJs::merge(map, mergeTargetId, current); break; case MergeType::Railway: MapProjector::projectToPlanar(map); - RailwayMergerJs::merge(map, mergeTargetId, current); + mergeTargetId = RailwayMergerJs::merge(map, mergeTargetId, current); SuperfluousNodeRemover::removeNodes(map); MapProjector::projectToWgs84(map); break; @@ -179,11 +175,13 @@ void ElementMergerJs::_merge(OsmMapPtr map, Isolate* current) // By convention, we're setting the status of any element that gets merged with something to // conflated, even if its yet to be reviewed against something else. ElementPtr mergedElement = map->getElement(mergeTargetId); - assert(mergedElement); - mergedElement->setStatus(Status(Status::Conflated)); - mergedElement->getTags()[MetadataTags::HootStatus()] = "3"; - mergedElement->getTags().remove(MetadataTags::HootMergeTarget()); - LOG_VART(mergedElement); + if (mergedElement) + { + mergedElement->setStatus(Status(Status::Conflated)); + mergedElement->getTags()[MetadataTags::HootStatus()] = "3"; + mergedElement->getTags().remove(MetadataTags::HootMergeTarget()); + LOG_VART(mergedElement); + } } ElementId ElementMergerJs::_getMergeTargetFeatureId(ConstOsmMapPtr map) @@ -210,10 +208,7 @@ ElementId ElementMergerJs::_getMergeTargetFeatureId(ConstOsmMapPtr map) std::make_shared(Status::Conflated)), std::make_shared(), map); if (numStatus1Elements != 1) - { - throw IllegalArgumentException( - "Input map must have at exactly one feature with status=1 or status=3."); - } + throw IllegalArgumentException("Input map must have at exactly one feature with status=1 or status=3."); // Return the ref or already conflated feature's ID as the target ID. ConstElementPtr status1Element = MapUtils::getFirstElementWithStatus(map, Status::Unknown1); @@ -232,10 +227,7 @@ ElementId ElementMergerJs::_getMergeTargetFeatureId(ConstOsmMapPtr map) std::make_shared(), map); LOG_VART(numMergeTargets); if (numMergeTargets != 1) - { - throw IllegalArgumentException( - QString("Input map must have exactly one feature marked with a %1 tag.").arg(MetadataTags::HootMergeTarget())); - } + throw IllegalArgumentException(QString("Input map must have exactly one feature marked with a %1 tag.").arg(MetadataTags::HootMergeTarget())); const long numNonMergeTargets = (long)FilteredVisitor::getStat( @@ -244,10 +236,7 @@ ElementId ElementMergerJs::_getMergeTargetFeatureId(ConstOsmMapPtr map) std::make_shared(), map); LOG_VART(numMergeTargets); if (numNonMergeTargets < 1) - { - throw IllegalArgumentException( - QString("Input map must have at least one feature not marked with a %1 tag.").arg(MetadataTags::HootMergeTarget())); - } + throw IllegalArgumentException(QString("Input map must have at least one feature not marked with a %1 tag.").arg(MetadataTags::HootMergeTarget())); TagKeyCriterion mergeTagCrit(MetadataTags::HootMergeTarget()); UniqueElementIdVisitor idSetVis; diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.cpp b/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.cpp index 94875fe06b..45bd990c68 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.cpp +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.cpp @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2015, 2017, 2018, 2019, 2020, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2015-2023 Maxar (http://www.maxar.com/) */ #include "PoiMergerJs.h" @@ -41,7 +41,7 @@ using namespace v8; namespace hoot { -void PoiMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) +ElementId PoiMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) { LOG_INFO("Merging POIs..."); @@ -68,6 +68,7 @@ void PoiMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* // // ...then pass those pairs one at a time through the merger, since it only merges pairs int poisMerged = 0; + std::vector> replacedNodes; // Make a copy of the node map so that the iterators work while merging const NodeMap nodes = map->getNodes(); for (auto it = nodes.begin(); it != nodes.end(); ++it) @@ -81,7 +82,6 @@ void PoiMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* matches.emplace(mergeTargetId, node->getElementId()); // apply script merging ScriptMerger merger(script, plugin, matches); - std::vector> replacedNodes; merger.apply(map, replacedNodes); LOG_VART(replacedNodes.size()); @@ -89,6 +89,17 @@ void PoiMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* } } LOG_INFO("Merged " << poisMerged << " POIs."); + + // Check if the merge target wasn't changed + if (map->containsElement(mergeTargetId)) + return mergeTargetId; + // Find the correct object that was merged + for (const auto& p : replacedNodes) + { + if (p.first == mergeTargetId && map->containsElement(p.second)) + return p.second; + } + return mergeTargetId; } } diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.h b/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.h index 54bbe6563b..759dcf5293 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.h +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/PoiMergerJs.h @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2015, 2018, 2019, 2021 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2015-2023 Maxar (http://www.maxar.com/) */ #ifndef POIMERGERJS_H @@ -32,8 +32,8 @@ #include #include -#include #include +#include namespace hoot { @@ -55,7 +55,7 @@ class PoiMergerJs * @param mergeTargetId the ID of the area which all other POIs should be merged into * @param current the context this method should run under */ - static void merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); + static ElementId merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); }; } diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.cpp b/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.cpp index 7099f814de..c0abc527eb 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.cpp +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.cpp @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2016, 2018, 2019, 2021, 2022 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2016-2023 Maxar (http://www.maxar.com/) */ #include "RailwayMergerJs.h" @@ -42,7 +42,7 @@ using namespace v8; namespace hoot { -void RailwayMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) +ElementId RailwayMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isolate* current) { LOG_INFO("Merging railways..."); @@ -63,6 +63,7 @@ void RailwayMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isola int numMerged = 0; RailwayCriterion crit; + std::vector> replacedElements; // Make a copy of the way map so that the mergers can modify the original const WayMap ways = map->getWays(); for (auto it = ways.begin(); it != ways.end(); ++it) @@ -78,9 +79,8 @@ void RailwayMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isola matches.emplace(mergeTargetId, ElementId::way(way->getId())); // apply script merging ScriptMerger merger(script, plugin, matches); - std::vector> replacedWays; - merger.apply(map, replacedWays); - LOG_VART(replacedWays.size()); + merger.apply(map, replacedElements); + LOG_VART(replacedElements.size()); numMerged++; } @@ -93,6 +93,17 @@ void RailwayMergerJs::merge(OsmMapPtr map, const ElementId& mergeTargetId, Isola map->visitWaysRw(remover); LOG_INFO("Merged " << numMerged << " railways."); + + // Check if the merge target wasn't changed + if (map->containsElement(mergeTargetId)) + return mergeTargetId; + // Find the correct object that was merged + for (const auto& p : replacedElements) + { + if (p.first == mergeTargetId && map->containsElement(p.second)) + return p.second; + } + return mergeTargetId; } } diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.h b/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.h index 86667a10ef..443d5b45b0 100644 --- a/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.h +++ b/hoot-js/src/main/cpp/hoot/js/conflate/merging/RailwayMergerJs.h @@ -22,7 +22,7 @@ * This will properly maintain the copyright information. Maxar * copyrights will be updated automatically. * - * @copyright Copyright (C) 2021 Maxar (http://www.maxar.com/) + * @copyright Copyright (C) 2021-2023 Maxar (http://www.maxar.com/) */ #ifndef RAILWAY_MERGER_JS_H @@ -57,7 +57,7 @@ class RailwayMergerJs * @param mergeTargetId the ID of the area which the other railway should be merged into * @param current the context this method should run under */ - static void merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); + static ElementId merge(OsmMapPtr map, const ElementId& mergeTargetId, v8::Isolate* current); }; } diff --git a/translations/test/ElementMergeServer.js b/translations/test/ElementMergeServer.js index 16e1363308..65c1091413 100644 --- a/translations/test/ElementMergeServer.js +++ b/translations/test/ElementMergeServer.js @@ -78,16 +78,41 @@ var areasInput = \ "; +var buildingsPositiveIntoNegativeId = + "\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + "; + var buildingsInput = "\ \ - \ - \ - \ - \ - \ - \ - \ + \ + \ + \ + \ + \ + \ + \ \ \ \ @@ -489,6 +514,17 @@ describe('ElementMergeServer', function () { }); }); + it('merges a positive id into a negative id and re-uses positive ids', function(done) { + testMergeXml(buildingsPositiveIntoNegativeId, function(tags) { + assert.equal(tags["hoot:merge:target"], undefined, "hoot:merge:target should not be present"); + assert.equal(tags["hoot:status"], "3", "hoot:status should be 3"); + assert.equal(tags["name"], "building 2"); + assert.equal(tags["alt_name"], "building 1"); + assert.equal(tags["building"], "yes"); + done(); + }); + }); + it('merges two rails with the one-to-many workflow', function() { testMerge(railsOneToManyInput, function(gj) { var tags = gj.features[0].properties;