
org.geolatte.maprenderer.java2D.PerpendicularOffsetStroke Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of geolatte-maprenderer Show documentation
Show all versions of geolatte-maprenderer Show documentation
A renderer with a geo-centered API.
The newest version!
/*
* This file is part of the GeoLatte project.
*
* GeoLatte is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GeoLatte is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GeoLatte. If not, see .
*
* Copyright (C) 2010 - 2011 and Ownership of code is shared by:
* Qmino bvba - Esperantolaan 4 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.maprenderer.java2D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.*;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
//Since the DuctusShapeRenderer in Oracle's Java2D implementation handles BasicStroke and any other strokes differently,
// the approach here is not ideal. It produces different rendering results from BasicStroke when pOffset == 0.
//therefore:
//TODO -- move the calculation of the offset to a the GeometryPathIterator.
public class PerpendicularOffsetStroke implements Stroke {
private static Logger LOGGER = LoggerFactory.getLogger(PerpendicularOffsetStroke.class);
private static float FLATNESS = .01f;
private float perpendicularOffset = 0f;
private BasicStroke delegate;
public PerpendicularOffsetStroke(float width) {
delegate = new BasicStroke(width);
}
public PerpendicularOffsetStroke(float width, int join, int cap) {
delegate = new BasicStroke(width, cap, join);
}
public PerpendicularOffsetStroke(float width, float offset, int join, int cap) {
delegate = new BasicStroke(width, cap, join);
this.perpendicularOffset = offset;
}
public PerpendicularOffsetStroke(float width, float offset) {
delegate = new BasicStroke(width);
this.perpendicularOffset = offset;
}
public PerpendicularOffsetStroke(float width, float offset, int join, int cap, float[] dashArray, float dashOffset) {
delegate = new BasicStroke(width, cap, join, 10f, dashArray, dashOffset);
this.perpendicularOffset = offset;
}
public float getPerpendicularOffset() {
return this.perpendicularOffset;
}
/**
* Creates the stroked shape.
*
* If the perpendicalar offset != 0, then the offset linesegments are joined much like the strategy JOIN_MITER.
*
*
* @param shape
* @return
*/
public Shape createStrokedShape(Shape shape) {
if (Math.abs(this.perpendicularOffset) < Math.ulp(1.f)) {
return delegate.createStrokedShape(shape);
}
GeneralPath result = new GeneralPath();
PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
float points[] = new float[6];
float moveX = 0, moveY = 0;
// point (lasX, lasty) is the point that the PathIterator pointed to in the previous iteration
float lastX = 0, lastY = 0;
// point (thisX, thisY) is the point that the pathIterator currently points to
float thisX = 0, thisY = 0;
// vector (lastOffsetX, lastOffsetY) is the previous offset vector (orthogonal to the vector
// determined by last and before last point)
double lastOffsetX = 0, lastOffsetY = 0;
// vector (offsetX, offsetY) is the current offset vector (orthogonal to the vector
// determined by last and current point)
double offsetX= 0, offsetY = 0;
// the point (loX, loY) is the current point + the previous offset-vector
float loX = 0, loY = 0;
int type = 0;
boolean first = true;
double offset = perpendicularOffset;
while (!it.isDone()) {
type = it.currentSegment(points);
switch (type) {
case PathIterator.SEG_MOVETO:
moveX = lastX = points[0];
moveY = lastY = points[1];
first = true;
break;
case PathIterator.SEG_CLOSE:
points[0] = moveX;
points[1] = moveY;
// Fall into....
case PathIterator.SEG_LINETO:
thisX = points[0];
thisY = points[1];
//(dx,dy) is the vector from last point to the current point
float dx = thisX - lastX;
float dy = thisY - lastY;
// segmentAngle is the angle of the linesegment between last and current points
float segmentAngle = (float) Math.atan2(dy, dx);
LOGGER.debug("segment-angle = " + segmentAngle);
offsetX = offset * Math.cos(segmentAngle + Math.PI / 2.0);
offsetY = offset * Math.sin(segmentAngle + Math.PI / 2.0);
// point (nloX, nloY) is last point + current offset vector
float nloX = (float) (lastX + offsetX);
float nloY = (float) (lastY + offsetY);
if (first) {
moveX = nloX;
moveY = nloY;
result.moveTo(moveX, moveY);
first = false;
} else if (nloX != loX || nloY != loY) {
// the formula for the signed angle between two vectors: ang = atan2(x1*y2-y1*x2,x1*x2+y1*y2
double angleBetweenOffsetVectors = Math.atan2( lastOffsetX*offsetY - lastOffsetY*offsetX, lastOffsetX*offsetX + lastOffsetY*offsetY);
double halfOffsetAngle = angleBetweenOffsetVectors / 2;
//iRadius is the length of the vector along the bisector of the two consecutive offset vectors that starts
// at the last point, and ends in the intersection of the two offset lines.
double iRadius = offset / Math.cos(halfOffsetAngle);
LOGGER.debug("offsetAngle = " + 2.0*halfOffsetAngle);
LOGGER.debug("halfOffsetAngle = " + halfOffsetAngle);
LOGGER.debug("iRadius = " + iRadius);
if (offset > 0 && halfOffsetAngle < -Math.PI /4 ||
offset < 0 && halfOffsetAngle > Math.PI/4) {
//In these cases the offset-lines intersect too far beyond the last point
//corect iRadius
iRadius = offset/ Math.cos(Math.PI/4);
float iloX = lastX + (float)(iRadius * Math.cos(segmentAngle + Math.PI/2 - 2*halfOffsetAngle - Math.signum(offset) * Math.PI/4));
float iloY = lastY + (float)(iRadius * Math.sin(segmentAngle + Math.PI/2 - 2*halfOffsetAngle - Math.signum(offset) * Math.PI/4));
result.lineTo(iloX, iloY);
iloX = lastX + (float)(iRadius * Math.cos(segmentAngle + Math.PI/2 + Math.signum(offset) *Math.PI/4));
iloY = lastY + (float)(iRadius * Math.sin(segmentAngle + Math.PI/2 + Math.signum(offset) *Math.PI/4));
result.lineTo(iloX, iloY);
} else {
float iloX = lastX + (float) (iRadius * Math.cos(segmentAngle + Math.PI/2 - halfOffsetAngle));
float iloY = lastY + (float)(iRadius * Math.sin(segmentAngle + Math.PI/2 - halfOffsetAngle));
result.lineTo(iloX, iloY);
}
}
loX = nloX + dx;
loY = nloY + dy;
lastX = thisX;
lastY = thisY;
lastOffsetX = offsetX;
lastOffsetY = offsetY;
break;
}
it.next();
}
result.lineTo(loX, loY);
return delegate.createStrokedShape(result);
}
//Delegate methods
public float getLineWidth() {
return delegate.getLineWidth();
}
public int getEndCap() {
return delegate.getEndCap();
}
public int getLineJoin() {
return delegate.getLineJoin();
}
public float getMiterLimit() {
return delegate.getMiterLimit();
}
public float[] getDashArray() {
return delegate.getDashArray();
}
public float getDashPhase() {
return delegate.getDashPhase();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy