Permalink
Browse files

Merge pull request #792 from jmarks213/feature/#791

Add directional arrows on polylines
  • Loading branch information...
monsieurtanuki committed Dec 5, 2017
2 parents a69ab61 + 81e870e commit 52531ba590672d30c3fae3eb0d734495505f7bee
@@ -22,6 +22,7 @@
import org.osmdroid.samplefragments.drawing.DrawPolygonHoles;
import org.osmdroid.samplefragments.drawing.DrawPolygonWithoutVerticalWrapping;
import org.osmdroid.samplefragments.drawing.DrawPolygonWithoutWrapping;
import org.osmdroid.samplefragments.drawing.DrawPolylineWithArrows;
import org.osmdroid.samplefragments.drawing.PressToPlot;
import org.osmdroid.samplefragments.drawing.PressToPlotWithoutWrapping;
import org.osmdroid.samplefragments.drawing.SampleDrawPolylineWithoutVerticalWrapping;
@@ -260,9 +261,10 @@ private SampleFactory() {
mSamples.add(PressToPlotWithoutWrapping.class);
mSamples.add(DrawPolygonWithoutVerticalWrapping.class);
mSamples.add(SampleDrawPolylineWithoutVerticalWrapping.class);
mSamples.add(DrawPolylineWithArrows.class);
if (Build.VERSION.SDK_INT >= 9)
mSamples.add(StreetAddressFragment.class);
mSamples.add(StreetAddressFragment.class); //map in a list view
}
public void addSample(Class<? extends BaseSampleFragment> clz) {
@@ -46,6 +46,7 @@ public void setMode(Mode mode) {
Polygon,
PolygonHole
}
protected boolean withArrows=false;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
@@ -136,6 +137,7 @@ private void touch_up() {
case Polyline:
Polyline line = new Polyline();
line.setPoints(geoPoints);
line.setDrawDirectionalArrows(withArrows, true);
line.setOnClickListener(new Polyline.OnClickListener() {
@Override
public boolean onClick(Polyline polyline, MapView mapView, GeoPoint eventPos) {
@@ -0,0 +1,21 @@
package org.osmdroid.samplefragments.drawing;
/**
* created on 11/28/2017.
*https://github.com/osmdroid/osmdroid/issues/791
* @author Alex O'Ree
*/
public class DrawPolylineWithArrows extends SampleDrawPolyline {
@Override
public String getSampleTitle() {
return "Draw a polyline with arrows";
}
@Override
public void addOverlays(){
super.addOverlays();
paint.withArrows=true;
}
}
@@ -17,7 +17,6 @@
import org.osmdroid.events.ZoomEvent;
import org.osmdroid.samplefragments.BaseSampleFragment;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Polygon;
import org.osmdroid.views.overlay.gestures.RotationGestureOverlay;
import static org.osmdroid.samplefragments.events.SampleMapEventListener.df;
@@ -0,0 +1,218 @@
package org.osmdroid.views.overlay;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import org.osmdroid.util.PointL;
import org.osmdroid.util.SegmentClipper;
import org.osmdroid.views.Projection;
import java.util.ArrayList;
/**
* Created by Jason Marks on 12/2/2017.
*/
class ArrowsLinearRing extends LinearRing implements SegmentClipper.SegmentClippable {
private final ArrayList<Path> mDirectionalArrows = new ArrayList<>();
private static final float DEFAULT_ARROW_LENGTH = 20f;
private static final boolean DEFAULT_INVERT_ARROWS = false;
private boolean mDrawDirectionalArrows = false;
private boolean mInvertDirectionalArrows = DEFAULT_INVERT_ARROWS;
private float mDirectionalArrowLength = DEFAULT_ARROW_LENGTH;
public ArrowsLinearRing(final Path pPath) {
super(pPath);
setSegmentClipperChild(this);
}
@Override
public void init() {
super.init();
}
@Override
public void lineTo(final long pX, final long pY) {
if (!mDrawDirectionalArrows) {
super.lineTo(pX, pY);
} else {
if (!getIsNextMove()) {
PointL mPreviousPoint = new PointL(getLatestPathPoint());
addDirectionalArrow(pX, pY, mPreviousPoint.x, mPreviousPoint.y);
}
super.lineTo(pX, pY);
}
}
@Override
void clearPath() {
super.clearPath();
mDirectionalArrows.clear();
}
@Override
PointL buildPathPortion(final Projection pProjection,
final boolean pClosePath, final PointL pOffset){
if (!mDirectionalArrows.isEmpty()) {
mDirectionalArrows.clear();
}
return super.buildPathPortion(pProjection, pClosePath, pOffset);
}
/**
* Update the stroke width
*
* @param strokeWidth
*/
public void setStrokeWidth(final float strokeWidth) {
this.mDirectionalArrowLength = DEFAULT_ARROW_LENGTH + strokeWidth;
}
/**
* A directional arrow is a single arrow drawn in the middle of two points to
* provide a visual cue for direction of movement between the two points.
*
* By default the arrows always point towards the lower index as the list of GeoPoints are
* processed. The direction the arrows point can be inverted.
*
* @param drawDirectionalArrows to enable or disable
* @param invertDirection invert the direction the arrows are drawn. Use null for default value
* @param strokeWidth the current stroke width of the paint that describes the object
*/
void setDrawDirectionalArrows(final boolean drawDirectionalArrows,
final Boolean invertDirection, final float strokeWidth) {
this.mDrawDirectionalArrows = drawDirectionalArrows;
// reset defaults if disabling
if (!drawDirectionalArrows) {
mInvertDirectionalArrows = DEFAULT_INVERT_ARROWS;
return;
}
setStrokeWidth(strokeWidth);
if (invertDirection != null) {
this.mInvertDirectionalArrows = invertDirection;
}
}
/**
* If enabled, draw the directional arrows
*
* @param canvas the canvas to draw on
*/
void drawDirectionalArrows(final Canvas canvas, final Paint mPaint) {
if (mDrawDirectionalArrows) {
if (mDirectionalArrows != null && mDirectionalArrows.size() > 0) {
Paint fillPaint = new Paint(mPaint);
fillPaint.setStyle(Paint.Style.FILL);
for (Path p : mDirectionalArrows) {
canvas.drawPath(p, fillPaint);
}
}
}
}
private void addDirectionalArrow(final long x0, final long y0, final long x1, final long y1) {
PointL screenPoint0 = new PointL(x0, y0);
PointL screenPoint1 = new PointL(x1, y1);
// if the points are really close don't draw an arrow
if (Math.abs(screenPoint0.x - screenPoint1.x) +
Math.abs(screenPoint0.y - screenPoint1.y) <= mDirectionalArrowLength) {
return;
}
if (mInvertDirectionalArrows) {
PointL temp = screenPoint0;
screenPoint0 = screenPoint1;
screenPoint1 = temp;
}
// mid point in projected pixels
PointL screenPoint3 = new PointL();
PointL screenPoint4 = new PointL();
screenPoint3.x = (screenPoint0.x + screenPoint1.x) / 2;
screenPoint3.y = (screenPoint0.y + screenPoint1.y) / 2;
double delta_x = screenPoint1.x - screenPoint0.x;
double delta_y = screenPoint1.y - screenPoint0.y;
float distance = mDirectionalArrowLength;
if (delta_x == 0) {
screenPoint3.x = screenPoint1.x;
screenPoint4.x = screenPoint1.x;
if (screenPoint0.y > screenPoint1.y) {
screenPoint3.y = screenPoint0.y + (int) Math.round(delta_y/2);
screenPoint4.y = screenPoint3.y - (int) distance;
} else {
screenPoint3.y = screenPoint0.y + (int) Math.round(delta_y/2);
screenPoint4.y = screenPoint3.y + (int) distance;
}
} else if (delta_y == 0) {
screenPoint3.y = screenPoint1.y;
screenPoint4.y = screenPoint1.y;
if (screenPoint0.x > screenPoint1.x) {
screenPoint3.x = screenPoint0.x + (int) Math.round(delta_x/2);
screenPoint4.x = screenPoint3.x - (int) distance;
} else {
screenPoint3.x = screenPoint0.x + (int) Math.round(delta_x/2);
screenPoint4.x = screenPoint3.x + (int) distance;
}
} else {
double slope = delta_y / delta_x;
/* The formula will calculate a new X,Y coordinate that is some distance away from
a given coordinate when a slope is known. Distance is the correct way of
thinking about what the number is in regards to the formula used. It can also
be thought of as the length of the squares diagonal.
*/
double r = Math.sqrt(1 + Math.pow(slope, 2));
// move pt3 half the distance of the square so pt 5 and 6 will intersect the midpt
screenPoint3.x = screenPoint3.x + (int) Math.round((distance / 2) / r);
screenPoint3.y = screenPoint3.y + (int) Math.round((distance / 2) * slope / r);
//calculate another point on the line that is distance in px away from the mid point
screenPoint4.x = screenPoint3.x - (int) Math.round((distance / r));
screenPoint4.y = screenPoint3.y - (int) Math.round((distance * slope / r));
}
/* 3rd point in the square
= ( ( x1 + x3 + y1 - y3 ) / 2 , ( x3 - x1 + y1 + y3 ) / 2 )
*/
PointL screenPoint5 = new PointL();
screenPoint5.x =
(screenPoint4.x + screenPoint3.x + screenPoint4.y - screenPoint3.y) / 2;
screenPoint5.y =
(screenPoint3.x - screenPoint4.x + screenPoint4.y + screenPoint3.y) / 2;
/* 4th point in the square
= ( ( x1 + x3 + y3 - y1 ) / 2 , ( x1 - x3 + y1 + y3 ) / 2 )
*/
PointL screenPoint6 = new PointL();
screenPoint6.x =
(screenPoint4.x + screenPoint3.x + screenPoint3.y - screenPoint4.y) / 2;
screenPoint6.y =
(screenPoint4.x - screenPoint3.x + screenPoint3.y + screenPoint4.y) / 2;
Path directionalArrowPath = new Path();
if (screenPoint0.x > screenPoint1.x && screenPoint0.y > screenPoint1.y) {
directionalArrowPath.moveTo(screenPoint5.x, screenPoint5.y);
directionalArrowPath.lineTo(screenPoint3.x, screenPoint3.y);
directionalArrowPath.lineTo(screenPoint6.x, screenPoint6.y);
} else if (screenPoint0.x < screenPoint1.x && screenPoint0.y < screenPoint1.y) {
directionalArrowPath.moveTo(screenPoint5.x, screenPoint5.y);
directionalArrowPath.lineTo(screenPoint4.x, screenPoint4.y);
directionalArrowPath.lineTo(screenPoint6.x, screenPoint6.y);
} else if (screenPoint0.x < screenPoint1.x && screenPoint0.y > screenPoint1.y) {
directionalArrowPath.moveTo(screenPoint5.x, screenPoint5.y);
directionalArrowPath.lineTo(screenPoint4.x, screenPoint4.y);
directionalArrowPath.lineTo(screenPoint6.x, screenPoint6.y);
} else {
directionalArrowPath.moveTo(screenPoint5.x, screenPoint5.y);
directionalArrowPath.lineTo(screenPoint3.x, screenPoint3.y);
directionalArrowPath.lineTo(screenPoint6.x, screenPoint6.y);
}
directionalArrowPath.close();
mDirectionalArrows.add(directionalArrowPath);
}
}
@@ -45,6 +45,7 @@
private final ArrayList<PointL> mProjectedPoints = new ArrayList<>();
private final PointL mLatestPathPoint = new PointL();
private SegmentClipper mSegmentClipper;
private SegmentClipper.SegmentClippable mSegmentClipperChild;
private final Path mPath;
private boolean mIsNextAMove;
private boolean mPrecomputed;
@@ -55,6 +56,10 @@ public LinearRing(final Path pPath) {
mPath = pPath;
}
void setSegmentClipperChild(final SegmentClipper.SegmentClippable segmentClipperChild) {
mSegmentClipperChild = segmentClipperChild;
}
@Override
public void init() {
mIsNextAMove = true;
@@ -74,6 +79,8 @@ public void lineTo(final long pX, final long pY) {
}
}
boolean getIsNextMove() { return mIsNextAMove; }
void clearPath() {
mOriginalPoints.clear();
mPrecomputed = false;
@@ -84,10 +91,14 @@ void addPoint(final GeoPoint pGeoPoint) {
mPrecomputed = false;
}
PointL getLatestPathPoint() { return mLatestPathPoint; }
ArrayList<GeoPoint> getPoints(){
return mOriginalPoints;
}
ArrayList<PointL> getProjectedPoints() { return mProjectedPoints; }
void setPoints(final List<GeoPoint> points) {
clearPath();
mOriginalPoints.addAll(points);
@@ -117,6 +128,7 @@ PointL buildPathPortion(final Projection pProjection,
}
final List<RectL> segments = new ArrayList<>();
getSegmentsFromProjected(pProjection, mProjectedPoints, segments, pClosePath);
final PointL offset;
if (pOffset != null) {
@@ -341,7 +353,7 @@ private void clip(final List<RectL> pSegments) {
* we can use the same SegmentClipper instead of constructing a new one at each canvas draw.
*/
public void setClipArea(final long pXMin, final long pYMin, final long pXMax, final long pYMax) {
mSegmentClipper = new SegmentClipper(pXMin, pYMin, pXMax, pYMax, this);
mSegmentClipper = new SegmentClipper(pXMin, pYMin, pXMax, pYMax, mSegmentClipperChild);
}
/**
Oops, something went wrong.

0 comments on commit 52531ba

Please sign in to comment.