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.INode;
011import org.openstreetmap.josm.data.osm.IPrimitive;
012import org.openstreetmap.josm.data.osm.IRelation;
013import org.openstreetmap.josm.data.osm.IWay;
014import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
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 PrimitiveVisitor {
035
036    private static final long serialVersionUID = 1L;
037
038    private final transient List<? extends IPrimitive> input;
039    private final transient List<StyleRecord> output;
040
041    private final transient ElemStyles styles;
042    private final int directExecutionTaskSize;
043    private final double circum;
044    private final NavigatableComponent nc;
045
046    private final boolean drawArea;
047    private final boolean drawMultipolygon;
048    private final boolean drawRestriction;
049
050    /**
051     * Constructs a new {@code ComputeStyleListWorker}.
052     * @param circum distance on the map in meters that 100 screen pixels represent
053     * @param nc navigable component
054     * @param input the primitives to process
055     * @param output the list of styles to which styles will be added
056     * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks
057     * @since 13810 (signature)
058     */
059    ComputeStyleListWorker(double circum, NavigatableComponent nc,
060            final List<? extends IPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize) {
061        this(circum, nc, input, output, directExecutionTaskSize, MapPaintStyles.getStyles());
062    }
063
064    /**
065     * Constructs a new {@code ComputeStyleListWorker}.
066     * @param circum distance on the map in meters that 100 screen pixels represent
067     * @param nc navigable component
068     * @param input the primitives to process
069     * @param output the list of styles to which styles will be added
070     * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks
071     * @param styles the {@link ElemStyles} instance used to generate primitive {@link StyleElement}s.
072     * @since 12964
073     * @since 13810 (signature)
074     */
075    ComputeStyleListWorker(double circum, NavigatableComponent nc,
076            final List<? extends IPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize,
077            ElemStyles styles) {
078        this.circum = circum;
079        this.nc = nc;
080        this.input = input;
081        this.output = output;
082        this.directExecutionTaskSize = directExecutionTaskSize;
083        this.styles = styles;
084        this.drawArea = circum <= Config.getPref().getInt("mappaint.fillareas", 10_000_000);
085        this.drawMultipolygon = drawArea && Config.getPref().getBoolean("mappaint.multipolygon", true);
086        this.drawRestriction = Config.getPref().getBoolean("mappaint.restriction", true);
087        this.styles.setDrawMultipolygon(drawMultipolygon);
088    }
089
090    @Override
091    protected List<StyleRecord> compute() {
092        if (input.size() <= directExecutionTaskSize) {
093            return computeDirectly();
094        } else {
095            final Collection<ForkJoinTask<List<StyleRecord>>> tasks = new ArrayList<>();
096            for (int fromIndex = 0; fromIndex < input.size(); fromIndex += directExecutionTaskSize) {
097                final int toIndex = Math.min(fromIndex + directExecutionTaskSize, input.size());
098                tasks.add(new ComputeStyleListWorker(circum, nc, input.subList(fromIndex, toIndex),
099                        new ArrayList<>(directExecutionTaskSize), directExecutionTaskSize, styles).fork());
100            }
101            for (ForkJoinTask<List<StyleRecord>> task : tasks) {
102                output.addAll(task.join());
103            }
104            return output;
105        }
106    }
107
108    /**
109     * Compute directly (without using fork/join) the style list. Only called for small input.
110     * @return list of computed style records
111     */
112    public List<StyleRecord> computeDirectly() {
113        MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
114        try {
115            for (final IPrimitive osm : input) {
116                acceptDrawable(osm);
117            }
118            return output;
119        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
120            throw BugReport.intercept(e).put("input-size", input.size()).put("output-size", output.size());
121        } finally {
122            MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
123        }
124    }
125
126    private void acceptDrawable(final IPrimitive osm) {
127        try {
128            if (osm.isDrawable()) {
129                osm.accept(this);
130            }
131        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
132            throw BugReport.intercept(e).put("osm", osm);
133        }
134    }
135
136    @Override
137    public void visit(INode n) {
138        add(n, StyledMapRenderer.computeFlags(n, false));
139    }
140
141    @Override
142    public void visit(IWay<?> w) {
143        add(w, StyledMapRenderer.computeFlags(w, true));
144    }
145
146    @Override
147    public void visit(IRelation<?> r) {
148        add(r, StyledMapRenderer.computeFlags(r, true));
149    }
150
151    /**
152     * Add new style records for the given node.
153     * @param osm node
154     * @param flags flags
155     * @since 13810 (signature)
156     */
157    public void add(INode osm, int flags) {
158        StyleElementList sl = styles.get(osm, circum, nc);
159        for (StyleElement s : sl) {
160            output.add(new StyleRecord(s, osm, flags));
161        }
162    }
163
164    /**
165     * Add new style records for the given way.
166     * @param osm way
167     * @param flags flags
168     * @since 13810 (signature)
169     */
170    public void add(IWay<?> osm, int flags) {
171        StyleElementList sl = styles.get(osm, circum, nc);
172        for (StyleElement s : sl) {
173            if ((drawArea && (flags & StyledMapRenderer.FLAG_DISABLED) == 0) || !(s instanceof AreaElement)) {
174                output.add(new StyleRecord(s, osm, flags));
175            }
176        }
177    }
178
179    /**
180     * Add new style records for the given relation.
181     * @param osm relation
182     * @param flags flags
183     * @since 13810 (signature)
184     */
185    public void add(IRelation<?> osm, int flags) {
186        StyleElementList sl = styles.get(osm, circum, nc);
187        for (StyleElement s : sl) {
188            if (drawAreaElement(flags, s) ||
189               (drawMultipolygon && drawArea && s instanceof TextElement) ||
190               (drawRestriction && s instanceof NodeElement)) {
191                output.add(new StyleRecord(s, osm, flags));
192            }
193        }
194    }
195
196    private boolean drawAreaElement(int flags, StyleElement s) {
197        return drawMultipolygon && drawArea && (s instanceof AreaElement || s instanceof AreaIconElement)
198                && (flags & StyledMapRenderer.FLAG_DISABLED) == 0;
199    }
200}