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 opacity 070 */ 071 public float opacity; 072 /** 073 * The alignment of the image 074 */ 075 public LineImageAlignment align; 076 077 private static final String[] REPEAT_IMAGE_KEYS = {REPEAT_IMAGE, REPEAT_IMAGE_WIDTH, REPEAT_IMAGE_HEIGHT, REPEAT_IMAGE_OPACITY, 078 null, null}; 079 080 /** 081 * Create a new image element 082 * @param c The cascade 083 * @param pattern The image to draw on the line repeatedly 084 * @param offset The offset to the side of the way 085 * @param spacing The space between the images 086 * @param phase The offset of the first image along the way 087 * @param opacity The opacity 088 * @param align The alignment of the image 089 */ 090 public RepeatImageElement(Cascade c, MapImage pattern, float offset, float spacing, float phase, float opacity, LineImageAlignment align) { 091 super(c, 2.9f); 092 CheckParameterUtil.ensureParameterNotNull(pattern); 093 CheckParameterUtil.ensureParameterNotNull(align); 094 this.pattern = pattern; 095 this.offset = offset; 096 this.spacing = spacing; 097 this.phase = phase; 098 this.opacity = opacity; 099 this.align = align; 100 } 101 102 /** 103 * Create a RepeatImageElement from the given environment 104 * @param env The environment 105 * @return The image style element or <code>null</code> if none should be painted 106 */ 107 public static RepeatImageElement create(Environment env) { 108 MapImage pattern = NodeElement.createIcon(env, REPEAT_IMAGE_KEYS); 109 if (pattern == null) 110 return null; 111 Cascade c = env.getCascade(); 112 float offset = c.get(REPEAT_IMAGE_OFFSET, 0f, Float.class); 113 float spacing = c.get(REPEAT_IMAGE_SPACING, 0f, Float.class); 114 float phase = -c.get(REPEAT_IMAGE_PHASE, 0f, Float.class); 115 float opacity = c.get(REPEAT_IMAGE_OPACITY, 1f, Float.class); 116 117 LineImageAlignment align = LineImageAlignment.CENTER; 118 Keyword alignKW = c.get(REPEAT_IMAGE_ALIGN, Keyword.CENTER, Keyword.class); 119 if ("top".equals(alignKW.val)) { 120 align = LineImageAlignment.TOP; 121 } else if ("bottom".equals(alignKW.val)) { 122 align = LineImageAlignment.BOTTOM; 123 } 124 125 return new RepeatImageElement(c, pattern, offset, spacing, phase, opacity, align); 126 } 127 128 @Override 129 public void paintPrimitive(IPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, 130 boolean selected, boolean outermember, boolean member) { 131 if (primitive instanceof IWay) { 132 IWay<?> w = (IWay<?>) primitive; 133 painter.drawRepeatImage(w, pattern, painter.isInactiveMode() || w.isDisabled(), offset, spacing, phase, opacity, align); 134 } 135 } 136 137 @Override 138 public boolean isProperLineStyle() { 139 return true; 140 } 141 142 @Override 143 public boolean equals(Object obj) { 144 if (this == obj) return true; 145 if (obj == null || getClass() != obj.getClass()) return false; 146 if (!super.equals(obj)) return false; 147 RepeatImageElement that = (RepeatImageElement) obj; 148 return align == that.align && 149 Float.compare(that.offset, offset) == 0 && 150 Float.compare(that.spacing, spacing) == 0 && 151 Float.compare(that.phase, phase) == 0 && 152 Objects.equals(pattern, that.pattern); 153 } 154 155 @Override 156 public int hashCode() { 157 return Objects.hash(super.hashCode(), pattern, offset, spacing, phase, opacity, align); 158 } 159 160 @Override 161 public String toString() { 162 return "RepeatImageStyle{" + super.toString() + "pattern=[" + pattern + 163 "], offset=" + offset + ", spacing=" + spacing + 164 ", phase=" + -phase + ", opacity=" + opacity + ", align=" + align + '}'; 165 } 166}