Best way to draw a path traveled

1.8k Views Asked by At

I'm making a application to track a veicle based in GPS coordinates.

I created a SurfaceView to draw the field, vehicle and the path (route) for him.

The result looked like this:

enter image description here

The black dots represent the coming of GPS coordinates, and blue rectangles would be the area covered by the path traveled. (the width of the path is configurable)

The way I'm drawing with blue rectangles (this is my question) which are the area covered by the path traveled. (the width of the path is configurable)

With that I need to overcome some situation.

  • I need to calculate the field's rotation angle so that the path always get left behind. (completed)
  • I need to calculate the angle of rotation of each rectangle so they are facing towards the vehicle. (completed)

In the future I will need:

  • Detect when the vehicle passes twice in the same place. (based on the path traveled)
  • Calculate the area (m²) all traveled by the vehicle.

I would like some tips for draw this path.

My code:

public void draw(Canvas canvas) {
    Log.d(getClass().getSimpleName(), "draw");
    canvas.save();

    // translate canvas to vehicle positon
    canvas.translate((float) center.cartesian(0), (float) center.cartesian(1));

    float fieldRotation = 0;

    if (trackerHistory.size() > 1) {
         /*
        Before drawing the way, only takes the last position and finds the angle of rotation of the field.
         */
        Vector lastPosition = new Vector(convertToTerrainCoordinates(lastPoint));
        Vector preLastPosition = new Vector(convertToTerrainCoordinates(preLastPoint));
        float shift = (float) lastPosition.distanceTo(preLastPosition);

        /*
        Having the last coordinate as a triangle, 'preLastCoord' saves the values of the legs, while 'shift' is the hypotenuse
        */
        // If the Y offset is negative, then the opposite side is the Y displacement
        if (preLastPosition.cartesian(1) < 0) {
            // dividing the opposite side by hipetenusa, we have the sine of the angle that must be rotated.
            double sin = preLastPosition.cartesian(1) / shift;

            // when Y is negative, it is necessary to add or subtract 90 degrees depending on the value of X
            // The "Math.asin()" calculates the radian arc to the sine previously calculated.
            // And the "Math.toDegress()" converts degrees to radians from 0 to 360.
            if (preLastPosition.cartesian(0) < 0) {
                fieldRotation = (float) (Math.toDegrees(Math.asin(sin)) - 90d);
            } else {
                fieldRotation = (float) (Math.abs(Math.toDegrees(Math.asin(sin))) + 90d);
            }
        }
        // if not, the opposite side is the X offset
        else {
            // dividing the opposite side by hipetenusa have the sine of the angle that must be rotated.
            double senAngulo = preLastPosition.cartesian(0) / shift;

            // The "Math.asin()" calculates the radian arc to the sine previously calculated.
            // And the "Math.toDegress()" converts degrees to radians from 0 to 360.
            fieldRotation = (float) Math.toDegrees(Math.asin(senAngulo));
        }
    }

    final float dpiTrackerWidth = Navigator.meterToDpi(trackerWidth); // width of rect

    final Path positionHistory = new Path(); // to draw the route
    final Path circle = new Path(); // to draw the positions

    /*
    Iterate the historical positions and draw the path
    */
    for (int i = 1; i < trackerHistory.size(); i++) {
        Vector currentPosition = new Vector(convertToTerrainCoordinates(trackerHistory.get(i))); // vector with X and Y position
        Vector lastPosition = new Vector(convertToTerrainCoordinates(trackerHistory.get(i - 1))); // vector with X and Y position

        circle.addCircle((float) currentPosition.cartesian(0), (float) currentPosition.cartesian(1), 3, Path.Direction.CW);
        circle.addCircle((float) lastPosition.cartesian(0), (float) lastPosition.cartesian(1), 3, Path.Direction.CW);

        if (isInsideOfScreen(currentPosition.cartesian(0), currentPosition.cartesian(1)) ||
                isInsideOfScreen(lastPosition.cartesian(0), lastPosition.cartesian(1))) {
            /*
            Calcule degree by triangle sides
             */
            float shift = (float) currentPosition.distanceTo(lastPosition);
            Vector dif = lastPosition.minus(currentPosition);
            float sin = (float) (dif.cartesian(0) / shift);

            float degress = (float) Math.toDegrees(Math.asin(sin));

            /*
            Create a Rect to draw displacement between two coordinates
             */
            RectF rect = new RectF();
            rect.left = (float) (currentPosition.cartesian(0) - (dpiTrackerWidth / 2));
            rect.right = rect.left + dpiTrackerWidth;
            rect.top = (float) currentPosition.cartesian(1);
            rect.bottom = rect.top - shift;

            Path p = new Path();
            Matrix m = new Matrix();
            p.addRect(rect, Path.Direction.CCW);
            m.postRotate(-degress, (float) currentPosition.cartesian(0), (float) currentPosition.cartesian(1));
            p.transform(m);

            positionHistory.addPath(p);
        }
    }

    // rotates the map to make the route down.
    canvas.rotate(fieldRotation);

    canvas.drawPath(positionHistory, paint);
    canvas.drawPath(circle, paint2);

    canvas.restore();
}

My goal is to have something like this application: https://play.google.com/store/apps/details?id=hu.zbertok.machineryguide (but only in 2D for now)

EDIT:

To clarify a bit more my doubts:

  • I do not have much experience about it. I would like a better way to draw the path. With rectangles it was not very good. Note that the curves are some empty spaces.
  • Another point is the rotation of rectangles, I'm rotating them at the time of drawing. I believe this will make it difficult to detect overlaps
  • I believe I need math help for the rotation of objects and overlapping detection. And it also helps to draw the path of a filled shape.
2

There are 2 best solutions below

2
On BEST ANSWER

After some research time, I came to a successful outcome. I will comment on my thoughts and how was the solution.

As I explained in question, along the way I have the coordinates traveled by the vehicle, and also a setting for the width of the path should be drawn.

Using LibGDX library is ready a number of features, such as the implementation of a "orthographic camera" to work with positioning, rotation, etc.

With LibGDX I converted GPS coordinates on my side points to the road traveled. Like this: enter image description here

The next challenge was to fill the path traveled. First I tried using rectangles, but the result was as shown in my question.

So the solution was to trace triangles using the side of the path as vertices. Like this: enter image description here

Then simply fill in the triangles. Like this: enter image description here

Finally, using Stencil, I set up OpenGL to highlight overlaps. Like this: enter image description here

Other issues fixed:

  • To calculate the covered area, simply calculate the area of existing triangles along the path.
  • To detect overlapping, just check if the current position of the vehicle is within a triangle.

Thanks to:

  • AlexWien for the attention and for their time.
  • Conner Anderson by videos of LibGDX
  • And a special thanks to Luis Eduardo for knowledge, helped me a lot.

The sample source code.

8
On

Usually such a path is drawn using a "path" method from the graphics lib. In that lib you can create a polyline, and give a line width. You further specify how corners are filled. (BEVEL_JOIN, MITTER_JOIN)

The main question is wheter the path is drawn while driving or afterwords. Afterwords is no problem. To draw while driving might be a bit tricky to avoid to redraw the path each second.

When using the Path with moveTo and lineTo to create a polyline, then you can set a line width, and the graphics lib will do that all for you. Then there will be no gaps, since it is a poly line.