コード例 #1
0
        internal PointCollection KeepWithin(List <VertexPositionColor> coastTriangles)
        {
            var rtree = new STRtree <Polygon>();

            for (int i = 0; i < coastTriangles.Count / 3; i++)
            {
                Coordinate[] coords = new Coordinate[] { new Coordinate(coastTriangles[i * 3].Position.X, coastTriangles[i * 3].Position.Y),
                                                         new Coordinate(coastTriangles[i * 3 + 1].Position.X, coastTriangles[i * 3 + 1].Position.Y),
                                                         new Coordinate(coastTriangles[i * 3 + 2].Position.X, coastTriangles[i * 3 + 2].Position.Y),
                                                         new Coordinate(coastTriangles[i * 3].Position.X, coastTriangles[i * 3].Position.Y) };
                var polygon = new Polygon(new LinearRing(coords));
                rtree.Insert(polygon.EnvelopeInternal, polygon);
            }
            rtree.Build();
            List <Vector2d> newpoints = new List <Vector2d>();

            foreach (var x in points)
            {
                bool contains = false;
                foreach (var p in rtree.Query(new Envelope(x.X, x.X, x.Y, x.Y)))
                {
                    if (p.Covers(new NetTopologySuite.Geometries.Point(x.X, x.Y)))
                    {
                        contains = true;
                        break;
                    }
                }
                if (contains)
                {
                    newpoints.Add(x);
                }
            }
            points = newpoints;
            return(this);
        }
コード例 #2
0
        public void Run()
        {
            hpRtree = new HPRtree <string>();
            stRtree = new STRtree <string>();

            //loadRandom(NUM_ITEMS);
            LoadGrid(NUM_ITEMS);

            var sw = new Stopwatch();

            sw.Start();
            stRtree.Build();
            sw.Stop();
            Console.WriteLine($"STRtree build time: {sw.ElapsedMilliseconds}ms");
            sw.Restart();
            hpRtree.Build();
            sw.Stop();
            Console.WriteLine($"HPRtree build time: {sw.ElapsedMilliseconds}ms");

            sw.Restart();
            for (int i = 0; i < NUM_QUERY; i++)
            {
                QueryRandom();
            }

            sw.Stop();
            Console.WriteLine($"Query time: {sw.ElapsedMilliseconds}ms");
        }
コード例 #3
0
 protected override void AddCells_internal()
 {
     foreach (var cell in CellList)
     {
         var cellPos = cell.PositionWithSize;
         NTSSTRTreeCellTree.Insert(new Envelope(cellPos[0].MinVal, cellPos[0].MaxVal,
                                                cellPos[1].MinVal, cellPos[1].MaxVal), cell);
     }
     NTSSTRTreeCellTree.Build();
 }
コード例 #4
0
 // ReSharper restore InconsistentNaming
 /// <summary>
 /// 
 /// </summary>
 /// <param name="g"></param>
 /// <returns></returns>
 public static STRtree<FacetSequence> BuildSTRtree(IGeometry g)
 {
     var tree = new STRtree<FacetSequence>(STRtreeNodeCapacity);
     var sections = ComputeFacetSequences(g);
     foreach (var section in sections)
     {
         tree.Insert(section.Envelope, section);
     }
     tree.Build();
     return tree;
 }
コード例 #5
0
        public MapGeometry(MapData map)
        {
            Map = map;

            Vertices = map.Vertices.Select(v => v.ToVector2()).ToArray();

            var minX = Vertices.Min(p => p.X);
            var maxX = Vertices.Max(p => p.X);
            var minY = Vertices.Min(p => p.Y);
            var maxY = Vertices.Max(p => p.Y);

            BottomLeftCorner = new Vector2(minX, minY);
            Area             = new Vector2(maxX - minX, maxY - minY);

            var lines = new List <Line>();

            for (int lineDefId = 0; lineDefId < Map.LineDefs.Count; lineDefId++)
            {
                var lineDef   = Map.LineDefs[lineDefId];
                var frontSide = Map.SideDefs[lineDef.SideFront];

                if (!lineDef.TwoSided)
                {
                    lines.Add(new Line(lineDefId, frontSide.Sector, lineDef.V1, Map.Vertices[lineDef.V1], lineDef.V2, Map.Vertices[lineDef.V2]));
                }
                else
                {
                    var backSide = Map.SideDefs[lineDef.SideBack];
                    lines.Add(new Line(lineDefId, frontSide.Sector, lineDef.V1, Map.Vertices[lineDef.V1], lineDef.V2, Map.Vertices[lineDef.V2], portalToSectorId: backSide.Sector));
                    lines.Add(new Line(lineDefId, backSide.Sector, lineDef.V1, Map.Vertices[lineDef.V1], lineDef.V2, Map.Vertices[lineDef.V2], portalToSectorId: frontSide.Sector));
                }
            }
            Lines = lines.ToArray();

            Sectors = new SectorInfo[map.Sectors.Count];

            foreach (var sectorIndex in Enumerable.Range(0, Sectors.Length))
            {
                var linesForSector = Lines.Where(line => line.SectorId == sectorIndex);

                Sectors[sectorIndex] = new SectorInfo(map.Sectors[sectorIndex], linesForSector);

                _sectorIdLookup.Insert(GetSectorMinimumBoundingRectangle(sectorIndex), sectorIndex);
            }
            _sectorIdLookup.Build();

            ThingToSectorId = new int[map.Things.Count];

            foreach (var thingIndex in Enumerable.Range(0, map.Things.Count))
            {
                ThingToSectorId[thingIndex] = FindSurroundingSector(map.Things[thingIndex].GetPosition());
            }
        }
コード例 #6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="g"></param>
        /// <returns></returns>
        public static STRtree <FacetSequence> BuildSTRtree(Geometry g)
// ReSharper restore InconsistentNaming
        {
            var tree     = new STRtree <FacetSequence>(STRtreeNodeCapacity);
            var sections = ComputeFacetSequences(g);

            foreach (var section in sections)
            {
                tree.Insert(section.Envelope, section);
            }
            tree.Build();
            return(tree);
        }
コード例 #7
0
        public void LoadMap()
        {
            using (var wad = WadReader.Read("freedoom2-udmf.wad"))
            {
                _map = new MapGeometry(MapData.LoadFrom(wad.GetTextmapStream("MAP28")));
            }

            _sectorBounds = new STRtree <int>();
            for (int sectorId = 0; sectorId < _map.Sectors.Length; sectorId++)
            {
                _sectorBounds.Insert(_map.GetSectorMinimumBoundingRectangle(sectorId), sectorId);
            }
            _sectorBounds.Build();
        }
コード例 #8
0
        //public static ISpatialIndex<IFeature> spatialIndex(IFeatureSet set)
        //{
        //    var tree = new STRtree<IFeature>(set.NumRows());

        //    foreach (var m in set.Features)
        //    {
        //        tree.Insert(m.Geometry.EnvelopeInternal, m);
        //    }

        //    tree.Build();
        //    return tree;
        //}

        public static ISpatialIndex <IFeature> spatialIndex(params IFeatureSet[] set)
        {
            var tree = new STRtree <IFeature>();

            foreach (var fs in set)
            {
                foreach (var m in fs.Features)
                {
                    tree.Insert(m.Geometry.EnvelopeInternal, m);
                }
            }

            tree.Build();
            return(tree);
        }
コード例 #9
0
        public override void StartRun(int size)
        {
            Console.WriteLine($"----- Tree size: {size}");

            _index = new STRtree <object>(NODE_SIZE);
            int side = (int)Math.Sqrt(size);

            LoadGrid(side, _index);

            var sw = new Stopwatch();

            sw.Start();
            _index.Build();
            sw.Stop();
            Console.WriteLine($"Build time = {sw.ElapsedMilliseconds}ms");
        }
コード例 #10
0
        /// <summary>
        /// Create a geographical view of the network
        /// </summary>
        /// <param name="topology"></param>
        public NetworkGeography(NetworkTopology topology)
        {
            foreach (var device in topology.Devices.Values)
            {
                // Add the device to the spatial index
                _geospatialElements.Insert(device.Envelope, device);

                // Add the edges to the tree (check on Id is due to edges being bi-directional, only want to add once)
                foreach (var adjacentDevice in device.AdjacentDevices.Where(ad => ad.Id > device.Id))
                {
                    var edge = new Edge(device, adjacentDevice);
                    _geospatialElements.Insert(edge.Envelope, edge);
                }
            }

            _geospatialElements.Build();
        }
コード例 #11
0
        public async Task <STRtree <SlimAccommodationData> > GetCountryAccommodationsTree(string countryCode,
                                                                                          string supplier, CancellationToken cancellationToken)
        {
            var countryAccommodations = new List <SlimAccommodationData>();
            var accommodations        = new List <SlimAccommodationData>();
            var skip = 0;

            do
            {
                accommodations = await _context.Accommodations.Where(ac => ac.CountryCode == countryCode &&
                                                                     !EF.Functions.JsonExists(ac.SupplierAccommodationCodes, supplier) &&
                                                                     ac.IsActive)
                                 .OrderBy(ac => ac.Id)
                                 .Skip(skip)
                                 .Take(_batchSize)
                                 .Select(ac => new SlimAccommodationData
                {
                    HtId    = ac.Id,
                    KeyData = ac.KeyData,
                    SupplierAccommodationCodes = ac.SupplierAccommodationCodes
                })
                                 .ToListAsync(cancellationToken);

                skip += _batchSize;
                countryAccommodations.AddRange(accommodations);
            } while (accommodations.Count > 0);

            if (!countryAccommodations.Any() || countryAccommodations.Count == 1)
            {
                return(new STRtree <SlimAccommodationData>());
            }

            var tree = new STRtree <SlimAccommodationData>(countryAccommodations.Count);

            foreach (var ac in countryAccommodations)
            {
                if (!ac.KeyData.Coordinates.IsEmpty() && ac.KeyData.Coordinates.IsValid())
                {
                    tree.Insert(new Point(ac.KeyData.Coordinates.Longitude,
                                          ac.KeyData.Coordinates.Latitude).EnvelopeInternal, ac);
                }
            }

            tree.Build();
            return(tree);
        }
コード例 #12
0
        public void TestStrIndex()
        {
            STRtree ndx = new STRtree();

            foreach (var v in CreateTestGeometries(1000, 0.0, 0.0, 3000.0, 3000.0))
            {
                ndx.Insert(v.EnvelopeInternal, v);
            }

            ndx.Build();
            IEnvelope queryExtents = new Envelope(100.0, 120.0, 100.0, 120.0);
            IList     matches      = ndx.Query(queryExtents);

            foreach (IGeometry list in matches)
            {
                Assert.IsTrue(list.EnvelopeInternal.Intersects(queryExtents), "a result from the index does not intersect the query bounds");
            }
        }
コード例 #13
0
        internal PointCollection RemoveNear(LineGraph roads, double minDis)
        {
            if (roads.nodes.Count == 0)
            {
                return(this);
            }
            var rtree = new STRtree <LineString>();

            foreach (var node in roads.nodes)
            {
                foreach (var next in node.nextConnections)
                {
                    Coordinate[] coords = new Coordinate[] { new Coordinate(node.pos.X, node.pos.Y), new Coordinate(next.pos.X, next.pos.Y) };
                    LineString   ls     = new LineString(coords);
                    rtree.Insert(ls.EnvelopeInternal, ls);
                }
            }
            rtree.Build();
            List <Vector2d> newpoints = new List <Vector2d>();

            foreach (var x in points)
            {
                bool isNear = false;
                foreach (var ls in rtree.Query(new Envelope(x.X - minDis, x.X + minDis, x.Y - minDis, x.Y + minDis)))
                {
                    if (ls.IsWithinDistance(new NetTopologySuite.Geometries.Point(x.X, x.Y), minDis))
                    {
                        isNear = true;
                        break;
                    }
                }
                if (!isNear)
                {
                    newpoints.Add(x);
                }
            }
            points = newpoints;
            return(this);
        }
コード例 #14
0
 public void FinishInserting()
 {
     _index.Build();
 }
コード例 #15
0
 // TODO: somehow merge this with DoIntersections all in one pass elegantly
 // this method is called before all of these maps get added together
 // it will detect and remove shapes found inside each other
 public static void FixLoops(List<SectorConstrainedOSMAreaGraph> addingMaps, BlobCollection blobs)
 {
     // TODO: I think we still need to exclude intersecting loops from our thing
     STRtree<LoopRef> rtree = new STRtree<LoopRef>();
     List<MainLoopRef> mainLoopRefs = new List<MainLoopRef>();
     HashSet<AreaNode> explored = new HashSet<AreaNode>();
     foreach (var addingMap in addingMaps)
     {
         foreach (var nodeList in addingMap.nodes.Values)
         {
             foreach (var node in nodeList)
             {
                 if (explored.Contains(node)) continue;
                 List<AreaNode> newLoop = new List<AreaNode>();
                 AreaNode curr = node;
                 while (true)
                 {
                     newLoop.Add(curr);
                     explored.Add(curr);
                     if (curr.next == node)
                     {
                         mainLoopRefs.Add(new MainLoopRef() { nodes = newLoop, graph = addingMap });
                         break;
                     }
                     curr = curr.next;
                 }
             }
         }
     }
     foreach (var loopRef in mainLoopRefs)
     {
         loopRef.isCW = GetArea(loopRef.nodes, blobs) < 0;
     }
     foreach (var loopRef in mainLoopRefs)
     {
         for (int i = 0; i < loopRef.nodes.Count; i++)
         {
             Vector2d pos1 = blobs.nodes[loopRef.nodes[i].id];
             Vector2d pos2 = blobs.nodes[loopRef.nodes[(i + 1) % loopRef.nodes.Count].id];
             var env = new Envelope(Math.Min(pos1.X, pos2.X), Math.Max(pos1.X, pos2.X), Math.Min(pos1.Y, pos2.Y), Math.Max(pos1.Y, pos2.Y));
             rtree.Insert(env, new LoopRef() { nodes = loopRef.nodes, graph = loopRef.graph, v1 = pos1, v2 = pos2, n1 = loopRef.nodes[i].id, n2 = loopRef.nodes[(i + 1) % loopRef.nodes.Count].id, isCW = loopRef.isCW });
         }
     }
     rtree.Build();
     List<MainLoopRef> remove = new List<MainLoopRef>();
     foreach (var loopRef in mainLoopRefs)
     {
         HashSet<long> nodesLookup = new HashSet<long>();
         foreach (var n in loopRef.nodes) nodesLookup.Add(n.id);
         var node = blobs.nodes[loopRef.nodes.First().id];
         Vector2d v1 = new Vector2d(node.X, 10);
         Vector2d v2 = new Vector2d(node.X, -10);
         var env = new Envelope(node.X, node.X, -10, 10);
         var intersections = rtree.Query(env);
         bool doRemove = false;
         foreach (var group in intersections.GroupBy(x => x.graph))
         {
             bool isOwnGraph = group.First().graph == loopRef.graph;
             if (!isOwnGraph && group.Any(x => x.nodes.Any(y => nodesLookup.Contains(y.id)))) continue; // let's ignore WHOLE GRAPHS that intersect with us (might need to revise this thinking)
             var lessfiltered = group.Where(x => x.v1.X != x.v2.X).ToList(); // skip vertical intersections
             var sorted = lessfiltered.Where(x => Math.Min(x.v1.X, x.v2.X) != node.X).ToList(); // on perfect-overlap of adjoining lines, this will count things appropriately
             sorted = sorted.OrderBy(x => -Intersect(v1, v2, x.v1, x.v2).Y).ToList(); // order from bottom to top
             sorted = sorted.Where(x => x.nodes != loopRef.nodes).ToList(); // ignore our own loop
             List<int> prevSwaps = new List<int>();
             for (int i = 1; i < sorted.Count; i++)
             {
                 // swap perfectly adjacent if necessary
                 if ((sorted[i - 1].v2 == sorted[i].v1 && sorted[i - 1].v2.X == node.X && sorted[i - 1].V1Y() < sorted[i].V2Y()) || (sorted[i - 1].v1 == sorted[i].v2 && sorted[i - 1].v1.X == node.X && sorted[i - 1].V2Y() < sorted[i].V1Y()))
                 {
                     var temp = sorted[i - 1];
                     sorted[i - 1] = sorted[i];
                     sorted[i] = temp;
                     prevSwaps.Add(i - 1);
                 }
             }
             var validateStack = new List<LoopRef>();
             var contains = new List<LoopRef>();
             if (sorted.Count % 2 != 0) throw new NotImplementedException(); // malformed shape
             for (int i = 0; i < sorted.Count; i++)
             {
                 bool outer = !sorted[i].isCW; // we will no longer be checking for outers immediately within outers, etc. because sometimes people mess up
                 bool opens = outer == sorted[i].IsLeftToRight();
                 if (opens)
                 {
                     validateStack.Add(sorted[i]);
                 }
                 else
                 {
                     if (validateStack.Last().nodes != sorted[i].nodes) throw new NotImplementedException(); // malformed shape
                     if (Intersect(v1, v2, validateStack.Last().v1, validateStack.Last().v2).Y > node.Y && Intersect(v1, v2, sorted[i].v1, sorted[i].v2).Y < node.Y)
                     {
                         contains.Add(sorted[i]); // add the one that's above it, sure
                     }
                     validateStack.RemoveAt(validateStack.Count - 1);
                 }
             }
             if (validateStack.Count > 0) throw new NotImplementedException(); // malformed shape
             // remove duplicates that overlap perfectly
             for (int i = sorted.Count - 1; i > 0; i--)
             {
                 if (sorted[i].ContainsNode(sorted[i - 1].v1) || sorted[i].ContainsNode(sorted[i - 1].v2))
                 {
                     Vector2d inCommon = sorted[i].ContainsNode(sorted[i - 1].v1) ? sorted[i - 1].v1 : sorted[i - 1].v2;
                     if (inCommon.X == node.X)
                     {
                         sorted.RemoveRange(i - 1, 2);
                         i -= 2;
                     }
                 }
             }
             if (contains.Count == 0 && isOwnGraph && loopRef.isCW) doRemove = true;
             if (contains.Count == 0) continue;
             var immediate = contains.First();
             if (isOwnGraph && immediate.isCW == loopRef.isCW) doRemove = true; // remove the outermost invalid loop
             if (!isOwnGraph && !immediate.isCW) doRemove = true; // remove anything immediately contained by an outer
         }
         if (doRemove) remove.Add(loopRef);
     }
     foreach (var r in remove)
     {
         foreach (var node in r.nodes)
         {
             r.graph.nodes[node.id].Remove(node);
             if (r.graph.nodes[node.id].Count == 0) r.graph.nodes.Remove(node.id);
         }
     }
 }
コード例 #16
0
 internal static void DoIntersections(BlobCollection blobs)
 {
     Dictionary<string, long> uids = new Dictionary<string, long>(); // finally decided I needed something to guarantee uniqueness like this TODO: can probably eliminate this
     long uidCounter = -1000;
     List<Way> ways = TempGetWays(blobs);
     ways.Add(blobs.borderWay);
     List<WayRef> wayRefs = new List<WayRef>();
     STRtree<WayRef> rtree = new STRtree<WayRef>();
     foreach (var way in ways)
     {
         for (int i = 1; i < way.refs.Count; i++)
         {
             WayRef wayRef = new WayRef() { wayRef = way, nodePos = i - 1 };
             wayRefs.Add(wayRef);
             Vector2d pos1 = blobs.nodes[way.refs[i - 1]];
             Vector2d pos2 = blobs.nodes[way.refs[i]];
             var env = new Envelope(Math.Min(pos1.X, pos2.X), Math.Max(pos1.X, pos2.X), Math.Min(pos1.Y, pos2.Y), Math.Max(pos1.Y, pos2.Y));
             rtree.Insert(env, wayRef);
         }
     }
     rtree.Build();
     Dictionary<Way, List<NewIntersection>> intersections = new Dictionary<Way, List<NewIntersection>>();
     foreach (var n1 in wayRefs)
     {
         Vector2d pos1 = blobs.nodes[n1.wayRef.refs[n1.nodePos]];
         Vector2d pos2 = blobs.nodes[n1.wayRef.refs[n1.nodePos + 1]];
         var env = new Envelope(Math.Min(pos1.X, pos2.X), Math.Max(pos1.X, pos2.X), Math.Min(pos1.Y, pos2.Y), Math.Max(pos1.Y, pos2.Y));
         foreach (var n2 in rtree.Query(env))
         {
             if (n1.GetHashCode() <= n2.GetHashCode()) continue; // as a way of preventing the same query twice
             long Aid = n1.wayRef.refs[n1.nodePos];
             long Bid = n1.wayRef.refs[n1.nodePos + 1];
             long Cid = n2.wayRef.refs[n2.nodePos];
             long Did = n2.wayRef.refs[n2.nodePos + 1];
             Vector2d A = blobs.nodes[Aid];
             Vector2d B = blobs.nodes[Bid];
             Vector2d C = blobs.nodes[Cid];
             Vector2d D = blobs.nodes[Did];
             if (Math.Min(A.X, B.X) > Math.Max(C.X, D.X)) continue;
             if (Math.Max(A.X, B.X) < Math.Min(C.X, D.X)) continue;
             if (Math.Min(A.Y, B.Y) > Math.Max(C.Y, D.Y)) continue;
             if (Math.Max(A.Y, B.Y) < Math.Min(C.Y, D.Y)) continue;
             string intersectionKey = Aid + "," + Cid;
             // TODO: we're going to treat -1 as always matching for now
             bool ACSame = Aid == Cid;
             bool ADSame = Aid == Did;
             bool BCSame = Bid == Cid;
             bool BDSame = Bid == Did;
             // a subset of possible tiny angles that can cause rounding errors
             // only thing that changes between these is the condition, line direction, and the newpoint id
             // TODO: is this really what fixed the nonsense at 240202043? the angleDiff was only 0.009, which seems too big to cause an issue
             bool someCollinear = false;
             bool isBorderIntersection = Aid < 0 || Bid < 0 || Cid < 0 || Did < 0;
             someCollinear |= CheckCollinear(Aid, n2.nodePos, n2.nodePos + 1, n2, intersections, blobs, true, isBorderIntersection);
             someCollinear |= CheckCollinear(Bid, n2.nodePos, n2.nodePos + 1, n2, intersections, blobs, true, isBorderIntersection);
             someCollinear |= CheckCollinear(Cid, n1.nodePos, n1.nodePos + 1, n1, intersections, blobs, true, isBorderIntersection);
             someCollinear |= CheckCollinear(Did, n1.nodePos, n1.nodePos + 1, n1, intersections, blobs, true, isBorderIntersection);
             if (!ACSame && !ADSame && !BCSame && !BDSame) // proper intersection
             {
                 if (someCollinear)
                 {
                     if (n1.wayRef.id == n2.wayRef.id)
                     {
                         n1.wayRef.selfIntersects = true; // mark for destruction, probably
                     }
                 }
                 else
                 {
                     // let's sort these lines to guarantee duplicates by value
                     long[] ns = new long[] { Aid, Bid, Cid, Did };
                     if (ns[0] >= ns[2]) ns = ns.Reverse().ToArray();
                     if (ns[0] >= ns[1])
                     {
                         var t = ns[1];
                         ns[1] = ns[0];
                         ns[0] = t;
                     }
                     if (ns[2] >= ns[3])
                     {
                         var t = ns[3];
                         ns[3] = ns[2];
                         ns[2] = t;
                     }
                     Vector2d intersection = Intersect(blobs.nodes[ns[0]], blobs.nodes[ns[1]], blobs.nodes[ns[2]], blobs.nodes[ns[3]]);
                     if (intersection != null)
                     {
                         long intersectionID;
                         if (uids.ContainsKey(intersectionKey))
                         {
                             intersectionID = uids[intersectionKey];
                         }
                         else
                         {
                             intersectionID = uidCounter--;
                             uids[intersectionKey] = intersectionID;
                         }
                         NewIntersection newNode1 = new NewIntersection() { nodeID = intersectionID, wayRef = n1.wayRef, nodePos = n1.nodePos + 1 };
                         NewIntersection newNode2 = new NewIntersection() { nodeID = intersectionID, wayRef = n2.wayRef, nodePos = n2.nodePos + 1 };
                         blobs.nodes[intersectionID] = intersection;
                         if (!intersections.ContainsKey(n1.wayRef)) intersections.Add(n1.wayRef, new List<NewIntersection>());
                         intersections[n1.wayRef].Add(newNode1);
                         if (!intersections.ContainsKey(n2.wayRef)) intersections.Add(n2.wayRef, new List<NewIntersection>());
                         intersections[n2.wayRef].Add(newNode2);
                         if (n1.wayRef.id == n2.wayRef.id)
                         {
                             n1.wayRef.selfIntersects = true; // mark for destruction, probably
                         }
                     }
                 }
             }
         }
     }
     List<long> wayids = new List<long>();
     foreach (var pair in intersections)
     {
         if (wayids.Contains(pair.Key.id)) throw new NotImplementedException();
         wayids.Add(pair.Key.id);
     }
     foreach (var pair in intersections)
     {
         foreach (var intersection in pair.Value)
         {
             intersection.sortRank = Sorter(intersection, blobs);
         }
     }
     foreach (var pair in intersections)
     {
         var sorted = pair.Value.OrderBy(x => x.sortRank).ToList();
         // get rid of duplicates
         for (int i = sorted.Count - 1; i > 0; i--)
         {
             if (sorted[i].nodeID == sorted[i - 1].nodeID) sorted.RemoveAt(i);
         }
         // now insert them
         for (int i = sorted.Count - 1; i >= 0; i--)
         {
             pair.Key.refs.Insert(sorted[i].nodePos, sorted[i].nodeID);
         }
     }
     RemoveDuplicates(blobs); // remove duplicates at the end to also remove duplicate intersections
 }