Changeset 9099 in josm for trunk/src/org


Ignore:
Timestamp:
2015-12-11T17:36:59+01:00 (4 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/src/org/openstreetmap/josm
Files:
7 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);
Note: See TracChangeset for help on using the changeset viewer.