001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.styleelement; 003 004import java.util.Objects; 005 006import org.openstreetmap.josm.data.osm.IPrimitive; 007import org.openstreetmap.josm.data.osm.IWay; 008import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings; 009import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer; 010import org.openstreetmap.josm.gui.mappaint.Cascade; 011import org.openstreetmap.josm.gui.mappaint.Environment; 012import org.openstreetmap.josm.gui.mappaint.Keyword; 013import org.openstreetmap.josm.tools.CheckParameterUtil; 014 015/** 016 * Style element that displays a repeated image pattern along a way. 017 */ 018public class RepeatImageElement extends StyleElement { 019 020 /** 021 * The side on which the image should be aligned to the line. 022 */ 023 public enum LineImageAlignment { 024 /** 025 * Align it to the top side of the line 026 */ 027 TOP(.5), 028 /** 029 * Align it to the center of the line 030 */ 031 CENTER(0), 032 /** 033 * Align it to the bottom of the line 034 */ 035 BOTTOM(-.5); 036 037 private final double alignmentOffset; 038 039 LineImageAlignment(double alignmentOffset) { 040 this.alignmentOffset = alignmentOffset; 041 } 042 043 /** 044 * Gets the alignment offset. 045 * @return The offset relative to the image height compared to placing the image in the middle of the line. 046 */ 047 public double getAlignmentOffset() { 048 return alignmentOffset; 049 } 050 } 051 052 /** 053 * The image to draw on the line repeatedly 054 */ 055 public MapImage pattern; 056 /** 057 * The offset to the side of the way 058 */ 059 public float offset; 060 /** 061 * The space between the images 062 */ 063 public float spacing; 064 /** 065 * The offset of the first image along the way 066 */ 067 public float phase; 068 /** 069 * The alignment of the image 070 */ 071 public LineImageAlignment align; 072 073 private static final String[] REPEAT_IMAGE_KEYS = {REPEAT_IMAGE, REPEAT_IMAGE_WIDTH, REPEAT_IMAGE_HEIGHT, REPEAT_IMAGE_OPACITY, 074 null, null}; 075 076 /** 077 * Create a new image element 078 * @param c The cascade 079 * @param pattern The image to draw on the line repeatedly 080 * @param offset The offset to the side of the way 081 * @param spacing The space between the images 082 * @param phase The offset of the first image along the way 083 * @param align The alignment of the image 084 */ 085 public RepeatImageElement(Cascade c, MapImage pattern, float offset, float spacing, float phase, LineImageAlignment align) { 086 super(c, 2.9f); 087 CheckParameterUtil.ensureParameterNotNull(pattern); 088 CheckParameterUtil.ensureParameterNotNull(align); 089 this.pattern = pattern; 090 this.offset = offset; 091 this.spacing = spacing; 092 this.phase = phase; 093 this.align = align; 094 } 095 096 /** 097 * Create a RepeatImageElement from the given environment 098 * @param env The environment 099 * @return The image style element or <code>null</code> if none should be painted 100 */ 101 public static RepeatImageElement create(Environment env) { 102 MapImage pattern = NodeElement.createIcon(env, REPEAT_IMAGE_KEYS); 103 if (pattern == null) 104 return null; 105 Cascade c = env.mc.getCascade(env.layer); 106 float offset = c.get(REPEAT_IMAGE_OFFSET, 0f, Float.class); 107 float spacing = c.get(REPEAT_IMAGE_SPACING, 0f, Float.class); 108 float phase = -c.get(REPEAT_IMAGE_PHASE, 0f, Float.class); 109 110 LineImageAlignment align = LineImageAlignment.CENTER; 111 Keyword alignKW = c.get(REPEAT_IMAGE_ALIGN, Keyword.CENTER, Keyword.class); 112 if ("top".equals(alignKW.val)) { 113 align = LineImageAlignment.TOP; 114 } else if ("bottom".equals(alignKW.val)) { 115 align = LineImageAlignment.BOTTOM; 116 } 117 118 return new RepeatImageElement(c, pattern, offset, spacing, phase, align); 119 } 120 121 @Override 122 public void paintPrimitive(IPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, 123 boolean selected, boolean outermember, boolean member) { 124 if (primitive instanceof IWay) { 125 IWay<?> w = (IWay<?>) primitive; 126 painter.drawRepeatImage(w, pattern, painter.isInactiveMode() || w.isDisabled(), offset, spacing, phase, align); 127 } 128 } 129 130 @Override 131 public boolean isProperLineStyle() { 132 return true; 133 } 134 135 @Override 136 public boolean equals(Object obj) { 137 if (this == obj) return true; 138 if (obj == null || getClass() != obj.getClass()) return false; 139 if (!super.equals(obj)) return false; 140 RepeatImageElement that = (RepeatImageElement) obj; 141 return align == that.align && 142 Float.compare(that.offset, offset) == 0 && 143 Float.compare(that.spacing, spacing) == 0 && 144 Float.compare(that.phase, phase) == 0 && 145 Objects.equals(pattern, that.pattern); 146 } 147 148 @Override 149 public int hashCode() { 150 return Objects.hash(super.hashCode(), pattern, offset, spacing, phase, align); 151 } 152 153 @Override 154 public String toString() { 155 return "RepeatImageStyle{" + super.toString() + "pattern=[" + pattern + 156 "], offset=" + offset + ", spacing=" + spacing + 157 ", phase=" + (-phase) + ", align=" + align + '}'; 158 } 159}