// we'll just apply this to multipolygons for now, skipping relations like 313091 private bool IsValid(SuperWayCollection wayCollection) { foreach (var superWay in wayCollection.linkedWays) { if (!IsValid(superWay)) { return(false); } if (sector.ContainsCoord(nodes[superWay.First().refs.First()])) { return(false); } if (sector.ContainsCoord(nodes[superWay.Last().refs.Last()])) { return(false); } } foreach (var superLoop in wayCollection.loopedWays) { if (!IsValid(superLoop)) { return(false); } } return(true); }
internal SectorConstrainedOSMAreaGraph GetCoastAreaMap(string key, string value) { // remember: "If you regard this as tracing around an area of land, then the coastline way should be running counterclockwise." // gather ways with matching starts/ends to form a super-way, coast ways should always run the same direction, so this becomes easier SuperWayCollection superWays = GenerateSuperWayCollection(EnumerateWays().Where(x => x.keyValues.ContainsKey(key) && x.keyValues[key] == value), false); SectorConstrainedOSMAreaGraph map = DoMultipolygon(superWays); if (OSMMetaFinal.IsPixelLand(sector) || borderWay.refs.Count > 5) // just return a big ol' square { SectorConstrainedOSMAreaGraph temp = new SectorConstrainedOSMAreaGraph(); for (int i = 1; i < borderWay.refs.Count; i++) { if (!temp.nodes.ContainsKey(borderWay.refs[i])) { temp.nodes[borderWay.refs[i]] = new List <AreaNode>(); temp.nodes[borderWay.refs[i]].Add(new AreaNode() { id = borderWay.refs[i] }); } } for (int i = 1; i < borderWay.refs.Count; i++) { AreaNode prev = temp.nodes[borderWay.refs[i - 1]].Single(); AreaNode next = temp.nodes[borderWay.refs[i]].Single(); if (prev.id != next.id) { prev.next = next; next.prev = prev; } } map = map.Intersect(temp, this); } BlobsIntersector.FixLoops(new List <SectorConstrainedOSMAreaGraph>() { map }, this); if (Constants.DEBUG_MODE) { map.CheckValid(); } return(map); }
private SectorConstrainedOSMAreaGraph DoMultipolygon(SuperWayCollection superWays) { SectorConstrainedOSMAreaGraph map = new SectorConstrainedOSMAreaGraph(); foreach (var superWay in superWays.linkedWays) // we expect these to always start and end outside the sector { AddConstrainedPaths(map, superWay); } map.CloseLines(this); map.RemoveDuplicateLines(); if (Constants.DEBUG_MODE) { map.CheckValid(); } foreach (var superLoop in superWays.loopedWays) { double wayArea = GetArea(superLoop); if (Math.Abs(wayArea) < SMALLEST_ALLOWED_AREA) { continue; // ignore zero-area ways since it really messes with the tesselator (ex: way 43624681) TODO: maybe check for absolute zero via node duplication? } bool isCW = wayArea < 0; var temp = new SectorConstrainedOSMAreaGraph(); bool untouchedLoop = CheckIfUntouchedAndSpin(superLoop); if (untouchedLoop) { AddUntouchedLoop(temp, superLoop); } else { AddConstrainedPaths(temp, superLoop); } temp.CloseLines(this); temp.RemoveDuplicateLines(); if (Constants.DEBUG_MODE) { temp.CheckValid(); } map.Add(temp, this); } return(map); }
// TODO: temporary states are passed around with incorrect metadata on the ways at times (ref reversing and fake ways), ignore this for now since the final product lacks these issues private SuperWayCollection GenerateSuperWayCollection(IEnumerable <Way> ways, bool ignoreDirection) { SuperWayCollection collection = new SuperWayCollection(); Dictionary <long, List <Way> > startsWith = new Dictionary <long, List <Way> >(); Dictionary <long, List <Way> > endsWith = new Dictionary <long, List <Way> >(); // first, we construct our linkedWays and loopedWays foreach (var ourWay in ways) { long ourFirstNode = ourWay.refs.First(); long ourLastNode = ourWay.refs.Last(); if (ignoreDirection) { // force existing superWays to align with ourWay if (endsWith.ContainsKey(ourLastNode) && startsWith.ContainsKey(ourFirstNode) && endsWith[ourLastNode] == startsWith[ourFirstNode]) // actually very common { JustFlipIt(endsWith[ourLastNode], startsWith, endsWith); } else { if (endsWith.ContainsKey(ourLastNode)) { JustFlipIt(endsWith[ourLastNode], startsWith, endsWith); } if (startsWith.ContainsKey(ourFirstNode)) { JustFlipIt(startsWith[ourFirstNode], startsWith, endsWith); } } } if (endsWith.ContainsKey(ourFirstNode) && startsWith.ContainsKey(ourLastNode)) // first, try to insert between A & B { var lineA = endsWith[ourFirstNode]; var lineB = startsWith[ourLastNode]; var lineBLastNode = lineB.Last().refs.Last(); if (lineA == lineB) // we've got a closed loop here { lineA.Add(ourWay); collection.loopedWays.Add(lineA); endsWith.Remove(ourFirstNode); startsWith.Remove(ourLastNode); } else { lineA.Add(ourWay); lineA.AddRange(lineB); endsWith[lineBLastNode] = lineA; endsWith.Remove(ourFirstNode); startsWith.Remove(ourLastNode); } } else if (endsWith.ContainsKey(ourFirstNode)) // now, try to append it to something { var lineA = endsWith[ourFirstNode]; lineA.Add(ourWay); endsWith[ourLastNode] = lineA; endsWith.Remove(ourFirstNode); } else if (startsWith.ContainsKey(ourLastNode)) // now, try to prepend it to something { var lineB = startsWith[ourLastNode]; lineB.Insert(0, ourWay); startsWith[ourFirstNode] = lineB; startsWith.Remove(ourLastNode); } else // it's completely new and disconnected { List <Way> us = new List <Way>() { ourWay }; if (ourFirstNode == ourLastNode) { collection.loopedWays.Add(us); } else { startsWith[ourFirstNode] = us; endsWith[ourLastNode] = us; } } } collection.linkedWays = startsWith.Values.ToList(); return(collection); }
internal SectorConstrainedOSMAreaGraph GetAreaMap(string key, string value) { var simpleWays = EnumerateWays().Where(x => x.keyValues.ContainsKey(key) && x.keyValues[key] == value); // we expect all of these to be closed loops // copy this dang multipolygon way id gathering logic Dictionary <long, Way> wayLookup = new Dictionary <long, Way>(); foreach (var way in EnumerateWays()) { wayLookup[way.id] = way; } List <List <long> > inners = new List <List <long> >(); List <List <long> > outers = new List <List <long> >(); List <long> relationIds = new List <long>(); HashSet <long> otherInnerOuters = new HashSet <long>(); foreach (var blob in blobs) { if (blob.type != "OSMData") { continue; } int typeIndex = blob.pBlock.stringtable.vals.IndexOf("type"); int multipolygonIndex = blob.pBlock.stringtable.vals.IndexOf("multipolygon"); int outerIndex = blob.pBlock.stringtable.vals.IndexOf("outer"); int innerIndex = blob.pBlock.stringtable.vals.IndexOf("inner"); int keyIndex = blob.pBlock.stringtable.vals.IndexOf(key); int valueIndex = blob.pBlock.stringtable.vals.IndexOf(value); if (new[] { typeIndex, multipolygonIndex, outerIndex, innerIndex, keyIndex, valueIndex }.Contains(-1)) { continue; } foreach (var pGroup in blob.pBlock.primitivegroup) { foreach (var relation in pGroup.relations) { bool isKeyValue = false; bool isTypeMultipolygon = false; for (int i = 0; i < relation.keys.Count; i++) { if (relation.keys[i] == keyIndex && relation.vals[i] == valueIndex) { isKeyValue = true; } if (relation.keys[i] == typeIndex && relation.vals[i] == multipolygonIndex) { isTypeMultipolygon = true; } } if (isTypeMultipolygon) { if (isKeyValue) { List <long> innerWayIds = new List <long>(); List <long> outerWayIds = new List <long>(); for (int i = 0; i < relation.roles_sid.Count; i++) { // just outer for now if (relation.types[i] == 1) { if (relation.roles_sid[i] == 0 && innerIndex != 0 && outerIndex != 0) { // some ways are in a relation without any inner/outer tag // ex: 359181377 in relation 304768 outerWayIds.Add(relation.memids[i]); } else { if (relation.roles_sid[i] == innerIndex) { innerWayIds.Add(relation.memids[i]); } if (relation.roles_sid[i] == outerIndex) { outerWayIds.Add(relation.memids[i]); } } } } inners.Add(innerWayIds); outers.Add(outerWayIds); relationIds.Add(relation.id); } else { for (int i = 0; i < relation.roles_sid.Count; i++) { // just outer for now if (relation.types[i] == 1) { otherInnerOuters.Add(relation.memids[i]); } } } } } } } HashSet <long> innersAndOuters = new HashSet <long>(); foreach (var innerList in inners) { foreach (var inner in innerList) { innersAndOuters.Add(inner); } } foreach (var outerList in outers) { foreach (var outer in outerList) { innersAndOuters.Add(outer); } } // end gathering logic // add each simple way, flipping them where necessary List <SectorConstrainedOSMAreaGraph> addingMaps = new List <SectorConstrainedOSMAreaGraph>(); foreach (var way in simpleWays) { if (innersAndOuters.Contains(way.id)) { continue; // sometimes lake multipolygons also tag several pieces - at best this is redundant, and at worst causes errors } if (way.refs.Count < 3) { continue; // we -usually- only ever see lines with multipolygons, but I found a weird way like 43435045 } if (way.selfIntersects) { continue; // just ignore ways like 43410874 to keep you sane } SectorConstrainedOSMAreaGraph simpleMap = new SectorConstrainedOSMAreaGraph(); var superLoop = new List <Way>() { way }; if (way.refs.Last() != way.refs.First()) { if (otherInnerOuters.Contains(way.id)) { continue; // unsure of how else to ignore bad ways like 43815149 } way.refs.Add(way.refs.First()); // some folks forget to close a simple way, or perhaps the mistake is tagging subcomponents of a relation } if (!IsValid(superLoop)) { continue; // ignore relations like 512080985 which contain duplicate nodes } double wayArea = GetArea(superLoop); if (Math.Abs(wayArea) < SMALLEST_ALLOWED_AREA) { continue; // ignore zero-area ways since it really messes with the tesselator (ex: way 43624681) TODO: maybe check for absolute zero via node duplication? } bool isCW = wayArea < 0; if (isCW) { way.refs.Reverse(); // the simple polygons are always "outers" } bool untouchedLoop = CheckIfUntouchedAndSpin(superLoop); if (untouchedLoop) { AddUntouchedLoop(simpleMap, superLoop); } else { AddConstrainedPaths(simpleMap, superLoop); simpleMap.CloseLines(this); if (Constants.DEBUG_MODE) { simpleMap.CheckValid(); } } simpleMap.RemoveDuplicateLines(); if (Constants.DEBUG_MODE) { simpleMap.CheckValid(); } addingMaps.Add(simpleMap); } // construct each multipolygon to add separately for (int i = 0; i < inners.Count; i++) // foreach multipolygon, basically { if (OSMMetaFinal.GLOBAL_FINAL.badRelations.Contains(relationIds[i])) { continue; } // TODO: with islands inside of ponds inside of islands inside of ponds, etc. we wouldn't expect this to work // however, we're taking advantage of the fact that Add/Subtract doesn't check for that for now (until Finalize) SuperWayCollection superInnerWays = GenerateSuperWayCollection(inners[i].Where(x => wayLookup.ContainsKey(x)).Select(x => Copy(wayLookup[x])), true); SuperWayCollection superOuterWays = GenerateSuperWayCollection(outers[i].Where(x => wayLookup.ContainsKey(x)).Select(x => Copy(wayLookup[x])), true); superInnerWays.loopedWays = superInnerWays.loopedWays.Where(x => Math.Abs(GetArea(x)) >= SMALLEST_ALLOWED_AREA).ToList(); // ignore zero-area ways since it really messes with the tesselator (ex: way 43624681) TODO: maybe check for absolute zero via node duplication? superOuterWays.loopedWays = superOuterWays.loopedWays.Where(x => Math.Abs(GetArea(x)) >= SMALLEST_ALLOWED_AREA).ToList(); // ignore zero-area ways since it really messes with the tesselator (ex: way 43624681) TODO: maybe check for absolute zero via node duplication? if (!IsValid(superInnerWays)) { continue; } if (!IsValid(superOuterWays)) { continue; } OrientSuperWays(superInnerWays, superOuterWays, gridPointInfo.relations.Contains(relationIds[i])); SectorConstrainedOSMAreaGraph innerMap = DoMultipolygon(superInnerWays); SectorConstrainedOSMAreaGraph outerMap = DoMultipolygon(superOuterWays); if (Constants.DEBUG_MODE) { innerMap.CheckValid(); } if (Constants.DEBUG_MODE) { outerMap.CheckValid(); } SectorConstrainedOSMAreaGraph multiPolygon = outerMap.Subtract(innerMap, this); if (Constants.DEBUG_MODE) { multiPolygon.CheckValid(); } addingMaps.Add(multiPolygon); } SectorConstrainedOSMAreaGraph map = new SectorConstrainedOSMAreaGraph(); BlobsIntersector.FixLoops(addingMaps, this); foreach (var addingMap in addingMaps) { map.Add(addingMap, this); } if (Constants.DEBUG_MODE) { map.CheckValid(); } return(map); }
private void OrientSuperWays(SuperWayCollection superInnerWays, SuperWayCollection superOuterWays, bool topLeftIsInside) { List <List <Way> > allLinkedWays = new List <List <Way> >(); allLinkedWays.AddRange(superInnerWays.linkedWays); allLinkedWays.AddRange(superOuterWays.linkedWays); List <List <Way> > allLoopedWays = new List <List <Way> >(); allLoopedWays.AddRange(superInnerWays.loopedWays); allLoopedWays.AddRange(superOuterWays.loopedWays); // assume all loops dont touch the edge for now (they can be corrected later) foreach (var superLoop in allLoopedWays) { SectorConstrainedOSMAreaGraph temp = new SectorConstrainedOSMAreaGraph(); bool isCW = ApproximateCW(superLoop); if (isCW) // TODO: why did I have this has the opposite logic before?? { // force to be an "outer" superLoop.Reverse(); foreach (var way in superLoop) { way.refs.Reverse(); } } } // now do the -real- orienting List <SuperWayIntersection> intersections = new List <SuperWayIntersection>(); foreach (var superWay in superOuterWays.linkedWays) // we expect these to always start and end outside the sector { AddIntersections(intersections, superWay, false); } foreach (var superLoop in superOuterWays.loopedWays) { bool untouchedLoop = CheckIfUntouchedAndSpin(superLoop); if (!untouchedLoop) { AddIntersections(intersections, superLoop, false); } } if (intersections.Count == 0 && topLeftIsInside) { // we are inside some quite large relation - let's add the big ol' box so we can subtract from it superOuterWays.loopedWays.Add(new List <Way>() { borderWay }); } foreach (var superWay in superInnerWays.linkedWays) // we expect these to always start and end outside the sector { AddIntersections(intersections, superWay, true); } foreach (var superLoop in superInnerWays.loopedWays) { bool untouchedLoop = CheckIfUntouchedAndSpin(superLoop); if (!untouchedLoop) { AddIntersections(intersections, superLoop, true); } } if (intersections.Count % 2 != 0) { throw new NotImplementedException(); } intersections = intersections.OrderBy(x => (Math.Atan2(x.intersection.Y - 0.5, x.intersection.X - 0.5) + 8 * Math.PI - (-Math.PI * 3 / 4)) % (2 * Math.PI)).ToList(); // clockwise order starting at top-left // look for duplicates that are near each other, had this issue with relation 534928, if they're close enough we'll just ignore them for now - TODO: this may cause issues later HashSet <int> ignore = new HashSet <int>(); for (int i = 0; i < intersections.Count; i++) { int j = (i + 1) % intersections.Count; if ((intersections[i].intersection - intersections[j].intersection).Length() < 0.0000001) { ignore.Add(i); ignore.Add(j); } } HashSet <List <Way> > correctDirectionHash = new HashSet <List <Way> >(); HashSet <List <Way> > incorrectDirectionHash = new HashSet <List <Way> >(); for (int i = 0; i < intersections.Count; i++) { if (ignore.Contains(i)) { continue; } bool correctDirection = intersections[i].leaving == (topLeftIsInside ^ (i % 2 == 1)); if (intersections[i].inner) { correctDirection = !correctDirection; // flip all of the inners back, since we're about to subtract them } if (correctDirection) { correctDirectionHash.Add(intersections[i].superWay); if (incorrectDirectionHash.Contains(intersections[i].superWay)) { throw new NotImplementedException(); // disagreement } } else { incorrectDirectionHash.Add(intersections[i].superWay); if (correctDirectionHash.Contains(intersections[i].superWay)) { throw new NotImplementedException(); // disagreement } } } foreach (var superWay in incorrectDirectionHash) { superWay.Reverse(); foreach (var way in superWay) { way.refs.Reverse(); } } }