001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.styling;
017:
018: import org.geotools.event.AbstractGTComponent;
019: import org.geotools.factory.CommonFactoryFinder;
020: import org.geotools.factory.GeoTools;
021: import org.opengis.filter.FilterFactory;
022: import org.opengis.filter.expression.Expression;
023: import org.opengis.util.Cloneable;
024:
025: // J2SE depedencies
026: import java.util.Arrays;
027:
028: /**
029: * Provides a Java representation of the Stroke object in an SLD document. A
030: * stroke defines how a line is rendered.
031: *
032: * @author James Macgill, CCG
033: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/styling/StrokeImpl.java $
034: * @version $Id: StrokeImpl.java 25459 2007-05-08 05:19:25Z jgarnett $
035: */
036: public class StrokeImpl extends AbstractGTComponent implements Stroke,
037: Cloneable {
038: private FilterFactory filterFactory;
039: private Expression color;
040: private float[] dashArray;
041: private Expression dashOffset;
042: private Graphic fillGraphic;
043: private Graphic strokeGraphic;
044: private Expression lineCap;
045: private Expression lineJoin;
046: private Expression opacity;
047: private Expression width;
048:
049: /**
050: * Creates a new instance of Stroke
051: */
052: protected StrokeImpl() {
053: this (CommonFactoryFinder.getFilterFactory(GeoTools
054: .getDefaultHints()));
055: }
056:
057: protected StrokeImpl(FilterFactory factory) {
058: filterFactory = factory;
059: }
060:
061: public void setFilterFactory(FilterFactory factory) {
062: filterFactory = factory;
063: }
064:
065: /**
066: * This parameter gives the solid color that will be used for a stroke.<br>
067: * The color value is RGB-encoded using two hexidecimal digits per
068: * primary-color component in the order Red, Green, Blue, prefixed with
069: * the hash (#) sign. The hexidecimal digits between A and F may be in
070: * either upper or lower case. For example, full red is encoded as
071: * "#ff0000" (with no quotation marks). The default color is defined to
072: * be black ("#000000"). Note: in CSS this parameter is just called Stroke
073: * and not Color.
074: *
075: * @return The color of the stroke encoded as a hexidecimal RGB value.
076: */
077: public Expression getColor() {
078: return color;
079: }
080:
081: /**
082: * This parameter sets the solid color that will be used for a stroke.<br>
083: * The color value is RGB-encoded using two hexidecimal digits per
084: * primary-color component in the order Red, Green, Blue, prefixed with
085: * the hash (#) sign. The hexidecimal digits between A and F may be in
086: * either upper or lower case. For example, full red is encoded as
087: * "#ff0000" (with no quotation marks). The default color is defined to
088: * be black ("#000000"). Note: in CSS this parameter is just called Stroke
089: * and not Color.
090: *
091: * @param color The color of the stroke encoded as a hexidecimal RGB value.
092: * This must not be null.
093: *
094: * @throws IllegalArgumentException DOCUMENT ME!
095: */
096: public void setColor(Expression color) {
097: if (color == null) {
098: throw new IllegalArgumentException("Color must be provided");
099: }
100:
101: if (this .color == color) {
102: return;
103: }
104:
105: Expression old = this .color;
106: this .color = color;
107: fireChildChanged("color", color, old);
108: }
109:
110: /**
111: * This parameter sets the solid color that will be used for a stroke.<br>
112: * The color value is RGB-encoded using two hexidecimal digits per
113: * primary-color component in the order Red, Green, Blue, prefixed with
114: * the hash (#) sign. The hexidecimal digits between A and F may be in
115: * either upper or lower case. For example, full red is encoded as
116: * "#ff0000" (with no quotation marks). The default color is defined to
117: * be black ("#000000"). Note: in CSS this parameter is just called Stroke
118: * and not Color.
119: *
120: * @param color The color of the stroke encoded as a hexidecimal RGB value.
121: */
122: public void setColor(String color) {
123: setColor(filterFactory.literal(color));
124: }
125:
126: /**
127: * This parameter encodes the dash pattern as a series of floats.<br>
128: * The first number gives the length in pixels of the dash to draw, the
129: * second gives the amount of space to leave, and this pattern repeats.<br>
130: * If an odd number of values is given, then the pattern is expanded by
131: * repeating it twice to give an even number of values. The default is to
132: * draw an unbroken line.<br>
133: * For example, "2 1 3 2" would produce:<br>
134: * <code>-- --- -- --- --
135: * --- -- --- -- --- --</code>
136: *
137: * @return The dash pattern as an array of float values in the form
138: * "dashlength gaplength ..."
139: */
140: public float[] getDashArray() {
141: float[] ret = new float[0];
142:
143: if (dashArray != null) {
144: ret = new float[dashArray.length];
145: System.arraycopy(dashArray, 0, ret, 0, dashArray.length);
146: }
147:
148: return ret;
149: }
150:
151: /**
152: * This parameter encodes the dash pattern as a series of floats.<br>
153: * The first number gives the length in pixels of the dash to draw, the
154: * second gives the amount of space to leave, and this pattern repeats.<br>
155: * If an odd number of values is given, then the pattern is expanded by
156: * repeating it twice to give an even number of values. The default is to
157: * draw an unbroken line.<br>
158: * For example, "2 1 3 2" would produce:<br>
159: * <code>-- --- -- ---
160: * -- --- -- --- --
161: * --- --</code>
162: *
163: * @param dashPattern The dash pattern as an array of float values in the
164: * form "dashlength gaplength ..."
165: */
166: public void setDashArray(float[] dashPattern) {
167: dashArray = dashPattern;
168: fireChanged();
169: }
170:
171: /**
172: * This param determines where the dash pattern should start from.
173: *
174: * @return where the dash should start from.
175: */
176: public Expression getDashOffset() {
177: return dashOffset;
178: }
179:
180: /**
181: * This param determines where the dash pattern should start from.
182: *
183: * @param dashOffset The distance into the dash pattern that should act as
184: * the start.
185: */
186: public void setDashOffset(Expression dashOffset) {
187: if (dashOffset == null) {
188: return;
189: }
190:
191: Expression old = this .dashOffset;
192: this .dashOffset = dashOffset;
193: fireChildChanged("dashOffset", dashOffset, old);
194: }
195:
196: /**
197: * This parameter indicates that a stipple-fill repeated graphic will be
198: * used and specifies the fill graphic to use.
199: *
200: * @return The graphic to use as a stipple fill. If null, then no Stipple
201: * fill should be used.
202: */
203: public Graphic getGraphicFill() {
204: return fillGraphic;
205: }
206:
207: /**
208: * This parameter indicates that a stipple-fill repeated graphic will be
209: * used and specifies the fill graphic to use.
210: *
211: * @param fillGraphic The graphic to use as a stipple fill. If null, then
212: * no Stipple fill should be used.
213: */
214: public void setGraphicFill(Graphic fillGraphic) {
215: if (this .fillGraphic == fillGraphic) {
216: return;
217: }
218:
219: Graphic old = this .fillGraphic;
220:
221: this .fillGraphic = fillGraphic;
222: fireChildChanged("fillGraphic", fillGraphic, old);
223: }
224:
225: /**
226: * This parameter indicates that a repeated-linear-graphic graphic stroke
227: * type will be used and specifies the graphic to use. Proper stroking
228: * with a linear graphic requires two "hot-spot" points within the space
229: * of the graphic to indicate where the rendering line starts and stops.
230: * In the case of raster images with no special mark-up, this line will be
231: * assumed to be the middle pixel row of the image, starting from the
232: * first pixel column and ending at the last pixel column.
233: *
234: * @return The graphic to use as a linear graphic. If null, then no graphic
235: * stroke should be used.
236: */
237: public Graphic getGraphicStroke() {
238: return strokeGraphic;
239: }
240:
241: /**
242: * This parameter indicates that a repeated-linear-graphic graphic stroke
243: * type will be used and specifies the graphic to use. Proper stroking
244: * with a linear graphic requires two "hot-spot" points within the space
245: * of the graphic to indicate where the rendering line starts and stops.
246: * In the case of raster images with no special mark-up, this line will be
247: * assumed to be the middle pixel row of the image, starting from the
248: * first pixel column and ending at the last pixel column.
249: *
250: * @param strokeGraphic The graphic to use as a linear graphic. If null,
251: * then no graphic stroke should be used.
252: */
253: public void setGraphicStroke(Graphic strokeGraphic) {
254: if (this .strokeGraphic == strokeGraphic) {
255: return;
256: }
257:
258: Graphic old = this .strokeGraphic;
259: this .strokeGraphic = strokeGraphic;
260: fireChildChanged("strokeGraphic", strokeGraphic, old);
261: }
262:
263: /**
264: * This parameter controls how line strings should be capped.
265: *
266: * @return The cap style. This will be one of "butt", "round" and "square"
267: * There is no defined default.
268: */
269: public Expression getLineCap() {
270: return lineCap;
271: }
272:
273: /**
274: * This parameter controls how line strings should be capped.
275: *
276: * @param lineCap The cap style. This can be one of "butt", "round" and
277: * "square" There is no defined default.
278: */
279: public void setLineCap(Expression lineCap) {
280: if (lineCap == null) {
281: return;
282: }
283:
284: Expression old = this .lineCap;
285: this .lineCap = lineCap;
286: fireChildChanged("lineCap", lineCap, old);
287: }
288:
289: /**
290: * This parameter controls how line strings should be joined together.
291: *
292: * @return The join style. This will be one of "mitre", "round" and
293: * "bevel". There is no defined default.
294: */
295: public Expression getLineJoin() {
296: return lineJoin;
297: }
298:
299: /**
300: * This parameter controls how line strings should be joined together.
301: *
302: * @param lineJoin The join style. This will be one of "mitre", "round"
303: * and "bevel". There is no defined default.
304: */
305: public void setLineJoin(Expression lineJoin) {
306: if (lineJoin == null) {
307: return;
308: }
309:
310: Expression old = this .lineJoin;
311: this .lineJoin = lineJoin;
312: fireChildChanged("lineJoin", lineJoin, old);
313: }
314:
315: /**
316: * This specifies the level of translucency to use when rendering the stroke.<br>
317: * The value is encoded as a floating-point value between 0.0 and 1.0 with
318: * 0.0 representing totally transparent and 1.0 representing totally
319: * opaque. A linear scale of translucency is used for intermediate values.<br>
320: * For example, "0.65" would represent 65% opacity. The default value is
321: * 1.0 (opaque).
322: *
323: * @return The opacity of the stroke, where 0.0 is completely transparent
324: * and 1.0 is completely opaque.
325: */
326: public Expression getOpacity() {
327: return opacity;
328: }
329:
330: /**
331: * This specifies the level of translucency to use when rendering the stroke.<br>
332: * The value is encoded as a floating-point value between 0.0 and 1.0 with
333: * 0.0 representing totally transparent and 1.0 representing totally
334: * opaque. A linear scale of translucency is used for intermediate values.<br>
335: * For example, "0.65" would represent 65% opacity. The default value is
336: * 1.0 (opaque).
337: *
338: * @param opacity The opacity of the stroke, where 0.0 is completely
339: * transparent and 1.0 is completely opaque.
340: */
341: public void setOpacity(Expression opacity) {
342: if (opacity == null) {
343: return;
344: }
345:
346: Expression old = this .opacity;
347: this .opacity = opacity;
348: fireChildChanged("opacity", opacity, old);
349: }
350:
351: /**
352: * This parameter gives the absolute width (thickness) of a stroke in
353: * pixels encoded as a float. The default is 1.0. Fractional numbers are
354: * allowed but negative numbers are not.
355: *
356: * @return The width of the stroke in pixels. This may be fractional but
357: * not negative.
358: */
359: public Expression getWidth() {
360: return width;
361: }
362:
363: /**
364: * This parameter sets the absolute width (thickness) of a stroke in pixels
365: * encoded as a float. The default is 1.0. Fractional numbers are allowed
366: * but negative numbers are not.
367: *
368: * @param width The width of the stroke in pixels. This may be fractional
369: * but not negative.
370: */
371: public void setWidth(Expression width) {
372: if (width == null) {
373: return;
374: }
375:
376: Expression old = width;
377: this .width = width;
378: fireChildChanged("width", width, old);
379: }
380:
381: public String toString() {
382: StringBuffer out = new StringBuffer(
383: "org.geotools.styling.StrokeImpl:\n");
384: out.append("\tColor " + this .color + "\n");
385: out.append("\tWidth " + this .width + "\n");
386: out.append("\tOpacity " + this .opacity + "\n");
387: out.append("\tLineCap " + this .lineCap + "\n");
388: out.append("\tLineJoin " + this .lineJoin + "\n");
389: out.append("\tDash Array " + this .dashArray + "\n");
390: out.append("\tDash Offset " + this .dashOffset + "\n");
391: out.append("\tFill Graphic " + this .fillGraphic + "\n");
392: out.append("\tStroke Graphic " + this .strokeGraphic);
393:
394: return out.toString();
395: }
396:
397: public java.awt.Color getColor(org.geotools.feature.Feature feature) {
398: return java.awt.Color.decode((String) this .getColor().evaluate(
399: feature));
400: }
401:
402: public void accept(StyleVisitor visitor) {
403: visitor.visit(this );
404: }
405:
406: /**
407: * Clone the StrokeImpl object.
408: *
409: * <p>
410: * The clone is a deep copy of the original, except for the expression
411: * values which are immutable.
412: * </p>
413: *
414: * @see org.geotools.styling.Stroke#clone()
415: */
416: public Object clone() {
417: try {
418: StrokeImpl clone = (StrokeImpl) super .clone();
419:
420: if (dashArray != null) {
421: clone.dashArray = new float[dashArray.length];
422: System.arraycopy(dashArray, 0, clone.dashArray, 0,
423: dashArray.length);
424: }
425:
426: if (fillGraphic != null) {
427: clone.fillGraphic = (Graphic) ((Cloneable) fillGraphic)
428: .clone();
429: }
430:
431: if (strokeGraphic != null) {
432: clone.strokeGraphic = (Graphic) ((Cloneable) strokeGraphic)
433: .clone();
434: }
435:
436: return clone;
437: } catch (CloneNotSupportedException e) {
438: // This will never happen
439: throw new RuntimeException("Failed to clone StrokeImpl");
440: }
441: }
442:
443: public int hashCode() {
444: final int PRIME = 1000003;
445: int result = 0;
446:
447: if (color != null) {
448: result = (PRIME * result) + color.hashCode();
449: }
450:
451: if (dashOffset != null) {
452: result = (PRIME * result) + dashOffset.hashCode();
453: }
454:
455: if (fillGraphic != null) {
456: result = (PRIME * result) + fillGraphic.hashCode();
457: }
458:
459: if (strokeGraphic != null) {
460: result = (PRIME * result) + strokeGraphic.hashCode();
461: }
462:
463: if (lineCap != null) {
464: result = (PRIME * result) + lineCap.hashCode();
465: }
466:
467: if (lineJoin != null) {
468: result = (PRIME * result) + lineJoin.hashCode();
469: }
470:
471: if (opacity != null) {
472: result = (PRIME * result) + opacity.hashCode();
473: }
474:
475: if (width != null) {
476: result = (PRIME * result) + width.hashCode();
477: }
478:
479: if (dashArray != null) {
480: result = (PRIME * result) + hashCodeDashArray(dashArray);
481: }
482:
483: return result;
484: }
485:
486: /*
487: * Helper method to compute the hashCode of float arrays.
488: */
489: private int hashCodeDashArray(float[] a) {
490: final int PRIME = 1000003;
491:
492: if (a == null) {
493: return 0;
494: }
495:
496: int result = 0;
497:
498: for (int i = 0; i < a.length; i++) {
499: result = (PRIME * result) + Float.floatToIntBits(a[i]);
500: }
501:
502: return result;
503: }
504:
505: /**
506: * Compares this stroke with another stroke for equality.
507: *
508: * @param oth The other StrokeImpl to compare
509: *
510: * @return True if this and oth are equal.
511: */
512: public boolean equals(Object oth) {
513: if (this == oth) {
514: return true;
515: }
516:
517: if (oth == null) {
518: return false;
519: }
520:
521: if (oth.getClass() != getClass()) {
522: return false;
523: }
524:
525: StrokeImpl other = (StrokeImpl) oth;
526:
527: // check the color first - most likely to change
528: if (this .color == null) {
529: if (other.color != null) {
530: return false;
531: }
532: } else {
533: if (!this .color.equals(other.color)) {
534: return false;
535: }
536: }
537:
538: // check the width
539: if (this .width == null) {
540: if (other.width != null) {
541: return false;
542: }
543: } else {
544: if (!this .width.equals(other.width)) {
545: return false;
546: }
547: }
548:
549: // check the dashOffset
550: if (this .dashOffset == null) {
551: if (other.dashOffset != null) {
552: return false;
553: }
554: } else {
555: if (!this .dashOffset.equals(other.dashOffset)) {
556: return false;
557: }
558: }
559:
560: if (this .lineCap == null) {
561: if (other.lineCap != null) {
562: return false;
563: }
564: } else {
565: if (!this .lineCap.equals(other.lineCap)) {
566: return false;
567: }
568: }
569:
570: if (this .lineJoin == null) {
571: if (other.lineJoin != null) {
572: return false;
573: }
574: } else {
575: if (!this .lineJoin.equals(other.lineJoin)) {
576: return false;
577: }
578: }
579:
580: if (this .opacity == null) {
581: if (other.opacity != null) {
582: return false;
583: }
584: } else {
585: if (!this .opacity.equals(other.opacity)) {
586: return false;
587: }
588: }
589:
590: if (this .fillGraphic == null) {
591: if (other.fillGraphic != null) {
592: return false;
593: }
594: } else {
595: if (!this .fillGraphic.equals(other.fillGraphic)) {
596: return false;
597: }
598: }
599:
600: if (this .strokeGraphic == null) {
601: if (other.strokeGraphic != null) {
602: return false;
603: }
604: } else {
605: if (!this .strokeGraphic.equals(other.strokeGraphic)) {
606: return false;
607: }
608: }
609:
610: if (!Arrays.equals(dashArray, other.dashArray)) {
611: return false;
612: }
613:
614: return true;
615: }
616: }
|