All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geolatte.maprenderer.java2D.PerpendicularOffsetStroke Maven / Gradle / Ivy

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