Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.symbology / org.gvsig.symbology.lib / org.gvsig.symbology.lib.impl / src / main / java / org / apache / batik / ext / awt / geom / DefaultPathLength.java @ 43156

History | View | Annotate | Download (18.4 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24

    
25
package org.apache.batik.ext.awt.geom;
26
/*
27
 * Based on portions of code from org.apache.batik.ext.awt.geom of the
28
 * batik-awt-util project, The Apache XML Graphics Project.
29
 * https://xmlgraphics.apache.org/
30
 */
31

    
32
import java.awt.Shape;
33
import java.awt.geom.AffineTransform;
34
import java.awt.geom.FlatteningPathIterator;
35
import java.awt.geom.PathIterator;
36
import java.awt.geom.Point2D;
37
import java.util.List;
38
import java.util.ArrayList;
39

    
40
import org.gvsig.symbology.PathLength;
41
/**
42
 * Utilitiy class for length calculations of paths.
43
 * <p>
44
 *   PathLength is a utility class for calculating the length
45
 *   of a path, the location of a point at a particular length
46
 *   along the path, and the angle of the tangent to the path
47
 *   at a given length.
48
 * </p>
49
 * <p>
50
 *   It uses a FlatteningPathIterator to create a flattened version
51
 *   of the Path. This means the values returned are not always
52
 *   exact (in fact, they rarely are), but in most cases they
53
 *   are reasonably accurate.
54
 * </p>
55
 *
56
 * @author <a href="mailto:dean.jackson@cmis.csiro.au">Dean Jackson</a>
57
 * @version $Id$
58
 */
59
public class DefaultPathLength implements PathLength {
60

    
61
    /**
62
     * The path to use for calculations.
63
     */
64
    protected Shape path;
65

    
66
    /**
67
     * The list of flattened path segments.
68
     */
69
    protected List segments;
70

    
71
    /**
72
     * Array where the index is the index of the original path segment
73
     * and the value is the index of the first of the flattened segments
74
     * in {@link #segments} that corresponds to that original path segment.
75
     */
76
    protected int[] segmentIndexes;
77

    
78
    /**
79
     * Cached copy of the path length.
80
     */
81
    protected float pathLength;
82

    
83
    /**
84
     * Whether this path been flattened yet.
85
     */
86
    protected boolean initialised;
87

    
88
    /**
89
     * Creates a new PathLength object for the specified {@link Shape}.
90
     * @param path The Path (or Shape) to use.
91
     */
92
    public DefaultPathLength(Shape path) {
93
        setPath(path);
94
    }
95

    
96
    /**
97
     * Returns the path to use for calculations.
98
     * @return Path used in calculations.
99
     */
100
    public Shape getPath() {
101
        return path;
102
    }
103

    
104
    /**
105
     * Sets the path to use for calculations.
106
     * @param v Path to be used in calculations.
107
     */
108
    public void setPath(Shape v) {
109
        this.path = v;
110
        initialised = false;
111
    }
112

    
113
    /**
114
     * Returns the length of the path used by this PathLength object.
115
     * @return The length of the path.
116
     */
117
    @Override
118
    public float lengthOfPath() {
119
        if (!initialised) {
120
            initialise();
121
        }
122
        return pathLength;
123
    }
124

    
125
    /**
126
     * Flattens the path and determines the path length.
127
     */
128
    protected void initialise() {
129
        pathLength = 0f;
130

    
131
        PathIterator pi = path.getPathIterator(new AffineTransform());
132
        SingleSegmentPathIterator sspi = new SingleSegmentPathIterator();
133
        segments = new ArrayList(20);
134
        List indexes = new ArrayList(20);
135
        int index = 0;
136
        int origIndex = -1;
137
        float lastMoveX = 0f;
138
        float lastMoveY = 0f;
139
        float currentX = 0f;
140
        float currentY = 0f;
141
        float[] seg = new float[6];
142
        int segType;
143

    
144
        segments.add(new PathSegment(PathIterator.SEG_MOVETO, 0f, 0f, 0f,
145
                                     origIndex));
146

    
147
        while (!pi.isDone()) {
148
            origIndex++;
149
            indexes.add(new Integer(index));
150
            segType = pi.currentSegment(seg);
151
            switch (segType) {
152
                case PathIterator.SEG_MOVETO:
153
                    segments.add(new PathSegment(segType, seg[0], seg[1],
154
                                                 pathLength, origIndex));
155
                    currentX = seg[0];
156
                    currentY = seg[1];
157
                    lastMoveX = currentX;
158
                    lastMoveY = currentY;
159
                    index++;
160
                    pi.next();
161
                    break;
162
                case PathIterator.SEG_LINETO:
163
                    pathLength += Point2D.distance(currentX, currentY, seg[0],
164
                                                   seg[1]);
165
                    segments.add(new PathSegment(segType, seg[0], seg[1],
166
                                                 pathLength, origIndex));
167
                    currentX = seg[0];
168
                    currentY = seg[1];
169
                    index++;
170
                    pi.next();
171
                    break;
172
                case PathIterator.SEG_CLOSE:
173
                    pathLength += Point2D.distance(currentX, currentY,
174
                                                   lastMoveX, lastMoveY);
175
                    segments.add(new PathSegment(PathIterator.SEG_LINETO,
176
                                                 lastMoveX, lastMoveY,
177
                                                 pathLength, origIndex));
178
                    currentX = lastMoveX;
179
                    currentY = lastMoveY;
180
                    index++;
181
                    pi.next();
182
                    break;
183
                default:
184
                    sspi.setPathIterator(pi, currentX, currentY);
185
                    FlatteningPathIterator fpi =
186
                        new FlatteningPathIterator(sspi, 0.01f);
187
                    while (!fpi.isDone()) {
188
                        segType = fpi.currentSegment(seg);
189
                        if (segType == PathIterator.SEG_LINETO) {
190
                            pathLength += Point2D.distance(currentX, currentY,
191
                                                           seg[0], seg[1]);
192
                            segments.add(new PathSegment(segType, seg[0],
193
                                                         seg[1], pathLength,
194
                                                         origIndex));
195
                            currentX = seg[0];
196
                            currentY = seg[1];
197
                            index++;
198
                        }
199
                        fpi.next();
200
                    }
201
            }
202
        }
203
        segmentIndexes = new int[indexes.size()];
204
        for (int i = 0; i < segmentIndexes.length; i++) {
205
            segmentIndexes[i] = ((Integer) indexes.get(i)).intValue();
206
        }
207
        initialised = true;
208
    }
209

    
210
    /**
211
     * Returns the number of segments in the path.
212
     */
213
    public int getNumberOfSegments() {
214
        if (!initialised) {
215
            initialise();
216
        }
217
        return segmentIndexes.length;
218
    }
219

    
220
    /**
221
     * Returns the length at the start of the segment given by the specified
222
     * index.
223
     */
224
    public float getLengthAtSegment(int index) {
225
        if (!initialised) {
226
            initialise();
227
        }
228
        if (index <= 0) {
229
            return 0;
230
        }
231
        if (index >= segmentIndexes.length) {
232
            return pathLength;
233
        }
234
        PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
235
        return seg.getLength();
236
    }
237

    
238
    /**
239
     * Returns the index of the segment at the given distance along the path.
240
     */
241
    public int segmentAtLength(float length) {
242
        int upperIndex = findUpperIndex(length);
243
        if (upperIndex == -1) {
244
            // Length is off the end of the path.
245
            return -1;
246
        }
247

    
248
        if (upperIndex == 0) {
249
            // Length was probably zero, so return the upper segment.
250
            PathSegment upper = (PathSegment) segments.get(upperIndex);
251
            return upper.getIndex();
252
        }
253

    
254
        PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
255
        return lower.getIndex();
256
    }
257

    
258
    /**
259
     * Returns the point that is the given proportion along the path segment
260
     * given by the specified index.
261
     */
262
    public Point2D pointAtLength(int index, float proportion) {
263
        if (!initialised) {
264
            initialise();
265
        }
266
        if (index < 0 || index >= segmentIndexes.length) {
267
            return null;
268
        }
269
        PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
270
        float start = seg.getLength();
271
        float end;
272
        if (index == segmentIndexes.length - 1) {
273
            end = pathLength;
274
        } else {
275
            seg = (PathSegment) segments.get(segmentIndexes[index + 1]);
276
            end = seg.getLength();
277
        }
278
        return pointAtLength(start + (end - start) * proportion);
279
    }
280

    
281
    /**
282
     * Returns the point that is at the given length along the path.
283
     * @param length The length along the path
284
     * @return The point at the given length
285
     */
286
    @Override
287
    public Point2D pointAtLength(float length) {
288
        int upperIndex = findUpperIndex(length);
289
        if (upperIndex == -1) {
290
            // Length is off the end of the path.
291
            return null;
292
        }
293

    
294
        PathSegment upper = (PathSegment) segments.get(upperIndex);
295

    
296
        if (upperIndex == 0) {
297
            // Length was probably zero, so return the upper point.
298
            return new Point2D.Float(upper.getX(), upper.getY());
299
        }
300

    
301
        PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
302

    
303
        // Now work out where along the line would be the length.
304
        float offset = length - lower.getLength();
305

    
306
        // Compute the slope.
307
        double theta = Math.atan2(upper.getY() - lower.getY(),
308
                                  upper.getX() - lower.getX());
309

    
310
        float xPoint = (float) (lower.getX() + offset * Math.cos(theta));
311
        float yPoint = (float) (lower.getY() + offset * Math.sin(theta));
312

    
313
        return new Point2D.Float(xPoint, yPoint);
314
    }
315

    
316
    /**
317
     * Returns the slope of the path at the specified length.
318
     * @param index The segment number
319
     * @param proportion The proportion along the given segment
320
     * @return the angle in radians, in the range [-{@link Math#PI},
321
     *         {@link Math#PI}].
322
     */
323
    public float angleAtLength(int index, float proportion) {
324
        if (!initialised) {
325
            initialise();
326
        }
327
        if (index < 0 || index >= segmentIndexes.length) {
328
            return 0f;
329
        }
330
        PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
331
        float start = seg.getLength();
332
        float end;
333
        if (index == segmentIndexes.length - 1) {
334
            end = pathLength;
335
        } else {
336
            seg = (PathSegment) segments.get(segmentIndexes[index + 1]);
337
            end = seg.getLength();
338
        }
339
        return angleAtLength(start + (end - start) * proportion);
340
    }
341

    
342
    /**
343
     * Returns the slope of the path at the specified length.
344
     * @param length The length along the path
345
     * @return the angle in radians, in the range [-{@link Math#PI},
346
     *         {@link Math#PI}].
347
     */
348
    @Override
349
    public float angleAtLength(float length) {
350
        int upperIndex = findUpperIndex(length);
351
        if (upperIndex == -1) {
352
            // Length is off the end of the path.
353
            return 0f;
354
        }
355

    
356
        PathSegment upper = (PathSegment) segments.get(upperIndex);
357

    
358
        if (upperIndex == 0) {
359
            // Length was probably zero, so return the angle between the first
360
            // and second segments.
361
            upperIndex = 1;
362
        }
363

    
364
        PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
365

    
366
        // Compute the slope.
367
        return (float) Math.atan2(upper.getY() - lower.getY(),
368
                                  upper.getX() - lower.getX());
369
    }
370

    
371
    /**
372
     * Returns the index of the path segment that bounds the specified
373
     * length along the path.
374
     * @param length The length along the path
375
     * @return The path segment index, or -1 if there is not such segment
376
     */
377
    public int findUpperIndex(float length) {
378
        if (!initialised) {
379
            initialise();
380
        }
381

    
382
        if (length < 0 || length > pathLength) {
383
            // Length is outside the path, so return -1.
384
            return -1;
385
        }
386

    
387
        // Find the two segments that are each side of the length.
388
        int lb = 0;
389
        int ub = segments.size() - 1;
390
        while (lb != ub) {
391
            int curr = (lb + ub) >> 1;
392
            PathSegment ps = (PathSegment) segments.get(curr);
393
            if (ps.getLength() >= length) {
394
                ub = curr;
395
            } else {
396
                lb = curr + 1;
397
            }
398
        }
399
        for (;;) {
400
            PathSegment ps = (PathSegment) segments.get(ub);
401
            if (ps.getSegType() != PathIterator.SEG_MOVETO
402
                    || ub == segments.size() - 1) {
403
                break;
404
            }
405
            ub++;
406
        }
407

    
408
        int upperIndex = -1;
409
        int currentIndex = 0;
410
        int numSegments = segments.size();
411
        while (upperIndex <= 0 && currentIndex < numSegments) {
412
            PathSegment ps = (PathSegment) segments.get(currentIndex);
413
            if (ps.getLength() >= length
414
                    && ps.getSegType() != PathIterator.SEG_MOVETO) {
415
                upperIndex = currentIndex;
416
            }
417
            currentIndex++;
418
        }
419
        return upperIndex;
420
    }
421

    
422
    /**
423
     * A {@link PathIterator} that returns only the next path segment from
424
     * another {@link PathIterator}.
425
     */
426
    protected static class SingleSegmentPathIterator implements PathIterator {
427

    
428
        /**
429
         * The path iterator being wrapped.
430
         */
431
        protected PathIterator it;
432

    
433
        /**
434
         * Whether the single segment has been passed.
435
         */
436
        protected boolean done;
437

    
438
        /**
439
         * Whether the generated move command has been returned.
440
         */
441
        protected boolean moveDone;
442

    
443
        /**
444
         * The x coordinate of the next move command.
445
         */
446
        protected double x;
447

    
448
        /**
449
         * The y coordinate of the next move command.
450
         */
451
        protected double y;
452

    
453
        /**
454
         * Sets the path iterator to use and the initial SEG_MOVETO command
455
         * to return before it.
456
         */
457
        public void setPathIterator(PathIterator it, double x, double y) {
458
            this.it = it;
459
            this.x = x;
460
            this.y = y;
461
            done = false;
462
            moveDone = false;
463
        }
464

    
465
        public int currentSegment(double[] coords) {
466
            int type = it.currentSegment(coords);
467
            if (!moveDone) {
468
                coords[0] = x;
469
                coords[1] = y;
470
                return SEG_MOVETO;
471
            }
472
            return type;
473
        }
474

    
475
        public int currentSegment(float[] coords) {
476
            int type = it.currentSegment(coords);
477
            if (!moveDone) {
478
                coords[0] = (float) x;
479
                coords[1] = (float) y;
480
                return SEG_MOVETO;
481
            }
482
            return type;
483
        }
484

    
485
        public int getWindingRule() {
486
            return it.getWindingRule();
487
        }
488

    
489
        public boolean isDone() {
490
            return done || it.isDone();
491
        }
492

    
493
        public void next() {
494
            if (!done) {
495
                if (!moveDone) {
496
                    moveDone = true;
497
                } else {
498
                    it.next();
499
                    done = true;
500
                }
501
            }
502
        }
503
    }
504

    
505
    /**
506
     * A single path segment in the flattened version of the path.
507
     * This is a local helper class. PathSegment-objects are stored in
508
     * the {@link PathLength#segments} - list.
509
     * This is used as an immutable value-object.
510
     */
511
    protected static class PathSegment {
512

    
513
        /**
514
         * The path segment type.
515
         */
516
        protected final int segType;
517

    
518
        /**
519
         * The x coordinate of the path segment.
520
         */
521
        protected float x;
522

    
523
        /**
524
         * The y coordinate of the path segment.
525
         */
526
        protected float y;
527

    
528
        /**
529
         * The length of the path segment, accumulated from the start.
530
         */
531
        protected float length;
532

    
533
        /**
534
         * The index of the original path segment this flattened segment is a
535
         * part of.
536
         */
537
        protected int index;
538

    
539
        /**
540
         * Creates a new PathSegment with the specified parameters.
541
         * @param segType The segment type
542
         * @param x The x coordinate
543
         * @param y The y coordinate
544
         * @param len The segment length
545
         * @param idx The index of the original path segment this flattened
546
         *            segment is a part of
547
         */
548
        PathSegment(int segType, float x, float y, float len, int idx) {
549
            this.segType = segType;
550
            this.x = x;
551
            this.y = y;
552
            this.length = len;
553
            this.index = idx;
554
        }
555

    
556
        /**
557
         * Returns the segment type.
558
         */
559
        public int getSegType() {
560
            return segType;
561
        }
562

    
563
        /**
564
         * Returns the x coordinate of the path segment.
565
         */
566
        public float getX() {
567
            return x;
568
        }
569

    
570
        /**
571
         * Sets the x coordinate of the path segment.
572
         */
573
        public void setX(float v) {
574
            x = v;
575
        }
576

    
577
        /**
578
         * Returns the y coordinate of the path segment.
579
         */
580
        public float getY() {
581
            return y;
582
        }
583

    
584
        /**
585
         * Sets the y coordinate of the path segment.
586
         */
587
        public void setY(float v) {
588
            y = v;
589
        }
590

    
591
        /**
592
         * Returns the length of the path segment.
593
         */
594
        public float getLength() {
595
            return length;
596
        }
597

    
598
        /**
599
         * Sets the length of the path segment.
600
         */
601
        public void setLength(float v) {
602
            length = v;
603
        }
604

    
605
        /**
606
         * Returns the segment index.
607
         */
608
        public int getIndex() {
609
            return index;
610
        }
611

    
612
        /**
613
         * Sets the segment index.
614
         */
615
        public void setIndex(int v) {
616
            index = v;
617
        }
618
    }
619
}