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 | 43156 | jjdelcerro | /**
|
---|---|---|---|
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 | } |