001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm.visitor.paint;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import java.util.concurrent.ForkJoinTask;
008import java.util.concurrent.RecursiveTask;
009
010import org.openstreetmap.josm.data.osm.Node;
011import org.openstreetmap.josm.data.osm.OsmPrimitive;
012import org.openstreetmap.josm.data.osm.Relation;
013import org.openstreetmap.josm.data.osm.Way;
014import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
015import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer.StyleRecord;
016import org.openstreetmap.josm.gui.NavigatableComponent;
017import org.openstreetmap.josm.gui.mappaint.ElemStyles;
018import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
019import org.openstreetmap.josm.gui.mappaint.StyleElementList;
020import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
021import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement;
022import org.openstreetmap.josm.gui.mappaint.styleelement.AreaIconElement;
023import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
024import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
025import org.openstreetmap.josm.gui.mappaint.styleelement.TextElement;
026import org.openstreetmap.josm.spi.preferences.Config;
027import org.openstreetmap.josm.tools.JosmRuntimeException;
028import org.openstreetmap.josm.tools.bugreport.BugReport;
029
030/**
031 * Helper to compute style list.
032 * @since 11914 (extracted from StyledMapRenderer)
033 */
034public class ComputeStyleListWorker extends RecursiveTask<List<StyleRecord>> implements OsmPrimitiveVisitor {
035    private final transient List<? extends OsmPrimitive> input;
036    private final transient List<StyleRecord> output;
037
038    private final transient ElemStyles styles;
039    private final int directExecutionTaskSize;
040    private final double circum;
041    private final NavigatableComponent nc;
042
043    private final boolean drawArea;
044    private final boolean drawMultipolygon;
045    private final boolean drawRestriction;
046
047    /**
048     * Constructs a new {@code ComputeStyleListWorker}.
049     * @param circum distance on the map in meters that 100 screen pixels represent
050     * @param nc navigatable component
051     * @param input the primitives to process
052     * @param output the list of styles to which styles will be added
053     * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks
054     */
055    ComputeStyleListWorker(double circum, NavigatableComponent nc,
056            final List<? extends OsmPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize) {
057        this(circum, nc, input, output, directExecutionTaskSize, MapPaintStyles.getStyles());
058    }
059
060    /**
061     * Constructs a new {@code ComputeStyleListWorker}.
062     * @param circum distance on the map in meters that 100 screen pixels represent
063     * @param nc navigatable component
064     * @param input the primitives to process
065     * @param output the list of styles to which styles will be added
066     * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks
067     * @param styles the {@link ElemStyles} instance used to generate primitive {@link StyleElement}s.
068     * @since 12964
069     */
070    ComputeStyleListWorker(double circum, NavigatableComponent nc,
071            final List<? extends OsmPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize,
072            ElemStyles styles) {
073        this.circum = circum;
074        this.nc = nc;
075        this.input = input;
076        this.output = output;
077        this.directExecutionTaskSize = directExecutionTaskSize;
078        this.styles = styles;
079        this.drawArea = circum <= Config.getPref().getInt("mappaint.fillareas", 10_000_000);
080        this.drawMultipolygon = drawArea && Config.getPref().getBoolean("mappaint.multipolygon", true);
081        this.drawRestriction = Config.getPref().getBoolean("mappaint.restriction", true);
082        this.styles.setDrawMultipolygon(drawMultipolygon);
083    }
084
085    @Override
086    protected List<StyleRecord> compute() {
087        if (input.size() <= directExecutionTaskSize) {
088            return computeDirectly();
089        } else {
090            final Collection<ForkJoinTask<List<StyleRecord>>> tasks = new ArrayList<>();
091            for (int fromIndex = 0; fromIndex < input.size(); fromIndex += directExecutionTaskSize) {
092                final int toIndex = Math.min(fromIndex + directExecutionTaskSize, input.size());
093                tasks.add(new ComputeStyleListWorker(circum, nc, input.subList(fromIndex, toIndex),
094                        new ArrayList<>(directExecutionTaskSize), directExecutionTaskSize, styles).fork());
095            }
096            for (ForkJoinTask<List<StyleRecord>> task : tasks) {
097                output.addAll(task.join());
098            }
099            return output;
100        }
101    }
102
103    /**
104     * Compute directly (without using fork/join) the style list. Only called for small input.
105     * @return list of computed style records
106     */
107    public List<StyleRecord> computeDirectly() {
108        MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
109        try {
110            for (final OsmPrimitive osm : input) {
111                acceptDrawable(osm);
112            }
113            return output;
114        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
115            throw BugReport.intercept(e).put("input-size", input.size()).put("output-size", output.size());
116        } finally {
117            MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
118        }
119    }
120
121    private void acceptDrawable(final OsmPrimitive osm) {
122        try {
123            if (osm.isDrawable()) {
124                osm.accept(this);
125            }
126        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
127            throw BugReport.intercept(e).put("osm", osm);
128        }
129    }
130
131    @Override
132    public void visit(Node n) {
133        add(n, StyledMapRenderer.computeFlags(n, false));
134    }
135
136    @Override
137    public void visit(Way w) {
138        add(w, StyledMapRenderer.computeFlags(w, true));
139    }
140
141    @Override
142    public void visit(Relation r) {
143        add(r, StyledMapRenderer.computeFlags(r, true));
144    }
145
146    /**
147     * Add new style records for the given node.
148     * @param osm node
149     * @param flags flags
150     */
151    public void add(Node osm, int flags) {
152        StyleElementList sl = styles.get(osm, circum, nc);
153        for (StyleElement s : sl) {
154            output.add(new StyleRecord(s, osm, flags));
155        }
156    }
157
158    /**
159     * Add new style records for the given way.
160     * @param osm way
161     * @param flags flags
162     */
163    public void add(Way osm, int flags) {
164        StyleElementList sl = styles.get(osm, circum, nc);
165        for (StyleElement s : sl) {
166            if ((drawArea && (flags & StyledMapRenderer.FLAG_DISABLED) == 0) || !(s instanceof AreaElement)) {
167                output.add(new StyleRecord(s, osm, flags));
168            }
169        }
170    }
171
172    /**
173     * Add new style records for the given relation.
174     * @param osm relation
175     * @param flags flags
176     */
177    public void add(Relation osm, int flags) {
178        StyleElementList sl = styles.get(osm, circum, nc);
179        for (StyleElement s : sl) {
180            if (drawAreaElement(flags, s) ||
181               (drawMultipolygon && drawArea && s instanceof TextElement) ||
182               (drawRestriction && s instanceof NodeElement)) {
183                output.add(new StyleRecord(s, osm, flags));
184            }
185        }
186    }
187
188    private boolean drawAreaElement(int flags, StyleElement s) {
189        return drawMultipolygon && drawArea && (s instanceof AreaElement || s instanceof AreaIconElement)
190                && (flags & StyledMapRenderer.FLAG_DISABLED) == 0;
191    }
192}