Ticket #17819: 17819-multi-thread-v2.patch
| File 17819-multi-thread-v2.patch, 15.2 KB (added by , 7 years ago) |
|---|
-
src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java
16 16 import java.util.Map; 17 17 import java.util.Map.Entry; 18 18 import java.util.Set; 19 import java.util.concurrent.ForkJoinPool; 20 import java.util.concurrent.ForkJoinTask; 21 import java.util.concurrent.RecursiveTask; 22 import java.util.function.Supplier; 19 23 20 24 import org.openstreetmap.josm.command.ChangeCommand; 21 25 import org.openstreetmap.josm.command.Command; … … 38 42 import org.openstreetmap.josm.tools.Geometry; 39 43 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 40 44 import org.openstreetmap.josm.tools.Logging; 45 import org.openstreetmap.josm.tools.Pair; 46 import org.openstreetmap.josm.tools.Utils; 41 47 42 48 /** 43 49 * Checks if multipolygons are valid … … 44 50 * @since 3669 45 51 */ 46 52 public class MultipolygonTest extends Test { 53 private static final ForkJoinPool THREAD_POOL = newForkJoinPool(); 47 54 55 private static ForkJoinPool newForkJoinPool() { 56 try { 57 return Utils.newForkJoinPool( 58 "multipolygon_creation.numberOfThreads", "multipolygon-test-%d", Thread.NORM_PRIORITY); 59 } catch (SecurityException e) { 60 Logging.log(Logging.LEVEL_ERROR, "Unable to create new ForkJoinPool", e); 61 return null; 62 } 63 } 64 48 65 /** Non-Way in multipolygon */ 49 66 public static final int WRONG_MEMBER_TYPE = 1601; 50 67 /** No useful role for multipolygon member */ … … 463 480 * @param sharedNodes all nodes shared by multiple ways of this multipolygon 464 481 */ 465 482 private void checkOrSetRoles(Relation r, List<PolyData> allPolygons, Map<Long, RelationMember> wayMap, Set<Node> sharedNodes) { 466 PolygonLevelFinder levelFinder = new PolygonLevelFinder(sharedNodes); 467 List<PolygonLevel> list = levelFinder.findOuterWays(allPolygons); 483 List<PolygonLevel> list = findOuterWaysMultiThread(allPolygons, sharedNodes); 468 484 if (list == null || list.isEmpty()) { 469 485 return; 470 486 } … … 800 816 } 801 817 802 818 /** 819 * Collects outer way and corresponding inner ways from all rings. 820 * @param rings the polygon rings 821 * @param sharedNodes all nodes shared by multiple ways of this multipolygon 822 * @return list of nesting levels 823 */ 824 private static List<PolygonLevel> findOuterWaysMultiThread(List<PolyData> rings, Set<Node> sharedNodes) { 825 final IntersectionMatrix cache = new IntersectionMatrix(rings); 826 PolygonLevelFinder worker = new PolygonLevelFinder(cache, sharedNodes, rings, 0, rings.size(), 827 new ArrayList<PolygonLevel>(), 128); 828 if (THREAD_POOL != null) { 829 return THREAD_POOL.invoke(worker); 830 } else { 831 return worker.computeDirectly(); 832 } 833 } 834 835 /** 836 * Helper class to avoid unneeded costly intersection calculations. 837 * If the intersection between polygons a and b was calculated we also know 838 * the result of intersection between b and a. The lookup in the hash tables is 839 * much faster than the intersection calculation. 840 */ 841 private static class IntersectionMatrix { 842 private final Map<Pair<PolyData, PolyData>, PolygonIntersection> results; 843 844 IntersectionMatrix(Collection<PolyData> polygons) { 845 results = new HashMap<>(Utils.hashMapInitialCapacity(polygons.size() * polygons.size())); 846 } 847 848 /** 849 * Compute the reverse result of the intersection test done by {@code Geometry.polygonIntersection(Area a1, Area a2)} 850 * 851 * @param intersection the intersection result for polygons a1 and a2 (in that order) 852 * @return the intersection result for a2 and a1 853 */ 854 private static PolygonIntersection getReverseIntersectionResult(PolygonIntersection intersection) { 855 switch (intersection) { 856 case FIRST_INSIDE_SECOND: 857 return PolygonIntersection.SECOND_INSIDE_FIRST; 858 case SECOND_INSIDE_FIRST: 859 return PolygonIntersection.FIRST_INSIDE_SECOND; 860 default: 861 return intersection; 862 } 863 } 864 865 /** 866 * Returns the precomputed intersection between two polygons if known. Otherwise perform {@code computation}. 867 * 868 * @param a1 first polygon 869 * @param a2 second polygon 870 * @param computation the computation to perform when intersection is unknown 871 * @return the intersection between two polygons 872 * @see Map#computeIfAbsent 873 */ 874 PolygonIntersection computeIfAbsent(PolyData a1, PolyData a2, Supplier<PolygonIntersection> computation) { 875 PolygonIntersection intersection = results.get(Pair.create(a1, a2)); 876 if (intersection == null) { 877 intersection = computation.get(); 878 synchronized (results) { 879 results.put(Pair.create(a1, a2), intersection); 880 results.put(Pair.create(a2, a1), getReverseIntersectionResult(intersection)); 881 } 882 } 883 return intersection; 884 } 885 886 } 887 888 /** 803 889 * Find nesting levels of polygons. Logic taken from class MultipolygonBuilder, uses different structures. 804 890 */ 805 private static class PolygonLevelFinder { 806 private final Set<Node> sharedNodes; 891 private static class PolygonLevelFinder extends RecursiveTask<List<PolygonLevel>> { 892 private final transient IntersectionMatrix cache; 893 private final transient Set<Node> sharedNodes; 894 private final transient List<PolyData> input; 895 private final int from; 896 private final int to; 897 private final transient List<PolygonLevel> output; 898 private final int directExecutionTaskSize; 807 899 808 PolygonLevelFinder(Set<Node> sharedNodes) { 900 private static final long serialVersionUID = 0; 901 902 PolygonLevelFinder(IntersectionMatrix cache, Set<Node> sharedNodes, List<PolyData> input, int from, int to, List<PolygonLevel> output, 903 int directExecutionTaskSize) { 904 this.cache = cache; 809 905 this.sharedNodes = sharedNodes; 906 this.input = input; 907 this.from = from; 908 this.to = to; 909 this.output = output; 910 this.directExecutionTaskSize = directExecutionTaskSize; 810 911 } 811 912 812 List<PolygonLevel> findOuterWays(List<PolyData> allPolygons) {813 return findOuterWaysRecursive(0, allPolygons);814 }815 816 913 private List<PolygonLevel> findOuterWaysRecursive(int level, List<PolyData> polygons) { 817 914 final List<PolygonLevel> result = new ArrayList<>(); 818 915 … … 824 921 } 825 922 826 923 private void processOuterWay(int level, List<PolyData> polygons, List<PolygonLevel> result, PolyData pd) { 827 List<PolyData> inners = findInnerWaysCandidates(pd, polygons);924 Pair<Boolean, List<PolyData>> res = findInnerWaysForOuterCandidate(pd, polygons); 828 925 829 if ( inners != null) {926 if (res.a) { 830 927 //add new outer polygon 831 928 PolygonLevel pol = new PolygonLevel(pd, level); 832 929 833 930 //process inner ways 931 List<PolyData> inners = res.b; 834 932 if (!inners.isEmpty()) { 835 List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, inners); 836 result.addAll(innerList); 933 result.addAll(findOuterWaysRecursive(level + 1, inners)); 837 934 } 838 935 839 936 result.add(pol); … … 841 938 } 842 939 843 940 /** 844 * Check if polygon is an out-most ring , if so, collect the inners941 * Check if polygon is an out-most ring for all given polygons, if so, collect the inner rings. 845 942 * @param outerCandidate polygon which is checked 846 943 * @param polygons all polygons 847 * @return null if outerCandidate is inside any other polygon, else a list of inner polygons (which might be empty) 944 * @return pair, first value is true if outerCandidate is a real outer ring, 2nd value contains the list of inner rings 945 * which might be empty 848 946 */ 849 private List<PolyData> findInnerWaysCandidates(PolyData outerCandidate, List<PolyData> polygons) {947 private Pair<Boolean, List<PolyData>> findInnerWaysForOuterCandidate(PolyData outerCandidate, List<PolyData> polygons) { 850 948 List<PolyData> innerCandidates = new ArrayList<>(); 851 949 Boolean outerGood = Boolean.TRUE; 852 950 for (PolyData inner : polygons) { 853 if (inner == outerCandidate) { 854 continue; 951 if (inner != outerCandidate && outerCandidate.getBounds().intersects(inner.getBounds())) { 952 final PolygonIntersection intersection = cache.computeIfAbsent(inner, outerCandidate, 953 () -> getNesting(inner, outerCandidate)); 954 if (PolygonIntersection.FIRST_INSIDE_SECOND == intersection) { 955 innerCandidates.add(inner); 956 } else if (PolygonIntersection.SECOND_INSIDE_FIRST == intersection) { 957 outerGood = Boolean.FALSE; 958 break; 959 } 855 960 } 856 if (!outerCandidate.getBounds().intersects(inner.getBounds())) { 857 continue; 858 } 859 boolean useIntersectionTest = false; 860 Node unsharedOuterNode = null; 861 Node unsharedInnerNode = getNonIntersectingNode(outerCandidate, inner); 862 if (unsharedInnerNode != null) { 863 if (checkIfNodeIsInsidePolygon(unsharedInnerNode, outerCandidate)) { 864 innerCandidates.add(inner); 961 } 962 return new Pair<>(outerGood, innerCandidates); 963 } 964 965 private PolygonIntersection getNesting(PolyData innerCandidate, PolyData outerCandidate) { 966 boolean useIntersectionTest = false; 967 Node unsharedOuterNode = null; 968 Node unsharedInnerNode = getNonIntersectingNode(outerCandidate, innerCandidate); 969 if (unsharedInnerNode != null) { 970 if (checkIfNodeIsInsidePolygon(unsharedInnerNode, outerCandidate)) { 971 return PolygonIntersection.FIRST_INSIDE_SECOND; 972 } else { 973 // inner is not inside outerCandidate, check if it contains outerCandidate 974 unsharedOuterNode = getNonIntersectingNode(innerCandidate, outerCandidate); 975 if (unsharedOuterNode != null) { 976 if (checkIfNodeIsInsidePolygon(unsharedOuterNode, innerCandidate)) { 977 return PolygonIntersection.SECOND_INSIDE_FIRST; // outer is inside inner 978 } 865 979 } else { 866 // inner is not inside outerCandidate, check if it contains outerCandidate 867 unsharedOuterNode = getNonIntersectingNode(inner, outerCandidate); 868 if (unsharedOuterNode != null) { 869 if (checkIfNodeIsInsidePolygon(unsharedOuterNode, inner)) { 870 return null; // outer is inside inner 871 } 872 } else { 873 useIntersectionTest = true; 874 } 980 useIntersectionTest = true; 875 981 } 982 } 983 } else { 984 // all nodes of inner are also nodes of outerCandidate 985 unsharedOuterNode = getNonIntersectingNode(innerCandidate, outerCandidate); 986 if (unsharedOuterNode == null) { 987 return PolygonIntersection.CROSSING; // all nodes shared -> same ways, maybe different direction 876 988 } else { 877 // all nodes of inner are also nodes of outerCandidate 878 unsharedOuterNode = getNonIntersectingNode(inner, outerCandidate); 879 if (unsharedOuterNode == null) { 880 return null; // all nodes shared -> same ways, maybe different direction 989 if (checkIfNodeIsInsidePolygon(unsharedOuterNode, innerCandidate)) { 990 return null; // outer is inside inner 881 991 } else { 882 if (checkIfNodeIsInsidePolygon(unsharedOuterNode, inner)) { 883 return null; // outer is inside inner 884 } else { 885 useIntersectionTest = true; 886 } 992 useIntersectionTest = true; 887 993 } 888 994 } 889 if (useIntersectionTest) {890 PolygonIntersection res = Geometry.polygonIntersection(inner.getNodes(), outerCandidate.getNodes());891 if (res == PolygonIntersection.FIRST_INSIDE_SECOND)892 innerCandidates.add(inner);893 else if (res == PolygonIntersection.SECOND_INSIDE_FIRST)894 return null;895 }896 995 } 897 return innerCandidates; 996 if (useIntersectionTest) { 997 return Geometry.polygonIntersection(innerCandidate.getNodes(), outerCandidate.getNodes()); 998 } 999 return PolygonIntersection.OUTSIDE; 898 1000 } 899 1001 900 1002 /** … … 910 1012 } 911 1013 return null; 912 1014 } 1015 1016 @Override 1017 protected List<PolygonLevel> compute() { 1018 if (to - from <= directExecutionTaskSize) { 1019 return computeDirectly(); 1020 } else { 1021 final Collection<ForkJoinTask<List<PolygonLevel>>> tasks = new ArrayList<>(); 1022 for (int fromIndex = from; fromIndex < to; fromIndex += directExecutionTaskSize) { 1023 tasks.add(new PolygonLevelFinder(cache, sharedNodes, input, fromIndex, 1024 Math.min(fromIndex + directExecutionTaskSize, to), new ArrayList<PolygonLevel>(), 1025 directExecutionTaskSize)); 1026 } 1027 for (ForkJoinTask<List<PolygonLevel>> task : ForkJoinTask.invokeAll(tasks)) { 1028 List<PolygonLevel> res = task.join(); 1029 output.addAll(res); 1030 } 1031 return output; 1032 } 1033 } 1034 1035 List<PolygonLevel> computeDirectly() { 1036 for (int i = from; i < to; i++) { 1037 processOuterWay(0, input, output, input.get(i)); 1038 } 1039 return output; 1040 } 913 1041 } 914 1042 915 1043 /** 916 1044 * Create a multipolygon relation from the given ways and test it. 917 1045 * @param ways the collection of ways 918 * @return a pair of a {@link Multipolygon} instance and the relation.1046 * @return the relation, caller should call getErrors() to check if relation is a valid multipolygon 919 1047 * @since 15160 920 1048 */ 921 1049 public Relation makeFromWays(Collection<Way> ways) {
