Changeset 9099 in josm


Ignore:
Timestamp:
2015-12-11T17:36:59+01:00 (3 years ago)
Author:
bastiK
Message:

mapcss partial fill: move threshold parameter ([9063]) into the mapcss style
(new property fill-extent-threshold)

  • add support for this parameter when area is unclosed
  • smaller extent for unclosed areas

(see #12104)

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPaintSettings.java

    r9082 r9099  
    4949    /** Preference: should only the data area outline be drawn */
    5050    private boolean outlineOnly;
    51     /** Preference: parameter to avoid partial fill on small area objects:
    52      * If more than a certain percentage of the total area would be filled by
    53      * partial fill, then fill this area completely (avoiding narrow gap in the
    54      * center) */
    55     private double partialFillThreshold;
    5651    /** Color Preference for selected objects */
    5752    private Color selectedColor;
     
    111106
    112107        outlineOnly = Main.pref.getBoolean("draw.data.area_outline_only", false);
    113         partialFillThreshold = Main.pref.getDouble("draw.area.partial_fill_threshold", 70);
    114108    }
    115109
     
    352346        return outlineOnly;
    353347    }
    354 
    355     /**
    356      * Returns the partial fill threshold.
    357      * This parameter is used to avoid partial fill on small area objects:
    358      * If more than a certain percentage of the total area would be filled by
    359      * partial fill, then fill this area completely (avoiding narrow gap in the
    360      * center)
    361      * @return the partial fill threshold
    362      */
    363     public double getPartialFillThreshold() {
    364         return partialFillThreshold;
    365     }
    366348}
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

    r9082 r9099  
    336336    private boolean showIcons;
    337337    private boolean isOutlineOnly;
    338     private double partialFillThreshold;
    339338
    340339    private Font orderFont;
     
    467466     * far to fill from the boundary towards the center of the area;
    468467     * if null, area will be filled completely
    469      * @param pfClip clipping area for partial fill
     468     * @param pfClip clipping area for partial fill (only needed for unclosed
     469     * polygons)
    470470     * @param disabled If this should be drawn with a special disabled style.
    471471     * @param text The text to write on the area.
     
    492492                    }
    493493                    g.clip(clip);
    494                     g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
     494                    g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4));
    495495                    g.draw(area);
    496496                    g.setClip(oldClip);
     
    607607     * far to fill from the boundary towards the center of the area;
    608608     * if null, area will be filled completely
     609     * @param extentThreshold if not null, determines if the partial filled should
     610     * be replaced by plain fill, when it covers a certain fraction of the total area
    609611     * @param disabled If this should be drawn with a special disabled style.
    610612     * @param text The text to write on the area.
    611613     */
    612     public void drawArea(Relation r, Color color, MapImage fillImage, Float extent, boolean disabled, TextElement text) {
     614    public void drawArea(Relation r, Color color, MapImage fillImage, Float extent, Float extentThreshold, boolean disabled, TextElement text) {
    613615        Multipolygon multipolygon = MultipolygonCache.getInstance().get(nc, r);
    614616        if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
     
    620622                }
    621623                if (extent != null) {
    622                     if (pd.isClosed()) {
    623                         AreaAndPerimeter ap = pd.getAreaAndPerimeter();
    624                         // if partial fill would only leave a small gap in the center ...
    625                         if (ap.getPerimeter() * extent * scale > partialFillThreshold / 100 * ap.getArea()) {
    626                             // ... turn it off and fill completely
    627                             extent = null;
    628                         }
    629                     } else {
     624                    if (!usePartialFill(pd.getAreaAndPerimeter(), extent, extentThreshold)) {
     625                        extent = null;
     626                    } else if (!pd.isClosed()) {
    630627                        pfClip = getPFClip(pd, extent * scale);
    631628                    }
     
    646643     * far to fill from the boundary towards the center of the area;
    647644     * if null, area will be filled completely
     645     * @param extentThreshold if not null, determines if the partial filled should
     646     * be replaced by plain fill, when it covers a certain fraction of the total area
    648647     * @param disabled If this should be drawn with a special disabled style.
    649648     * @param text The text to write on the area.
    650649     */
    651     public void drawArea(Way w, Color color, MapImage fillImage, Float extent, boolean disabled, TextElement text) {
     650    public void drawArea(Way w, Color color, MapImage fillImage, Float extent, Float extentThreshold, boolean disabled, TextElement text) {
    652651        Path2D.Double pfClip = null;
    653652        if (extent != null) {
    654             if (w.isClosed()) {
    655                 AreaAndPerimeter ap = Geometry.getAreaAndPerimeter(w.getNodes());
    656                 // if partial fill would only leave a small gap in the center ...
    657                 if (ap.getPerimeter() * extent * scale > partialFillThreshold / 100 * ap.getArea()) {
    658                     // ... turn it off and fill completely
    659                     extent = null;
    660                 }
    661             } else {
     653            if (!usePartialFill(Geometry.getAreaAndPerimeter(w.getNodes()), extent, extentThreshold)) {
     654                extent = null;
     655            } else if (!w.isClosed()) {
    662656                pfClip = getPFClip(w, extent * scale);
    663657            }
    664658        }
    665659        drawArea(w, getPath(w), color, fillImage, extent, pfClip, disabled, text);
     660    }
     661
     662    /**
     663     * Determine, if partial fill should be turned off for this object, because
     664     * only a small unfilled gap in the center of the area would be left.
     665     *
     666     * This is used to get a cleaner look for urban regions with many small
     667     * areas like buildings, etc.
     668     * @param ap the area and the perimeter of the object
     669     * @param extent the "width" of partial fill
     670     * @param threshold when the partial fill covers that much of the total
     671     * area, the partial fill is turned off; can be greater than 100% as the
     672     * covered area is estimated as <code>perimeter * extent</code>
     673     * @return true, if the partial fill should be used, false otherwise
     674     */
     675    private boolean usePartialFill(AreaAndPerimeter ap, float extent, Float threshold) {
     676        if (threshold == null) return true;
     677        return ap.getPerimeter() * extent * scale < threshold * ap.getArea();
    666678    }
    667679
     
    15191531        showIcons = paintSettings.getShowIconsDistance() > circum;
    15201532        isOutlineOnly = paintSettings.isOutlineOnly();
    1521         partialFillThreshold = paintSettings.getPartialFillThreshold();
    15221533        orderFont = new Font(Main.pref.get("mappaint.font", "Droid Sans"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
    15231534
     
    15961607    }
    15971608
     1609    /**
     1610     * Fix the clipping area of unclosed polygons for partial fill.
     1611     *
     1612     * The current algorithm for partial fill simply strokes the polygon with a
     1613     * large stroke width after masking the outside with a clipping area.
     1614     * This works, but for unclosed polygons, the mask can crop the corners at
     1615     * both ends (see #12104).
     1616     *
     1617     * This method fixes the clipping area by sort of adding the corners to the
     1618     * clip outline.
     1619     *
     1620     * @param clip the clipping area to modify (initially empty)
     1621     * @param nodes nodes of the polygon
     1622     * @param extent the extent
     1623     */
    15981624    private static void buildPFClip(Path2D.Double clip, List<Node> nodes, double extent) {
    15991625        boolean initial = true;
     
    16291655    }
    16301656
     1657    /**
     1658     * Get the point to add to the clipping area for partial fill of unclosed polygons.
     1659     *
     1660     * <code>(p1,p2)</code> is the first or last way segment and <code>p3</code> the
     1661     * opposite endpoint.
     1662     *
     1663     * @param p1 1st point
     1664     * @param p2 2nd point
     1665     * @param p3 3rd point
     1666     * @param extent the extent
     1667     * @return a point q, such that p1,p2,q form a right angle
     1668     * and the distance of q to p2 is <code>extent</code>. The point q lies on
     1669     * the same side of the line p1,p2 as the point p3.
     1670     * Returns null if p1,p2,p3 forms an angle greater 90 degrees. (In this case
     1671     * the corner of the partial fill would not be cut off by the mask, so an
     1672     * additional point is not necessary.)
     1673     */
    16311674    private static EastNorth getPFDisplacedEndPoint(EastNorth p1, EastNorth p2, EastNorth p3, double extent) {
    16321675        double dx1 = p2.getX() - p1.getX();
     
    16361679        if (dx1 * dx2 + dy1 * dy2 < 0) {
    16371680            double len = Math.sqrt(dx1 * dx1 + dy1 * dy1);
     1681            if (len == 0) return null;
    16381682            double dxm = -dy1 * extent / len;
    16391683            double dym = dx1 * extent / len;
  • trunk/src/org/openstreetmap/josm/gui/mappaint/AreaElemStyle.java

    r9008 r9099  
    2626    public TextElement text;
    2727    public Float extent;
     28    public Float extentThreshold;
    2829
    29     protected AreaElemStyle(Cascade c, Color color, MapImage fillImage, Float extent, TextElement text) {
     30    protected AreaElemStyle(Cascade c, Color color, MapImage fillImage, Float extent, Float extentThreshold, TextElement text) {
    3031        super(c, 1f);
    3132        CheckParameterUtil.ensureParameterNotNull(color);
    3233        this.color = color;
     34        this.fillImage = fillImage;
    3335        this.extent = extent;
    34         this.fillImage = fillImage;
     36        this.extentThreshold = extentThreshold;
    3537        this.text = text;
    3638    }
     
    3941        final Cascade c = env.mc.getCascade(env.layer);
    4042        MapImage fillImage = null;
    41         Color color = null;
    42         Float extent = null;
     43        Color color;
    4344
    4445        IconReference iconRef = c.get(FILL_IMAGE, null, IconReference.class);
     
    8182        }
    8283
    83         extent = c.get(FILL_EXTENT, null, float.class);
     84        Float extent = c.get(FILL_EXTENT, null, float.class);
     85        Float extentThreshold = c.get(FILL_EXTENT_THRESHOLD, null, float.class);
    8486
    8587        if (color != null)
    86             return new AreaElemStyle(c, color, fillImage, extent, text);
     88            return new AreaElemStyle(c, color, fillImage, extent, extentThreshold, text);
    8789        else
    8890            return null;
     
    101103                }
    102104            }
    103             painter.drawArea((Way) osm, myColor, fillImage, extent, painter.isInactiveMode() || osm.isDisabled(), text);
     105            painter.drawArea((Way) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled(), text);
    104106        } else if (osm instanceof Relation) {
    105107            if (color != null && (selected || outermember)) {
    106108                myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
    107109            }
    108             painter.drawArea((Relation) osm, myColor, fillImage, extent, painter.isInactiveMode() || osm.isDisabled(), text);
     110            painter.drawArea((Relation) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled(), text);
    109111        }
    110112    }
     
    124126        if (extent != other.extent)
    125127            return false;
     128        if (extentThreshold != other.extentThreshold)
     129            return false;
    126130        if (!Objects.equals(text, other.text))
    127131            return false;
     
    134138        hash = 61 * hash + color.hashCode();
    135139        hash = 61 * hash + (extent != null ? Float.floatToIntBits(extent) : 0);
     140        hash = 61 * hash + (extentThreshold != null ? Float.floatToIntBits(extent) : 0);
    136141        hash = 61 * hash + (fillImage != null ? fillImage.hashCode() : 0);
    137142        hash = 61 * hash + (text != null ? text.hashCode() : 0);
  • trunk/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java

    r9005 r9099  
    1111    String FILL_COLOR = "fill-color";
    1212    String FILL_EXTENT = "fill-extent";
     13    String FILL_EXTENT_THRESHOLD = "fill-extent-threshold";
    1314    String FILL_IMAGE = "fill-image";
    1415    String FILL_OPACITY = "fill-opacity";
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java

    r9087 r9099  
    633633            return IN_DOWNLOADED_AREA.evaluate(e.osm);
    634634        }
     635
     636        static boolean completely_downloaded(Environment e) {
     637            if (e.osm instanceof Relation) {
     638                return !((Relation) e.osm).hasIncompleteMembers();
     639            } else {
     640                return true;
     641            }
     642        }
     643
     644        static boolean closed2(Environment e) {
     645            if (e.osm instanceof Way && ((Way) e.osm).isClosed())
     646                return true;
     647            if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon())
     648                return MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) e.osm).getOpenEnds().isEmpty();
     649            return false;
     650        }
    635651    }
    636652
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj

    r8874 r9099  
    204204|   < ELEMENT_OF: "∈" >
    205205|   < CROSSING: "⧉" >
     206|   < PERCENT: "%" >
    206207|   < COMMENT_START: "/*" > : COMMENT
    207208|   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
     
    11261127}
    11271128{
    1128     f=ufloat() ( u=ident() | <DEG> { u = "°"; } )
     1129    f=ufloat() ( u=ident() | <DEG> { u = "°"; } | <PERCENT> { u = "%"; } )
    11291130    {
    11301131        Double m = unit_factor(u);
     
    11431144        case "grad": return Math.PI / 200;
    11441145        case "turn": return 2 * Math.PI;
     1146        case "%": return 0.01;
    11451147        case "px": return 1.;
    11461148        case "cm": return 96/2.54;
  • trunk/src/org/openstreetmap/josm/tools/Geometry.java

    r9063 r9099  
    978978     * Uses current projection; units are that of the projected coordinates.
    979979     *
    980      * @param nodes the list of nodes representing the polygon (must be
    981      * closed, i.e. first node equals last node)
     980     * @param nodes the list of nodes representing the polygon
    982981     * @return area and perimeter
    983982     */
    984983    public static AreaAndPerimeter getAreaAndPerimeter(List<Node> nodes) {
    985         if (nodes.get(0) != nodes.get(nodes.size() - 1)) {
    986             throw new IllegalArgumentException();
    987         }
    988984        double area = 0;
    989985        double perimeter = 0;
    990         Node lastN = null;
    991         for (Node n : nodes) {
    992             if (lastN != null) {
    993                 EastNorth p1 = lastN.getEastNorth();
    994                 EastNorth p2 = n.getEastNorth();
     986        if (!nodes.isEmpty()) {
     987            boolean closed = nodes.get(0) == nodes.get(nodes.size() - 1);
     988            int numSegments = closed ? nodes.size() - 1 : nodes.size();
     989            EastNorth p1 = nodes.get(0).getEastNorth();
     990            for (int i=1; i<=numSegments; i++) {
     991                EastNorth p2 = nodes.get(i == numSegments ? 0 : i).getEastNorth();
    995992                area += p1.east() * p2.north() - p2.east() * p1.north();
    996993                perimeter += p1.distance(p2);
    997             }
    998             lastN = n;
     994                p1 = p2;
     995            }
    999996        }
    1000997        return new AreaAndPerimeter(Math.abs(area) / 2, perimeter);
  • trunk/styles/standard/elemstyles.mapcss

    r9097 r9099  
    48664866/*************/
    48674867
     4868/* small extent for unclosed area (see below for closed) */
    48684869area[setting("partial_fill")] {
     4870    fill-extent: 15;
     4871}
     4872
     4873/* Turn partial fill off and us plain fill, when the partial fill covers about
     4874   100% of the area. This reduces artifacts (typically for incomplete multipolygons).
     4875   Switching between full and partial fill while drawing an area might be irritating,
     4876   so only do this at low zoom. */
     4877area|z-13[setting("partial_fill")] {
     4878    fill-extent-threshold: 100%;
     4879}
     4880
     4881/* Larger extent for closed areas.
     4882   Turn partial fill off, when it covers more than about 70% of the area. This is avoids
     4883   areas with small unfilled patches in the center. */
     4884area[setting("partial_fill")]:closed2 {
    48694885    fill-extent: 25;
    4870 }
     4886    fill-extent-threshold: 70%;
     4887}
     4888
Note: See TracChangeset for help on using the changeset viewer.