tillnagel / unfolding

A library to create interactive maps and geovisualizations in Processing and Java
http://unfoldingmaps.org
Other
477 stars 245 forks source link

Issue with using isInside method of the SimpleLinesMarker Class #177

Closed bchen0867 closed 4 years ago

bchen0867 commented 4 years ago

I am trying to implement a feature to allow a user to click a SimpleLinesMarker to select and highlight it. I used the isInside method of the SimpleLinesMarker Class.

Below is the related information in the JavaDoc of UnfoldingMaps:

isInside
protected boolean isInside(float checkX,
                           float checkY,
                           java.util.List<? extends processing.core.PVector> vectors)
Checks whether the position is within the border of the vectors. Uses a polygon containment algorithm. This method is used for both ScreenPosition as well as Location checks.
Parameters:
    checkX - The x position to check if inside.
    checkY - The y position to check if inside.
    vectors - The vectors of the polygon
Returns:
    True if inside, false otherwise.

And here is how I tried to implement it in my code: (RouteMarker class is a subclass of SimpleLinesMarker.)

    /** select the first route marker is clicked by the mouse*/
    private void checkRouteForClick(List<Marker> markers)
    {
        // Abort if there's already a marker clicked
        if (lastClicked != null) {
            return;
        }

        for (Marker m : markers) 
        {
            RouteMarker marker = (RouteMarker) m;
            float w = marker.getStrokeWeight()/2;
            List<PVector> vectors = new ArrayList<>(); 
            vectors.add(new PVector (marker.getScreenPosition(map).x, marker.getScreenPosition(map).y - w));
            vectors.add(new PVector (marker.getScreenPosition(map).x - w, marker.getScreenPosition(map).y));
            vectors.add(new PVector (marker.getScreenPosition(map).x + w, marker.getScreenPosition(map).y));
            vectors.add(new PVector (marker.getScreenPosition(map).x, marker.getScreenPosition(map).y + w));

            if (marker.isInside(mouseX, mouseY, vectors)) { // need to change the isInside method to the vector version 
                lastClicked = marker;
                marker.setClicked(true);
                return;
            }
        }
    }

This code gives this error: "The method isInside(float, float, List<? extends PVector>) from the type AbstractShapeMarker is not visible."

I am not sure if I'm on the right path to use this method to add the wanted feature (let the user click any part of the line to select the SimpleLinesMarker), and I'm also not sure how to implement it correctly. Any help would be very much appreciated! Vielen Dank!

I've also tried to use getFirstHitMarker method of Map, but it only catches AbstractMarker.

Other Info: Java SE 14.0.1 Eclipse IDE for Java Developers, Version: 2020-06 (4.16.0)

tillnagel commented 4 years ago

isInside(float, float, List<? extends PVector>) is protected and should be used internally. Only isInside(UnfoldingMap, float, float) and the isInsideByLocation methods are public.

You could implement the isInside(float, float, List<? extends PVector>) in your RouteMarker.

Lastly, alas, Unfolding does not provide a function to calculate distance from a point to a line.

bchen0867 commented 4 years ago

Thank you for your help! I got it working as I wanted.

Here is how I fixed my code in case someone else wants to implement the same feature.

Firstly, I've implemented the isInside(float, float, List<? extends PVector>) to be the result of a boolean function checkForClick(int mouseX, int mouseY, List<PVector> vectors) in my RouteMarker class.

public class RouteMarker extends SimpleLinesMarker
{
        // constructor and other functions not shown here
    public boolean checkForClick(int mouseX, int mouseY, List<PVector> vectors) {

        return this.isInside(mouseX, mouseY, vectors);
    }
}

Then, I fixed the get vector data section using getLocations() function before getScreenPosition(). In my old code, I used getScreenPosition() directly on the RouteMarker (which extends SimpleLinesMarker, and can't resolve to one location to return its screen position, thus returning to 0 ).

    /** select the first route marker is clicked by the mouse*/
    private void checkRouteForClick(List<Marker> markers)
    {
        for (Marker m: markers) 
        {
            RouteMarker marker = (RouteMarker) m;
            float w = marker.getStrokeWeight()*20; // increase the weight for test
            List<ScreenPosition> posList  = marker.getLocations().stream().map(b -> map.getScreenPosition(b)).collect(Collectors.toList());

            float x1 = min(posList.get(0).x, posList.get(1).x);
            float y1 = min(posList.get(0).y, posList.get(1).y);
            float x2 = max(posList.get(0).x, posList.get(1).x);
            float y2 = max(posList.get(0).y, posList.get(1).y);

            List<PVector> vectors = new ArrayList<>(); 
            vectors.add(new PVector (x1 - w, y1 - w));
            vectors.add(new PVector (x1 - w, y1 + w));
            vectors.add(new PVector (x2 + w, y2 + w));
            vectors.add(new PVector (x2 + w, y2 - w));
            System.out.println(vectors);

            pushStyle();
            stroke(255);

            if (marker.checkForClick(mouseX, mouseY, vectors)) { // need to change the isInside method to the vector version 
                lastClicked = marker;
                marker.setClicked(true);
                System.out.println("Route Marker clicked");
                System.out.println(marker.getProperties()); // test for clicking the route
                return;
            }
        }
    }
    @Override
    public void mouseClicked()
    {   
        // reset lastClicked to make sure only one is set Clicked per time
        if (lastClicked != null) {
            lastClicked.setClicked(false);
            lastClicked = null;
        }
        checkRouteForClick(showRoutes());
    }
tillnagel commented 4 years ago

Good to hear and thanks for sharing your code!

(NB for others: This seems to check for a bounding box around the line, resulting in an angle dependent click area.)