Android Question Polygon around multiple lat/lon points

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I need to write some code to get a polygon (list of lat/lng points) that surrounds a number of lat/lng points on a map.
This polygon needs to surround the points tightly, so a simple rectangle (which would be very easy to code) won't do.
This link looks promising:
https://github.com/mapbox/concaveman
And I wonder if maybe somebody of this group has already coded this for B4A.
In that case I would be very interested to see that code.

RBS
 

MasterGy

Member
Licensed User
@MasterGy

I liked to work with B4J and so I transferred your code. It works perfectly - and fast, 2 to 20 msecs per hull computation.
Your code is compact, and I can read each line. But for the life of me, I can't figure out how you're doing it - I'll study it more.

Can you outline the algorithm you use?
Thanks Williams!

And thanks for the interest, I'll try to describe how I started.

First of all, what is the smallest polygon that encloses the points? I considered this an important starting point because there is only 1 solution. So I'll start with that. This is 'step1' in the attached drawing.
It looks for the highest point Y, and compares the angle between the other points (atan2). The reference angle of the next point found will be the angle of the previous section. It measures up to that. Accordingly, it continues to search for the point with the smallest angle difference. And so on. If the next found point is the starting point, then the fence construction is over.

After that, I started experimenting with what method could be used to insert another fence point between two adjacent fence points. I tried several variations.
-fence-Points from size based on distance,
- the distance from the geometric center between two fence points,
-distance from the section (i.e. distance parallel to the section)

It worked with all of them, but they had a common flaw: even with a very small 'accurary', there were many points that, based on the conditions, were true for two sections. So then whoever finds it first will be inserted? Therefore, the direction of the investigation was visible in the section construction, they were not aesthetic. In addition, if the new fence section was installed in a non-ideal order, pruning also occurred.

I was wondering what the condition could be for which 1 point could correspond to only one section of the fence, or none at all. 2 or more compliances are no longer good!
The basis of the idea is that the sum of the 2 sides of a triangle can never be less than or equal to the 3rd side of the triangle. Therefore, as long as there is no parallel fence wall facing us (like the two sides of an i-letter), and as long as 'accurary' is not too much, 1 point will only correspond to one section. It is clearly visible in the drawing. the length of the fence section multiplied by 'accurary'. And if this value is smaller than the sum of the distances of the two points of the fence section of the examined point, then it is suitable. And there is a good chance that it will suit him. The higher the 'accurary', the deeper it will search, the further it will go.

So you run this along the fence. And again and again. Until you finally find no more free points. If there is no other point that fits, then the work is finished.

However, the larger the 'accurary', the greater the angle two sections will form, and in this case it may happen that the new fence sections are formed in such a sequence that they intersect each other. It's wrong, ugly, and confusing. In order for the thing to work even with a large 'accurary', I added that a new fence section is only created (actually 1 section disappears and 2 new ones are created) if two fence sections do not intersect after installation. 'isinstersection' sub examines the intersection of two sections. In this way, I rule out these errors, and it is also ethically good. The 'isintersection' sub runs many times and has many operations. If we use a small 'accurary', it can be eliminated, and then the processing will be faster. But it has to be very 'accurary'.
 

Attachments

  • steps.jpg
    steps.jpg
    230.5 KB · Views: 56
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
@MasterGy
Thanks, that helps a lot.

Step 1 is finding what is typically called a convex hull. I like your metaphor of a "fence" - as a youth I worked on a farm and spend a lot of time mending fences.
Almost all algorithms for a Concave Hull start with a Convex Hull, so that's good. It is also the basis for the algorithm referenced by the OP. Your code for that is fast and it works.

Step 2 is the tricky part, as your experimentation shows. Deep concave shapes will require a lot of steps. In your code, 100% accuracy is sometimes not enough.
testHull2.png


You'll find a very useful PDF discussing the issues of 'digging' into the Convex Hull.
 
Upvote 0

MasterGy

Member
Licensed User
@MasterGy
Thanks, that helps a lot.

Step 1 is finding what is typically called a convex hull. I like your metaphor of a "fence" - as a youth I worked on a farm and spend a lot of time mending fences.
Almost all algorithms for a Concave Hull start with a Convex Hull, so that's good. It is also the basis for the algorithm referenced by the OP. Your code for that is fast and it works.

Step 2 is the tricky part, as your experimentation shows. Deep concave shapes will require a lot of steps. In your code, 100% accuracy is sometimes not enough.
View attachment 158565

You'll find a very useful PDF discussing the issues of 'digging' into the Convex Hull.
It's great that you tested it, because I didn't even think about that. Indeed, such forms do not work well. You can increase 'accurary' in the code as you like, but it is not a good solution, because the outside and inside of the horseshoe will be different. I will still think of a solution to rule out this problem.
 
Last edited:
Upvote 0

MasterGy

Member
Licensed User
I made another approach.

First, it creates triangles with the "delaunay triangulation" algorithm, and I record the segments separately. Delaunay in short: examines all points in groups of 3. If the currently examined 3 points are found in the circle, and there is not a single point in it, then a triangle is created there. The process is slow, but it makes nice triangles. Adjacent triangles share common segments. The section that appears only once on the list is sure to be located on the perimeter of the top of the point.

From here came the hard part. Recognizing the received fence sections, as there can be several fences. And to put them in order.

The maximum size of the largest section can be set. This allows you to calibrate what areas the points surround. After all, it is a matter of interpretation what we should see in a cloud of points.


I put 'fence_maker' in a separate code module so that it can be used easily.

In your own program, preferably do this at the very beginning:
fence_maker.initialise(pointmax, fencemax)

This creates the arrays you will be working with. Enter the largest possible size. There won't be much fence, but let's give it a big value, for example fence_maker.initialise(2000,100)

Let's top up the points!
fence_maker.add_points (x,y)


Fence search:
fence_maker.way(maxlineheight)

This will start the calculation. The parameter specifies the maximum length of the fence section.

Once the calculation is complete, we can query:
fence_maker.N That's how many points we have uploaded
fence_maker_points(index,e) 'index-point index e: 0 or 1 . X or Y


fence_maker.fence_c This returns how many fences have been made
fence_maker.fence_length(n) n-fence length
fence_maker.fence(index,n) 'index-fence index , element n. We get the index of fence_maker_points() loaded

fence_maker.triangle_c 'so many triangles have been made
fence_maker_triangles(index,e) index-triangle index e 0.,1.2. element is the indices of the points forming the triangle (fence_maker_points)

fence_maker.lines_c 'the segments that make up the triangles
fence_maker.lines(index,e) index-section index, e:0.,1. point indices 2: how many triangles use this section?

fence_maker.limits(e) e:0,1,2 points X minimum, maximum, extent 3,4,5 points X minimum, maximum, extent
fence_maker.lonely_c so many points are not connected to any triangle. if there is a point to which the distance of the nearest point is greater than the 'maxlineheight' defined at the beginning of the generation, then there will be points that are lonely. This returns the number of these.


IMPORTANT NOTE! Sometimes we get several fences. Multiple shapes or a fence within a shape. The index 0 'fence' will always be the outer fence. So if the layout within the figure is not important to us, and we are only interested in the edge of the figure, then the index 0 is 'fence' (fence_maker.fence(0,p) , fence_maker.fence_length(0))



fance_maker.lonely(index) boolean index- point index - returns whether a point is lonely or not. (is it part of a triangle or not)


If we only want to create triangles, we can also use the command fence_maker.make_triangles (maxlineheight) after initialization. There are no such 'fence' values. 'triangles', 'lines', 'lonely', 'limits' can still be read.

fence_maker bitmapcreation (pwidth , pheight ,show_triangles )
With this command, we get the source of the generation as a BITMAP object. Image size and whether we want to draw triangles on it (true/false)

I tried to put various shapes on the user interface. Multiple shapes, concave, convex, hollow inside.

 

Attachments

  • fence_maker.zip
    21.9 KB · Views: 36
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
My effort based on this Github posting:


1731735174459.png


1731735365669.png


B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    
    Dim pointList As List

    Private cv As Canvas
    Dim nativeMe As JavaObject
    
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    pointList.Initialize
    
    nativeMe = Me
    
    Dim aa As JavaObject
    For i = 0 To 200
        Dim x As Double = Rnd(50, 520).As(Double)
        Dim y As Double = Rnd(200,400).As(Double)
        aa.InitializeNewInstance("b4j.example.main.Point", Array(x, y))
        pointList.Add(aa)
        cv.DrawCircle(x, y, 2, fx.Colors.Black, True, 0)
    Next
    
    
    Dim k As Int = 3
    Dim returnlist As List
    returnlist.Initialize
    returnlist = nativeMe.RunMethod("calculateConcaveHull", Array(pointList, k))
    
    Dim prevX, prevY As Double
    For i = 0 To returnlist.Size - 1
        Dim dd(2) As String = Regex.Split(" ", returnlist.Get(i))
        Dim bbx As Double = dd(0).Replace("(", "")
        Dim bby As Double = dd(1).Replace(")", "")
        cv.DrawCircle(bbx, bby, 2, fx.Colors.Blue, True, 0)

        If i = 0 Then
            prevX = bbx
            prevY = bby
        End If
        If i > 0 Then
            cv.Drawline(prevX, prevY, bbx, bby, fx.Colors.Red, 2)
        End If
        
        prevX = bbx
        prevY = bby
        
        
    Next
    Log("FINISH")

End Sub


#If Java

import javafx.util.Pair;

import java.util.*;


/**
 * ConcaveHull.java - 14/10/16
 *
 * @author Udo Schlegel - Udo.3.Schlegel(at)uni-konstanz.de
 * @version 1.0
 *
 * This is an implementation of the algorithm described by Adriano Moreira and Maribel Yasmina Santos:
 * CONCAVE HULL: A K-NEAREST NEIGHBOURS APPROACH FOR THE COMPUTATION OF THE REGION OCCUPIED BY A SET OF POINTS.
 * GRAPP 2007 - International Conference on Computer Graphics Theory and Applications; pp 61-68.
 *
 * https://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf
 *
 * With help from https://github.com/detlevn/QGIS-ConcaveHull-Plugin/blob/master/concavehull.py
 */


    public static class Point {

        private final Double x;
        private final Double y;

        public Point(Double x, Double y) {
            this.x = x;
            this.y = y;
        }

        public Double getX() {
            return x;
        }

        public Double getY() {
            return y;
        }

        public String toString() {
            return "(" + x + " " + y + ")";
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Point) {
                if (x.equals(((Point) obj).getX()) && y.equals(((Point) obj).getY())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            // http://stackoverflow.com/questions/22826326/good-hashcode-function-for-2d-coordinates
            // http://www.cs.upc.edu/~alvarez/calculabilitat/enumerabilitat.pdf
            int tmp = (int) (y + ((x + 1) / 2));
            return Math.abs((int) (x + (tmp * tmp)));
        }
    }

    private static Double euclideanDistance(Point a, Point b) {
        return Math.sqrt(Math.pow(a.getX() - b.getX(), 2) + Math.pow(a.getY() - b.getY(), 2));
    }

    private static ArrayList<Point> kNearestNeighbors(ArrayList<Point> l, Point q, Integer k) {
        ArrayList<Pair<Double, Point>> nearestList = new ArrayList<>();
        for (Point o : l) {
            nearestList.add(new Pair<>(euclideanDistance(q, o), o));
        }

        Collections.sort(nearestList, new Comparator<Pair<Double, Point>>() {
            @Override
            public int compare(Pair<Double, Point> o1, Pair<Double, Point> o2) {
                return o1.getKey().compareTo(o2.getKey());
            }
        });

        ArrayList<Point> result = new ArrayList<>();

        for (int i = 0; i < Math.min(k, nearestList.size()); i++) {
            result.add(nearestList.get(i).getValue());
        }

        return result;
    }

    private static Point findMinYPoint(ArrayList<Point> l) {
        Collections.sort(l, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return o1.getY().compareTo(o2.getY());
            }
        });
        return l.get(0);
    }

    private static Double calculateAngle(Point o1, Point o2) {
        return Math.atan2(o2.getY() - o1.getY(), o2.getX() - o1.getX());
    }

    private static Double angleDifference(Double a1, Double a2) {
        // calculate angle difference in clockwise directions as radians
        if ((a1 > 0 && a2 >= 0) && a1 > a2) {
            return Math.abs(a1 - a2);
        } else if ((a1 >= 0 && a2 > 0) && a1 < a2) {
            return 2 * Math.PI + a1 - a2;
        } else if ((a1 < 0 && a2 <= 0) && a1 < a2) {
            return 2 * Math.PI + a1 + Math.abs(a2);
        } else if ((a1 <= 0 && a2 < 0) && a1 > a2) {
            return Math.abs(a1 - a2);
        } else if (a1 <= 0 && 0 < a2) {
            return 2 * Math.PI + a1 - a2;
        } else if (a1 >= 0 && 0 >= a2) {
            return a1 + Math.abs(a2);
        } else {
            return 0.0;
        }
    }

    private static ArrayList<Point> sortByAngle(ArrayList<Point> l, Point q, Double a) {
        // Sort by angle descending
        Collections.sort(l, new Comparator<Point>() {
            @Override
            public int compare(final Point o1, final Point o2) {
                Double a1 = angleDifference(a, calculateAngle(q, o1));
                Double a2 = angleDifference(a, calculateAngle(q, o2));
                return a2.compareTo(a1);
            }
        });
        return l;
    }

    private static Boolean intersect(Point l1p1, Point l1p2, Point l2p1, Point l2p2) {
        // calculate part equations for line-line intersection
        Double a1 = l1p2.getY() - l1p1.getY();
        Double b1 = l1p1.getX() - l1p2.getX();
        Double c1 = a1 * l1p1.getX() + b1 * l1p1.getY();
        Double a2 = l2p2.getY() - l2p1.getY();
        Double b2 = l2p1.getX() - l2p2.getX();
        Double c2 = a2 * l2p1.getX() + b2 * l2p1.getY();
        // calculate the divisor
        Double tmp = (a1 * b2 - a2 * b1);

        // calculate intersection point x coordinate
        Double pX = (c1 * b2 - c2 * b1) / tmp;

        // check if intersection x coordinate lies in line line segment
        if ((pX > l1p1.getX() && pX > l1p2.getX()) || (pX > l2p1.getX() && pX > l2p2.getX())
                || (pX < l1p1.getX() && pX < l1p2.getX()) || (pX < l2p1.getX() && pX < l2p2.getX())) {
            return false;
        }

        // calculate intersection point y coordinate
        Double pY = (a1 * c2 - a2 * c1) / tmp;

        // check if intersection y coordinate lies in line line segment
        if ((pY > l1p1.getY() && pY > l1p2.getY()) || (pY > l2p1.getY() && pY > l2p2.getY())
                || (pY < l1p1.getY() && pY < l1p2.getY()) || (pY < l2p1.getY() && pY < l2p2.getY())) {
            return false;
        }

        return true;
    }

    private static boolean pointInPolygon(Point p, ArrayList<Point> pp) {
        boolean result = false;
        for (int i = 0, j = pp.size() - 1; i < pp.size(); j = i++) {
            if ((pp.get(i).getY() > p.getY()) != (pp.get(j).getY() > p.getY()) &&
                    (p.getX() < (pp.get(j).getX() - pp.get(i).getX()) * (p.getY() - pp.get(i).getY()) / (pp.get(j).getY() - pp.get(i).getY()) + pp.get(i).getX())) {
                result = !result;
            }
        }
        return result;
    }



    public static ArrayList<Point> calculateConcaveHull(ArrayList<Point> pointArrayList, Integer k) {

        // the resulting concave hull
        ArrayList<Point> concaveHull = new ArrayList<>();

        // optional remove duplicates
        HashSet<Point> set = new HashSet<>(pointArrayList);
        ArrayList<Point> pointArraySet = new ArrayList<>(set);

        // k has to be greater than 3 to execute the algorithm
        Integer kk = Math.max(k, 3);

        // return Points if already Concave Hull
        if (pointArraySet.size() < 3) {
            return pointArraySet;
        }

        // make sure that k neighbors can be found
        kk = Math.min(kk, pointArraySet.size() - 1);

        // find first point and remove from point list
        Point firstPoint = findMinYPoint(pointArraySet);
        concaveHull.add(firstPoint);
        Point currentPoint = firstPoint;
        pointArraySet.remove(firstPoint);

        Double previousAngle = 0.0;
        Integer step = 2;

        while ((currentPoint != firstPoint || step == 2) && pointArraySet.size() > 0) {

            // after 3 steps add first point to dataset, otherwise hull cannot be closed
            if (step == 5) {
                pointArraySet.add(firstPoint);
            }

            // get k nearest neighbors of current point
            ArrayList<Point> kNearestPoints = kNearestNeighbors(pointArraySet, currentPoint, kk);

            // sort points by angle clockwise
            ArrayList<Point> clockwisePoints = sortByAngle(kNearestPoints, currentPoint, previousAngle);

            // check if clockwise angle nearest neighbors are candidates for concave hull
            Boolean its = true;
            int i = -1;
            while (its && i < clockwisePoints.size() - 1) {
                i++;

                int lastPoint = 0;
                if (clockwisePoints.get(i) == firstPoint) {
                    lastPoint = 1;
                }

                // check if possible new concave hull point intersects with others
                int j = 2;
                its = false;
                while (!its && j < concaveHull.size() - lastPoint) {
                    its = intersect(concaveHull.get(step - 2), clockwisePoints.get(i), concaveHull.get(step - 2 - j), concaveHull.get(step - 1 - j));
                    j++;
                }
            }

            // if there is no candidate increase k - try again
            if (its) {
                return calculateConcaveHull(pointArrayList, k + 1);
            }

            // add candidate to concave hull and remove from dataset
            currentPoint = clockwisePoints.get(i);
            concaveHull.add(currentPoint);
            pointArraySet.remove(currentPoint);

            // calculate last angle of the concave hull line
            previousAngle = calculateAngle(concaveHull.get(step - 1), concaveHull.get(step - 2));

            step++;

        }

        // Check if all points are contained in the concave hull
        Boolean insideCheck = true;
        int i = pointArraySet.size() - 1;

        while (insideCheck && i > 0) {
            insideCheck = pointInPolygon(pointArraySet.get(i), concaveHull);
            i--;
        }

        // if not all points inside -  try again
        if (!insideCheck) {
            return calculateConcaveHull(pointArrayList, k + 1);
        } else {
            return concaveHull;
        }

    }




#End If
 

Attachments

  • b4jConcaveHull.zip
    4.7 KB · Views: 34
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
The attached B4J project will draw something like this (defining 4 seperate but adjacent areas in the code and a hull being drawn for each of the defined areas):

1731757102603.png


It is drawn in real time so that you can see how it is actually being drawn.
 

Attachments

  • b4jConcaveHull_2.zip
    6.1 KB · Views: 34
Last edited:
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Thanks, that sounds like a simple approach and it will be an interesting exercise to code that.
Just 2 questions spring to mind, related to the Java code in the mentioned link:
https://github.com/mapbox/concaveman
1. Will it be quicker to translate that Java code to B4A code?
2. What will run quicker, code based on your idea or code based on that Java code?
I am away for 5 days from today, but will give both a go and report back.

RBS
I think I have this all worked out.
For now I have gone with code as in this link:
https://github.com/mapbox/concaveman

This is the code as I have it now and that is all working fine.
As I just need a return list of lat/lng types based on a passed list of map lat/lng points I have
left out all the bitmap related code.

In a code module called MapUtilities:

B4X:
Sub Process_Globals
    
    Type TMapLatLng(fLat As Double, fLng As Double)
    Dim iMapPoints As Int ' Pontok száma
    Dim arrPoints() As TMapLatLng
    Dim arrHull() As Int
    Dim arrbHull_Busy() As Boolean
    Dim iHull_C As Int
    Dim pi As Float = 3.14159265
    'Dim arrLimits(6) As Double 'not needed anymore
    Dim bWorking As Boolean

End Sub

'return a TLatLng from lat/lng
Public Sub initLatLng(aLat As Double,aLng As Double) As TMapLatLng
    Dim ll As TMapLatLng
    ll.Initialize
    ll.fLat=validLat(aLat)
    ll.fLng=validLng(aLng)
    Return ll
End Sub

'ConcaveMan code
'-------------------------------------------------------------------------------------------

Sub Reset(iPoints As Int)
    
    Dim arrHull(iPoints) As Int
    Dim arrbHull_Busy(iPoints) As Boolean
    Dim arrPoints(iPoints) As TMapLatLng
    'Dim arrLimits(6) As Double 'not needed anymore
    
    iHull_C = 0
    iMapPoints = iPoints
    bWorking = False
    
End Sub

Sub GetBorderPoints(lstMapLatLonPoints As List, fAcc As Double) As List
    
    Dim i As Int
    Dim fMinY As Double = 99999999
    Dim fRang As Double  = 2 * pi
    Dim iMinAng_Ind As Int
    Dim iMinAng_NoLimit_Ind As Int
    Dim fMinAng_Ang As Double
    Dim fMinAng_NoLimit_Ang As Double
    Dim fMinAng_NoLimit_Dis  As Double
    Dim fDis As Double
    Dim fDis0 As Double
    Dim fDis1 As Double
    Dim fang As Double
    Dim iAktP As Int
    
    Dim bChange As Boolean
    Dim bNoOK As Boolean

    Dim iH1 As Int
    Dim iH2 As Int
    Dim iMinDis_Ind As Int
    Dim fMinDis As Double
    Dim fDis0 As Double
    Dim fDis1 As Double
    Dim fDis3 As Double
    Dim fDis4 As Double
    
    Dim tLL As TMapLatLng
    Dim lstBorderPoints As List
    
    Reset(lstMapLatLonPoints.Size)
    
    'this used to be done by Sub AddPoints
    '-------------------------------------
    For i = 0 To lstMapLatLonPoints.Size - 1
        tLL = lstMapLatLonPoints.Get(i)
        arrPoints(i) = tLL
    Next

    bWorking = True
    
    For i = 0 To iMapPoints - 1
        If arrPoints(i).fLat < fMinY Then
            iAktP = i
            fMinY = arrPoints(i).fLat
        End If
    Next

    ' A legalsó pontot kiemelni (piros szín)
    Add_Hull(iAktP, 0)

    '-------------------------------------------------------------- STANDARD FRAME
    Do While True
        'keresse a legkozelebbit,aminek a legkisebb a szoge,
        iMinAng_Ind = -1
        fMinAng_Ang = 999999

        iMinAng_NoLimit_Ind = -1
        fMinAng_NoLimit_Ang = 999999
        fMinAng_NoLimit_Dis = 999999

        For i = 0 To iMapPoints - 1
            If i = iAktP Then Continue
            fDis0 = arrPoints(i).fLng - arrPoints(iAktP).fLng
            fDis1 = arrPoints(i).fLat - arrPoints(iAktP).fLat
            fDis = Sqrt(fDis0 * fDis0 + fDis1 * fDis1)
            fang = DifAng(fRang, ATan2(fDis0, fDis1))


            If fang < fMinAng_NoLimit_Ang And fDis < fMinAng_NoLimit_Dis Then
                fMinAng_NoLimit_Ang = fang
                fMinAng_NoLimit_Dis = fDis
                iMinAng_NoLimit_Ind = i
            End If

            If fang < fMinAng_Ang Then
                fMinAng_Ang = fang
                iMinAng_Ind = i
            End If
        Next

        If iMinAng_Ind = -1 Then
            iMinAng_Ind = iMinAng_NoLimit_Ind
            fMinAng_Ang = fMinAng_NoLimit_Ang
        End If
        '    Line (points(iAktP, 0), points(iAktP, 1))-(points(iMinAng_Ind, 0), points(iMinAng_Ind, 1))

        If iAktP = arrHull(0) And iHull_C > 2 Then Exit
        Add_Hull(iAktP, iHull_C)

        fRang = ATan2(arrPoints(iAktP).fLng - arrPoints(iMinAng_Ind).fLng, arrPoints(iAktP).fLat - arrPoints(iMinAng_Ind).fLat)
        iAktP = iMinAng_Ind

    Loop

    '-------------------------------------  FRAME RESOLUTION

    Do While True  'For qqw = 0 To 100
        
        bChange = False
        iH1 = 0
        
        Do While True
            iH2 = (iH1 + 1) Mod (iHull_C - 0)

            fMinDis = 999999
            iMinDis_Ind = -1
        
            fDis3 = Sqrt(Power(arrPoints(arrHull(iH2)).fLng - arrPoints(arrHull(iH1)).fLng,2)  + Power((arrPoints(arrHull(iH2)).fLat - arrPoints(arrHull(iH1)).fLat),2))
            
            For ind = 0 To iMapPoints - 1
                If arrbHull_Busy(ind)  Then Continue
                fDis0 = Sqrt(Power(arrPoints(ind).fLng - arrPoints(arrHull(iH1)).fLng, 2)  + Power((arrPoints(ind).fLat - arrPoints(arrHull(iH1)).fLat), 2))
                fDis1 = Sqrt(Power(arrPoints(ind).fLng - arrPoints(arrHull(iH2)).fLng, 2)  + Power((arrPoints(ind).fLat - arrPoints(arrHull(iH2)).fLat), 2))

                fDis4 = fDis0 + fDis1
                If fDis3 * fAcc > fDis4 And (fDis4 < fMinDis) Then
                    fMinDis = fDis4
                    iMinDis_Ind = ind
                End If
            Next

            If iMinDis_Ind <> -1 Then
                bNoOK = Add_Hull(iMinDis_Ind, iH2)                
                If bNoOK  Then
                    iH1 = iH1 + 1
                    bChange = True
                End If
            End If

            iH1 = iH1 + 1
            If iH1 > iHull_C - 1 Then Exit
        Loop
        If bChange = False Then Exit
    Loop

    'this is not needed anymore
    '--------------------------
    'Calc_Limits
    
    bWorking = False
    
    lstBorderPoints.Initialize
    
    For i = 0 To iHull_C - 1
        tLL = initLatLng(arrPoints(arrHull(i)).fLat, arrPoints(arrHull(i)).fLng)
        lstBorderPoints.Add(tLL)
    Next
    
    tLL = initLatLng(arrPoints(arrHull(0)).fLat, arrPoints(arrHull(0)).fLng)
    lstBorderPoints.Add(tLL)
    
    Return lstBorderPoints
    
End Sub

Sub Add_Hull (iInd As Int, iHova As Int ) As Boolean
    
    'vizsgalat
    Dim bOK As Boolean= True
    Dim bFelt As Boolean
    Dim iT0 As Int
    Dim iT1 As Int
    Dim iT3 As Int

    'mentes elkeszitese
    Dim arrmHull(iHull_C + 10) As Int
    
    For t = 0 To iHull_C - 1
        arrmHull(t) = arrHull(t)
    Next

    'beiktat
    iHull_C = iHull_C + 1

    For t = iHull_C - 2 To iHova Step -1
        arrHull(t + 1) = arrHull(t)
    Next

    arrHull(iHova) = iInd
    
    '    GoTo 66
    For iT0 = 0 To iHull_C - 1: iT1 = (iT0 + 1) Mod iHull_C
        For t2 = 0 To iHull_C - 1: iT3 = (t2 + 1) Mod iHull_C
            
            bFelt = (arrHull(iT0) = arrHull(t2) Or arrHull(iT0) = arrHull(iT3))
            bFelt = (arrHull(iT1) = arrHull(t2) Or arrHull(iT1) = arrHull(iT3)) Or bFelt
            
            If bFelt Then Continue
            If IsIntersection(arrPoints(arrHull(iT0)).fLng, arrPoints(arrHull(iT0)).fLat, arrPoints(arrHull(iT1)).fLng, arrPoints(arrHull(iT1)).fLat, arrPoints(arrHull(t2)).fLng, arrPoints(arrHull(t2)).fLat, arrPoints(arrHull(iT3)).fLng, arrPoints(arrHull(iT3)).fLat) Then bOK = False
        Next
    Next

    '    ok = 1

    If bOK = False Then
        For t = 0 To iHull_C - 1
            arrHull(t) = arrmHull(t)
        Next
        iHull_C = iHull_C - 1
    Else
        arrbHull_Busy(iInd) = True
    End If

    Return bOK

End Sub

Sub DifAng (fAngle1 As Double, fAngle2 As Double) As Double
    
    Dim fDiff As Double
    Dim fC As Double =  2 * pi

    Do Until fAngle1 < fC
        fAngle1 = fAngle1 - fC
    Loop

    Do Until fAngle1 >= 0
        fAngle1 = fAngle1 + fC
    Loop

    Do Until fAngle2 < fC
        fAngle2 = fAngle2 - fC
    Loop

    Do Until fAngle2 >= 0
        fAngle2 = fAngle2 + fC
    Loop

    fDiff = fAngle1 - fAngle2

    If fDiff < -pi Then fDiff = fDiff + 2 * pi
    If fDiff > pi Then fDiff = fDiff - 2 * pi

    Return fDiff
    
End Sub

Sub IsIntersection (fX1 As Double, fY1 As Double, fX2 As Double, fY2 As Double, fX3 As Double, fY3 As Double, fX4 As Double, fY4 As Double) As Boolean
    
    Dim fDenominator As Double
    Dim fUA As Double
    Dim fUB As Double
    
    fDenominator = (fY4 - fY3) * (fX2 - fX1) - (fX4 - fX3) * (fY2 - fY1)
    
    If fDenominator = 0 Then
        Return False 'IsIntersection = 0
    Else
        fUA = ((fX4 - fX3) * (fY1 - fY3) - (fY4 - fY3) * (fX1 - fX3)) / fDenominator
        fUB = ((fX2 - fX1) * (fY1 - fY3) - (fY2 - fY1) * (fX1 - fX3)) / fDenominator
        Return fUA >= 0 And fUA <= 1 And fUB >= 0 And fUB <= 1
    End If
    
End Sub

Called from a B4XPage like this:

B4X:
    'the fAcc argument will determine the number of border points.
    'The higher this number the more border points and that will produce a more tighter border.
    '------------------------------------------------------------------------------------------
    lstPolygonPoints = MapUtilities.GetBorderPoints(lstMapPoints, 1.5)

I will have a look at all the other suggestions as well, but I think this will do OK.

RBS
 
Upvote 0

MasterGy

Member
Licensed User
I think I have this all worked out.
For now I have gone with code as in this link:
https://github.com/mapbox/concaveman

This is the code as I have it now and that is all working fine.
As I just need a return list of lat/lng types based on a passed list of map lat/lng points I have
left out all the bitmap related code.

In a code module called MapUtilities:

B4X:
Sub Process_Globals
  
    Type TMapLatLng(fLat As Double, fLng As Double)
    Dim iMapPoints As Int ' Pontok száma
    Dim arrPoints() As TMapLatLng
    Dim arrHull() As Int
    Dim arrbHull_Busy() As Boolean
    Dim iHull_C As Int
    Dim pi As Float = 3.14159265
    'Dim arrLimits(6) As Double 'not needed anymore
    Dim bWorking As Boolean

End Sub

'return a TLatLng from lat/lng
Public Sub initLatLng(aLat As Double,aLng As Double) As TMapLatLng
    Dim ll As TMapLatLng
    ll.Initialize
    ll.fLat=validLat(aLat)
    ll.fLng=validLng(aLng)
    Return ll
End Sub

'ConcaveMan code
'-------------------------------------------------------------------------------------------

Sub Reset(iPoints As Int)
  
    Dim arrHull(iPoints) As Int
    Dim arrbHull_Busy(iPoints) As Boolean
    Dim arrPoints(iPoints) As TMapLatLng
    'Dim arrLimits(6) As Double 'not needed anymore
  
    iHull_C = 0
    iMapPoints = iPoints
    bWorking = False
  
End Sub

Sub GetBorderPoints(lstMapLatLonPoints As List, fAcc As Double) As List
  
    Dim i As Int
    Dim fMinY As Double = 99999999
    Dim fRang As Double  = 2 * pi
    Dim iMinAng_Ind As Int
    Dim iMinAng_NoLimit_Ind As Int
    Dim fMinAng_Ang As Double
    Dim fMinAng_NoLimit_Ang As Double
    Dim fMinAng_NoLimit_Dis  As Double
    Dim fDis As Double
    Dim fDis0 As Double
    Dim fDis1 As Double
    Dim fang As Double
    Dim iAktP As Int
  
    Dim bChange As Boolean
    Dim bNoOK As Boolean

    Dim iH1 As Int
    Dim iH2 As Int
    Dim iMinDis_Ind As Int
    Dim fMinDis As Double
    Dim fDis0 As Double
    Dim fDis1 As Double
    Dim fDis3 As Double
    Dim fDis4 As Double
  
    Dim tLL As TMapLatLng
    Dim lstBorderPoints As List
  
    Reset(lstMapLatLonPoints.Size)
  
    'this used to be done by Sub AddPoints
    '-------------------------------------
    For i = 0 To lstMapLatLonPoints.Size - 1
        tLL = lstMapLatLonPoints.Get(i)
        arrPoints(i) = tLL
    Next

    bWorking = True
  
    For i = 0 To iMapPoints - 1
        If arrPoints(i).fLat < fMinY Then
            iAktP = i
            fMinY = arrPoints(i).fLat
        End If
    Next

    ' A legalsó pontot kiemelni (piros szín)
    Add_Hull(iAktP, 0)

    '-------------------------------------------------------------- STANDARD FRAME
    Do While True
        'keresse a legkozelebbit,aminek a legkisebb a szoge,
        iMinAng_Ind = -1
        fMinAng_Ang = 999999

        iMinAng_NoLimit_Ind = -1
        fMinAng_NoLimit_Ang = 999999
        fMinAng_NoLimit_Dis = 999999

        For i = 0 To iMapPoints - 1
            If i = iAktP Then Continue
            fDis0 = arrPoints(i).fLng - arrPoints(iAktP).fLng
            fDis1 = arrPoints(i).fLat - arrPoints(iAktP).fLat
            fDis = Sqrt(fDis0 * fDis0 + fDis1 * fDis1)
            fang = DifAng(fRang, ATan2(fDis0, fDis1))


            If fang < fMinAng_NoLimit_Ang And fDis < fMinAng_NoLimit_Dis Then
                fMinAng_NoLimit_Ang = fang
                fMinAng_NoLimit_Dis = fDis
                iMinAng_NoLimit_Ind = i
            End If

            If fang < fMinAng_Ang Then
                fMinAng_Ang = fang
                iMinAng_Ind = i
            End If
        Next

        If iMinAng_Ind = -1 Then
            iMinAng_Ind = iMinAng_NoLimit_Ind
            fMinAng_Ang = fMinAng_NoLimit_Ang
        End If
        '    Line (points(iAktP, 0), points(iAktP, 1))-(points(iMinAng_Ind, 0), points(iMinAng_Ind, 1))

        If iAktP = arrHull(0) And iHull_C > 2 Then Exit
        Add_Hull(iAktP, iHull_C)

        fRang = ATan2(arrPoints(iAktP).fLng - arrPoints(iMinAng_Ind).fLng, arrPoints(iAktP).fLat - arrPoints(iMinAng_Ind).fLat)
        iAktP = iMinAng_Ind

    Loop

    '-------------------------------------  FRAME RESOLUTION

    Do While True  'For qqw = 0 To 100
      
        bChange = False
        iH1 = 0
      
        Do While True
            iH2 = (iH1 + 1) Mod (iHull_C - 0)

            fMinDis = 999999
            iMinDis_Ind = -1
      
            fDis3 = Sqrt(Power(arrPoints(arrHull(iH2)).fLng - arrPoints(arrHull(iH1)).fLng,2)  + Power((arrPoints(arrHull(iH2)).fLat - arrPoints(arrHull(iH1)).fLat),2))
          
            For ind = 0 To iMapPoints - 1
                If arrbHull_Busy(ind)  Then Continue
                fDis0 = Sqrt(Power(arrPoints(ind).fLng - arrPoints(arrHull(iH1)).fLng, 2)  + Power((arrPoints(ind).fLat - arrPoints(arrHull(iH1)).fLat), 2))
                fDis1 = Sqrt(Power(arrPoints(ind).fLng - arrPoints(arrHull(iH2)).fLng, 2)  + Power((arrPoints(ind).fLat - arrPoints(arrHull(iH2)).fLat), 2))

                fDis4 = fDis0 + fDis1
                If fDis3 * fAcc > fDis4 And (fDis4 < fMinDis) Then
                    fMinDis = fDis4
                    iMinDis_Ind = ind
                End If
            Next

            If iMinDis_Ind <> -1 Then
                bNoOK = Add_Hull(iMinDis_Ind, iH2)              
                If bNoOK  Then
                    iH1 = iH1 + 1
                    bChange = True
                End If
            End If

            iH1 = iH1 + 1
            If iH1 > iHull_C - 1 Then Exit
        Loop
        If bChange = False Then Exit
    Loop

    'this is not needed anymore
    '--------------------------
    'Calc_Limits
  
    bWorking = False
  
    lstBorderPoints.Initialize
  
    For i = 0 To iHull_C - 1
        tLL = initLatLng(arrPoints(arrHull(i)).fLat, arrPoints(arrHull(i)).fLng)
        lstBorderPoints.Add(tLL)
    Next
  
    tLL = initLatLng(arrPoints(arrHull(0)).fLat, arrPoints(arrHull(0)).fLng)
    lstBorderPoints.Add(tLL)
  
    Return lstBorderPoints
  
End Sub

Sub Add_Hull (iInd As Int, iHova As Int ) As Boolean
  
    'vizsgalat
    Dim bOK As Boolean= True
    Dim bFelt As Boolean
    Dim iT0 As Int
    Dim iT1 As Int
    Dim iT3 As Int

    'mentes elkeszitese
    Dim arrmHull(iHull_C + 10) As Int
  
    For t = 0 To iHull_C - 1
        arrmHull(t) = arrHull(t)
    Next

    'beiktat
    iHull_C = iHull_C + 1

    For t = iHull_C - 2 To iHova Step -1
        arrHull(t + 1) = arrHull(t)
    Next

    arrHull(iHova) = iInd
  
    '    GoTo 66
    For iT0 = 0 To iHull_C - 1: iT1 = (iT0 + 1) Mod iHull_C
        For t2 = 0 To iHull_C - 1: iT3 = (t2 + 1) Mod iHull_C
          
            bFelt = (arrHull(iT0) = arrHull(t2) Or arrHull(iT0) = arrHull(iT3))
            bFelt = (arrHull(iT1) = arrHull(t2) Or arrHull(iT1) = arrHull(iT3)) Or bFelt
          
            If bFelt Then Continue
            If IsIntersection(arrPoints(arrHull(iT0)).fLng, arrPoints(arrHull(iT0)).fLat, arrPoints(arrHull(iT1)).fLng, arrPoints(arrHull(iT1)).fLat, arrPoints(arrHull(t2)).fLng, arrPoints(arrHull(t2)).fLat, arrPoints(arrHull(iT3)).fLng, arrPoints(arrHull(iT3)).fLat) Then bOK = False
        Next
    Next

    '    ok = 1

    If bOK = False Then
        For t = 0 To iHull_C - 1
            arrHull(t) = arrmHull(t)
        Next
        iHull_C = iHull_C - 1
    Else
        arrbHull_Busy(iInd) = True
    End If

    Return bOK

End Sub

Sub DifAng (fAngle1 As Double, fAngle2 As Double) As Double
  
    Dim fDiff As Double
    Dim fC As Double =  2 * pi

    Do Until fAngle1 < fC
        fAngle1 = fAngle1 - fC
    Loop

    Do Until fAngle1 >= 0
        fAngle1 = fAngle1 + fC
    Loop

    Do Until fAngle2 < fC
        fAngle2 = fAngle2 - fC
    Loop

    Do Until fAngle2 >= 0
        fAngle2 = fAngle2 + fC
    Loop

    fDiff = fAngle1 - fAngle2

    If fDiff < -pi Then fDiff = fDiff + 2 * pi
    If fDiff > pi Then fDiff = fDiff - 2 * pi

    Return fDiff
  
End Sub

Sub IsIntersection (fX1 As Double, fY1 As Double, fX2 As Double, fY2 As Double, fX3 As Double, fY3 As Double, fX4 As Double, fY4 As Double) As Boolean
  
    Dim fDenominator As Double
    Dim fUA As Double
    Dim fUB As Double
  
    fDenominator = (fY4 - fY3) * (fX2 - fX1) - (fX4 - fX3) * (fY2 - fY1)
  
    If fDenominator = 0 Then
        Return False 'IsIntersection = 0
    Else
        fUA = ((fX4 - fX3) * (fY1 - fY3) - (fY4 - fY3) * (fX1 - fX3)) / fDenominator
        fUB = ((fX2 - fX1) * (fY1 - fY3) - (fY2 - fY1) * (fX1 - fX3)) / fDenominator
        Return fUA >= 0 And fUA <= 1 And fUB >= 0 And fUB <= 1
    End If
  
End Sub

Called from a B4XPage like this:

B4X:
    'the fAcc argument will determine the number of border points.
    'The higher this number the more border points and that will produce a more tighter border.
    '------------------------------------------------------------------------------------------
    lstPolygonPoints = MapUtilities.GetBorderPoints(lstMapPoints, 1.5)

I will have a look at all the other suggestions as well, but I think this will do OK.

RBS


I worked on it a bit more, but now I'm done. It is much faster than the previous version. I recommend that you try this one as well, you can easily insert it into your program and it makes nicer triangles than the first version you want to use. It follows the edge of the shape better.

fence_maker.initialise(2000,100) 'points array, fences array
fence_maker.add_points(x,y) 'upload points
fence_maker.way(100) 'parameter: set maxlinelength <---- fence maker

If this has run, you can read:

fence_maker_N - we have uploaded so many points
fence_maker_points(n,0/1) - read point X,Y
fence_maker.fence_c 'how many fences are there?
fence_maker.fence_length(index) 'length of fences
fence_maker.fence(index,n) 'which fence, which point The outer fence has an index of 0, which encloses the entire shape
fence_maker.limits(n) 'n:0,1,2,3,4,5 minX,maxX, X extent, minY,maxY,Y extent
fence_maker.lonely_c 'Number of lonely points with no section connected
fence_maker.lonely(index) as boolean 'The given point is pagan or not
fence_maker.triangle_c 'so many triangles have been made
fence_maker.triangles(n,0/1/2) '3 points of n-triangle
 

Attachments

  • fence_maker_ready.zip
    21.6 KB · Views: 30
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I worked on it a bit more, but now I'm done. It is much faster than the previous version. I recommend that you try this one as well, you can easily insert it into your program and it makes nicer triangles than the first version you want to use. It follows the edge of the shape better.

fence_maker.initialise(2000,100) 'points array, fences array
fence_maker.add_points(x,y) 'upload points
fence_maker.way(100) 'parameter: set maxlinelength <---- fence maker

If this has run, you can read:

fence_maker_N - we have uploaded so many points
fence_maker_points(n,0/1) - read point X,Y
fence_maker.fence_c 'how many fences are there?
fence_maker.fence_length(index) 'length of fences
fence_maker.fence(index,n) 'which fence, which point The outer fence has an index of 0, which encloses the entire shape
fence_maker.limits(n) 'n:0,1,2,3,4,5 minX,maxX, X extent, minY,maxY,Y extent
fence_maker.lonely_c 'Number of lonely points with no section connected
fence_maker.lonely(index) as boolean 'The given point is pagan or not
fence_maker.triangle_c 'so many triangles have been made
fence_maker.triangles(n,0/1/2) '3 points of n-triangle
Thanks, will give this a go and see how it compares to my current code.

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I worked on it a bit more, but now I'm done. It is much faster than the previous version. I recommend that you try this one as well, you can easily insert it into your program and it makes nicer triangles than the first version you want to use. It follows the edge of the shape better.

fence_maker.initialise(2000,100) 'points array, fences array
fence_maker.add_points(x,y) 'upload points
fence_maker.way(100) 'parameter: set maxlinelength <---- fence maker

If this has run, you can read:

fence_maker_N - we have uploaded so many points
fence_maker_points(n,0/1) - read point X,Y
fence_maker.fence_c 'how many fences are there?
fence_maker.fence_length(index) 'length of fences
fence_maker.fence(index,n) 'which fence, which point The outer fence has an index of 0, which encloses the entire shape
fence_maker.limits(n) 'n:0,1,2,3,4,5 minX,maxX, X extent, minY,maxY,Y extent
fence_maker.lonely_c 'Number of lonely points with no section connected
fence_maker.lonely(index) as boolean 'The given point is pagan or not
fence_maker.triangle_c 'so many triangles have been made
fence_maker.triangles(n,0/1/2) '3 points of n-triangle
I had a go with your code, but it is quite a bit of work to understand it all and change it to make it accept a list of lat/lng types and make it return another list of lat/lng types.
The code as I have it is very fast and gives nice shapes.
I tested with a list of 260 lat/lng types and that returns the border points (in the right order) in 10 milli-secs as a list of 41 lat/lng types.
I am not sure it can be done any faster.
Attached my list of those 260 lat/lng values.

RBS
 

Attachments

  • LatLngList2.zip
    1.4 KB · Views: 25
Upvote 0

MasterGy

Member
Licensed User
I had a go with your code, but it is quite a bit of work to understand it all and change it to make it accept a list of lat/lng types and make it return another list of lat/lng types.
The code as I have it is very fast and gives nice shapes.
I tested with a list of 260 lat/lng types and that returns the border points (in the right order) in 10 milli-secs as a list of 41 lat/lng types.
I am not sure it can be done any faster.
Attached my list of those 260 lat/lng values.

RBS
I only left what was needed on the surface. I added the csv file to the file list.

If your cvs file records the xy values line by line and separated by commas, you can upload the points like this:

cvs file upload:
Sub upload_points_from_csv(csvfilename As String)

  fence_maker.N = 0 'clear points

  Dim list As List
  list. Initialize
  list = File.ReadList(File.DirAssets, csvfilename)

  For i = 0 To list.Size - 1
  Dim line As String = list.Get(i)
  Dim parts() As String = Regex.Split(",", line)
  fence_maker.add_points(parts(0),parts(1))
  Next


End Sub
After that comes the fence generation.
fence_maker.way(maxlineheight)

maxlineheight is a percentage. 100% means the largest distance resulting from the boundary values of the uploaded points. This is good if you want to surround the entire shape with the largest possible fence. However, then the indentations will be small and it will not follow the edges very well. If you take this value smaller, the triangle formation becomes more and more detailed, it follows the edges more and more.
You have to find what is right for you.

Once that's done, just read it. Since you only need the outer fence, its index is 0.
fence_maker.length(0) 'number of points, the fence, in order
fence_maker.fence(0,n) 'point index

show your csv file
 

Attachments

  • fencemaker3.zip
    23 KB · Views: 27
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I only left what was needed on the surface. I added the csv file to the file list.

If your cvs file records the xy values line by line and separated by commas, you can upload the points like this:

cvs file upload:
Sub upload_points_from_csv(csvfilename As String)

  fence_maker.N = 0 'clear points

  Dim list As List
  list. Initialize
  list = File.ReadList(File.DirAssets, csvfilename)

  For i = 0 To list.Size - 1
  Dim line As String = list.Get(i)
  Dim parts() As String = Regex.Split(",", line)
  fence_maker.add_points(parts(0),parts(1))
  Next


End Sub
After that comes the fence generation.
fence_maker.way(maxlineheight)

maxlineheight is a percentage. 100% means the largest distance resulting from the boundary values of the uploaded points. This is good if you want to surround the entire shape with the largest possible fence. However, then the indentations will be small and it will not follow the edges very well. If you take this value smaller, the triangle formation becomes more and more detailed, it follows the edges more and more.
You have to find what is right for you.

Once that's done, just read it. Since you only need the outer fence, its index is 0.
fence_maker.length(0) 'number of points, the fence, in order
fence_maker.fence(0,n) 'point index

show your csv file
Thanks, it does work very well.
It seems though my posted code is faster as when I ran it with the posted .csv it took 134 milli-secs.
I will double check this as sometimes it seems to get accurate timings you need to run code as ResumableSub.
Will have a look at this.

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Thanks, it does work very well.
It seems though my posted code is faster as when I ran it with the posted .csv it took 134 milli-secs.
I will double check this as sometimes it seems to get accurate timings you need to run code as ResumableSub.
Will have a look at this.

RBS
Have double-checked and the Concaveman code does indeed run faster.
Attached a backup file of the project comparing the 2 approaches.
I haven't looked at the fence shape of the Concaveman code as shown on a bitmap, but it looks fine
in my OSM map.

RBS
 

Attachments

  • Backup hull 2024-11-18 15.05.zip
    26.1 KB · Views: 28
Upvote 0

MasterGy

Member
Licensed User
Have double-checked and the Concaveman code does indeed run faster.
Attached a backup file of the project comparing the 2 approaches.
I haven't looked at the fence shape of the Concaveman code as shown on a bitmap, but it looks fine
in my OSM map.

RBS
I'm glad that the problem was finally solved!
You may have read it, but it may have escaped your attention. You are using my first solution. Fellow forum member William Lancee, however, found a bug. If the shape is too concave, no fence is formed in the central direction.
If your project will not encounter such a shape, then this is a good solution. I wanted to eliminate this error, so I tried the other approach, which is slower (more calculations), but works better. The disadvantage of my latter solution is that if the points are not homogeneously distributed, the "maxlineheight" must be set higher in order not to divide it into several areas.
Anyway, I'm happy about it, the main thing is that it worked! :)
 

Attachments

  • testHull2.png
    testHull2.png
    21.3 KB · Views: 22
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I'm glad that the problem was finally solved!
You may have read it, but it may have escaped your attention. You are using my first solution. Fellow forum member William Lancee, however, found a bug. If the shape is too concave, no fence is formed in the central direction.
If your project will not encounter such a shape, then this is a good solution. I wanted to eliminate this error, so I tried the other approach, which is slower (more calculations), but works better. The disadvantage of my latter solution is that if the points are not homogeneously distributed, the "maxlineheight" must be set higher in order not to divide it into several areas.
Anyway, I'm happy about it, the main thing is that it worked! :)
Does this bug happen with the Concaveman code?
If not then that might be the best approach.

RBS
 
Upvote 0

MasterGy

Member
Licensed User
Does this bug happen with the Concaveman code?
If not then that might be the best approach.

RBS
yes it happens because the two codes are the same ! Try it yourself and give it a strongly concave shape, such as a "C" shape.
To avoid this error, I created the second version.
 

Attachments

  • ConcaveMan(your last version).bas
    8.4 KB · Views: 21
  • frame_maker(my_first_version).bas
    7.4 KB · Views: 21
Upvote 0

MasterGy

Member
Licensed User
yes it happens because the two codes are the same ! Try it yourself and give it a strongly concave shape, such as a "C" shape.
To avoid this error, I created the second version.


so to be clear, I recommend this:
https://www.b4x.com/android/forum/threads/polygon-around-multiple-lat-lon-points.164046/post-1006755

This is the second (improved) version, which works completely differently than the first version. This creates a fence flawlessly (albeit more slowly).
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
yes it happens because the two codes are the same ! Try it yourself and give it a strongly concave shape, such as a "C" shape.
To avoid this error, I created the second version.
OK, will check that.
Never realised that your code (one of your posted codes) was the same as the code as in the quoted concaveman link:
!

RBS
 
Upvote 0

MasterGy

Member
Licensed User
OK, will check that.
Never realised that your code (one of your posted codes) was the same as the code as in the quoted concaveman link:
!

RBS
I don't understand this. This is very interesting. You posted "concaveman.bas".
Some of the variable names are different, but they contain the words I wrote. "beiktat" , "mentes elkeszitese", "goto 66" .

:))
 

Attachments

  • Image1.jpg
    Image1.jpg
    31 KB · Views: 27
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
OK, will check that.
Never realised that your code (one of your posted codes) was the same as the code as in the quoted concaveman link:
!

RBS
Have checked this now (in my OSM map app, actually it is a medical app) and indeed the C-shaped collection of points causes the bug you described, where the indentation is missed.
This is when the tightness factor (fAcc) is low, eg 1.
I don't think it is a major problem in my app, but I may go with your slower code.

RBS
 
Upvote 0
Top