GithubHelp home page GithubHelp logo

jannikmi / extremitypathfinder Goto Github PK

View Code? Open in Web Editor NEW
40.0 1.0 12.0 2.9 MB

python package for fast shortest path computation on 2D polygon or grid maps

Home Page: https://pypi.org/project/extremitypathfinder/

License: MIT License

Python 99.22% Makefile 0.78%
pathfinding path-planning pathfinder pathfinding-algorithm path polygon multipolygon shortest-paths shortest-path shortest-path-algorithm

extremitypathfinder's Introduction

extremitypathfinder

https://github.com/jannikmi/extremitypathfinder/actions/workflows/build.yml/badge.svg?branch=master documentation status pre-commit Total PyPI downloads latest version on PyPI

python package for fast geometric shortest path computation in 2D multi-polygon or grid environments based on visibility graphs.

./docs/_static/title_demo_plot.png

Quick Guide:

Install the package with the optional Numba extra for a significant speedup:

pip install extremitypathfinder[numba]
from extremitypathfinder import PolygonEnvironment

environment = PolygonEnvironment()
# counter clockwise vertex numbering!
boundary_coordinates = [(0.0, 0.0), (10.0, 0.0), (9.0, 5.0), (10.0, 10.0), (0.0, 10.0)]
# clockwise numbering!
list_of_holes = [
    [
        (3.0, 7.0),
        (5.0, 9.0),
        (4.5, 7.0),
        (5.0, 4.0),
    ],
]
environment.store(boundary_coordinates, list_of_holes, validate=False)
start_coordinates = (4.5, 1.0)
goal_coordinates = (4.0, 8.5)
path, length = environment.find_shortest_path(start_coordinates, goal_coordinates)

For more refer to the documentation.

Also see: GitHub, PyPI

extremitypathfinder's People

Contributors

andrewsyl avatar dependabot[bot] avatar georghess avatar gpapadok avatar idoria75 avatar jannikmi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

extremitypathfinder's Issues

avoid data structure copying

Currently the data structures are being enlaged with start and goal coordinates dynamically. rather initialise them with the required additional space and reuse them

Allow complex input geometries

allow input of complex geometries: input coords, and edges (index reference to two coords)
This is a more general data format for all kinds of geometries. Polygons are special case and should be converted to the more general data format then.

special case: empty boundary polygon
In some use cases the environment might only consist of obstacles (=holes).
-> support not providing a boundary polygon.

Optimisation: check edges with the biggest angle range first

The visibility algorithms become faster when candidates can be ruled out quickly (don't have to be checked in consequent calls).
Usually all edges have to be checked (unless no candidates remain).
For every edge being checked the angle range (= angle representation difference) has to be evaluated.
Edges with a bigger angle range have a higher chance of eliminating more candidates ("block more of the view").
-> sort the edges after their angle range and check edges with the biggest angle range first

Impossible Shortest Path Sometimes Ignoring Holes?

Hello,

I'm not sure if I'm doing something wrong here. I'm trying to make a path that should be impossible (and thus return None), but instead I'm getting a path that's ignoring a hole. Is this a mistake on my part, or a bug in the library? Thanks for your help.

Ubuntu 20.04
Python 3.8.5
extremitypathfinder 2.0.0

env = PlottingEnvironment(plotting_dir='./tmp/')
env.store(
    [(5, 5), (-5, 5), (-5, -5), (5, -5)],
    [[(-5, 1), (-5, 2), (5, 2), (5, 1)]],
    validate=True
)
env.prepare()
path, length = env.find_shortest_path(
    (0, 0),
    (0, 4)
)
assert path == None # path is actually [(0, 0), (0, 4)]

graph_path_plot_1609883109
graph_plot_1609883109
map_plot_160988310
path_plot_1609883110
prepared_map_plot_1609883109

Memory leak in DirectedHeuristicGraph

The same set for DirectedHeuristicGraph.all_nodes is reused and shared across all instances of it, and thus across all instances of PolygonEnvironment. This causes memory leaks if new PolygonEnvironments are created and destroyed.

I'd suggest creating an __init__ method for DirectedHeuristicGraph and initializing all instance variables in that.

Here's an example that demonstrates it:

from extremitypathfinder import PolygonEnvironment
environment = PolygonEnvironment()

# counter clockwise vertex numbering!
boundary_coordinates = [(0.0, 0.0), (10.0, 0.0), (9.0, 5.0), (10.0, 10.0), (0.0, 10.0)]

# clockwise numbering!
list_of_holes = [[(3.0, 7.0), (5.0, 9.0), (4.5, 7.0), (5.0, 4.0), ], ]
environment.store(boundary_coordinates, list_of_holes, validate=False)
environment.prepare()
print(f'first environment graph size originally: {len(environment.graph.all_nodes)}')

# these need to be different values from the previous ones
new_boundary_coordinates = [(0.1, 0.1), (10.1, 0.1), (9.1, 5.1), (10.1, 10.1), (0.1, 10.1)]
new_list_of_holes = [[(3.1, 7.1), (5.1, 9.1), (4.6, 7.1), (5.1, 4.1), ], ]
new_environment = PolygonEnvironment()
new_environment.store(new_boundary_coordinates, new_list_of_holes, validate=False)
new_environment.prepare()

print(f'first environment graph size after making second graph: {len(environment.graph.all_nodes)}')
print(f'second environment graph size: {len(new_environment.graph.all_nodes)}')
print(f'graphs are the same object: {environment.graph.all_nodes is new_environment.graph.all_nodes}')

ccw can't resolve path but cw can

from extremitypathfinder import PolygonEnvironment

environment = PolygonEnvironment()
boundary_coordinates = [(-2670.0220709984246, 2849.785328817447), (-534.0066345219713, 4408.091718018869), (2347.252358687458, 3455.8632661053057), (-290.58214669197565, 122.69563577652582), (2576.3601774807016, -1781.9414137932943), (-3616.182881567927, -2546.608038824627)]
list_of_holes = []
start = (-3616.182881567927, -2546.608038824627)
end = (-290.58214669197565, 4327.64226042047)
environment.store(boundary_coordinates, list_of_holes, validate=False)
environment.prepare()
print(environment.find_shortest_path(start, end))

return ([], None)
but reverse boundary_coordinates, can get ([(-3616.182881567927, -2546.608038824627), (-2670.0220709984246, 2849.785328817447), (-534.0066345219713, 4408.091718018869), (-290.58214669197565, 4327.64226042047)], 8379.112990147512)

further compile the core visibility algorithms

the agorithms have been refactored to mostly use numpy arrays already. refactor further to enable Numba JIT compilation (cf. utils_numba.py).

avoid copying the data structures. find a way of eliminating candidates without compiling new lists, but also without iterating over large static boolean arrays.

Path going straight through hole

I'm getting a path that runs straight through a hole. Technically it doesn't cross any of the edges of the hole, but runs along edges and crosses both in and out at vertices, so I gueses the issue is with checking edge crossings for visibility.

Setup to reproduce:

from extremitypathfinder.plotting import PlottingEnvironment

environment = PlottingEnvironment(plotting_dir="path\to\folder")

area = [(161.0, 47.0), (247.0, 47.0), (247.0, 7.0), (361.0, 7.0), (361.0, 441.0), (7.0, 441.0), (7.0, 7.0), (161.0, 7.0)]
holes = [[(272.0, 172.0), (272.0, 36.0), (136.0, 36.0), (136.0, 172.0), (155.6, 172.0), (155.6, 332.4), (252.4, 332.4), (252.4, 172.0)]]
(environment.graph, obj_dict) = PlottingEnvironment.get_graph(area, holes)
environment.save(obj_dict)

(start, end) = (50.004004, 44.004004), (318.004004, 44.004004)
sequence, sequence_length = environment.find_shortest_path(start, end)

graph_path_plot_1701788345

expose public method for obtaining the visible extremities

for a more modular and versatile usability of this package it would help to have a public method for computing the visible nodes from any given input point (cf. #62).

This method should be used by the ".find_shortest_path()" method for the given start and goal points

Enable parallel computing

take advantage of processors with multiple cores using parallel computing.
a lot of checks in the algorithms are independent and can be parallelized.

Adding steps on path?

Hi!
Right now, the algorithm takes the coordinates for the start and end points.
Would it be difficult to add an intermediary point (or several), and find the shortest path to go through them all? I can always try different options (length A->B->C vs A->C->B), but I was wondering if there could be a more elegant method that could be plugged directly in your algorithm.
Thanks!

Nice project btw, I will certainly use it in an upcoming demo of mine. I'll make sure to credit you.

Allow for overlapping polygons

Currently, overlapping polygons cannot be handled. In my experience, they often yield paths that go through polygons that overlap, rather than around.

At https://github.com/georghess/extremitypathfinder/tree/overlapping-polygons a potential fix addresses this when preparing the visibility graph. Basically, extremities that are within a polygon or outside the boundary should not be considered visible:

while len(extremities_to_check) > 0:
    # extremities are always visible to each other (bi-directional relation -> undirected graph)
    #  -> do not check extremities which have been checked already
    #  (would only give the same result when algorithms are correct)
    # the extremity itself must not be checked when looking for visible neighbours
    query_extremity: PolygonVertex = extremities_to_check.pop()
    # only extremities that are within the map are visible to other extremities
    if not self.within_map(query_extremity.coordinates):
        continue

Further, neighbours of query_extremity should not automatically be considered visible, since overlapping polygons might obstruct the view, e.g. just remove

try:
    candidate_extremities.remove(n1)
    visible_vertices.add((n1, n1.get_distance_to_origin()))
except KeyError:
    pass
try:
    candidate_extremities.remove(n2)
    visible_vertices.add((n2, n2.get_distance_to_origin()))
except KeyError:
    pass

Adjusted code passes all tests and produces feasible paths for overlapping polygons as far as I've seen.

return a "path" object

creating a class for representing paths (sequences of points) could be useful for some users which want to use more information about the computed shortest path. instead of returning just a sequence of coordinates and a length separately, simply return a Path instance
desired properties:

  • list of points (some of them will be extremity vertices)
  • list of coordinates
  • length

assert error while polygon two points are extremely close

Example code

from extremitypathfinder import PolygonEnvironment
environment = PolygonEnvironment()
boundary_coordinates = [(4196.035531444209, 1524.5174141906843), (120.45899596761083, 1964.5309552310625), (-1560.1327143180315, 3953.1950461671404), (-1845.7183461510908, 2184.3530258772616), (-4008.99249567332, 2308.524464730015), (-2077.147281929666, 104.95988170636463), (-4397.1723772192245, -1684.1387542764294), (-3542.469485595629, -2760.93575183657), (-3542.443309415404, -2760.9398379707277)]
list_of_holes = []
start = (4196.035531444209, 1524.5174141906843)
end = (-2077.147281929666, 104.95988170636463)
environment.store(boundary_coordinates, list_of_holes, validate=True)
environment.prepare()
print(environment.find_shortest_path(start, end, verify=True))

Singular matrix

I have a pretty simple polygons:
image
But the shortest path is not found and I get a Singular Matrix error.

Do you know why this is happening? Thanks.

Visibility graph ignoring boundary or holes in some cases

Hello!

It seems that visibility graph ignoring outer boundary or holes in some cases and it leads to impossible shortest paths.

Example with ignored boundary
map_plot_1659535886
graph_path_plot_16595359

Example with ignored hole
map_plot_1659536047
prepared_map_plot_1659536047

One more example with ignored boundary
map_plot_1659536334
graph_path_plot_1659536337

Pickle files to reproduce issue
maps.zip

MacOS Monterey 12.5
Python 3.8.9
extremitypathfinder 2.2.2

Optimisation: use undirected Graph

As the precomputed graph usually makes up the majority of the visibility graph (in comparison to the temporarily added edges for query start and goal nodes) and the precomputed part is actually undirected, also in general an undirected Graph should be used.

Allow direct A* grid search

As mentioned in [1, Ch. III 6.3] in 'chessboard-like grid worlds' (many small obstacles have a lot of extremities!) it can be better to use A* right away (implemented in graph_search.py).

add option to use A* directly
automatically use A* when a certain ratio of extremities vs. grid tiles is reached

Use lazily computed sparse representation matrix

For computing the visibility graph (connections), an angle representation for every point relative to a query point is being used.
This angle representation is a float between 0.0 and 4.0 (like radians without trigonometric functions).

Currently each point has an object oriented representation allowing computing the different angle representations lazily.
This object orientation is hindering fast numerical computation.

Idea: given N points in the visibility graph introduce a fixed ordering (indexing) of the points.
store the representation of each point for each other point as query point in an N x N matrix of floats

Lazy computation:
Initialise empty (all values "unknown")
Wrapper get_rep(...) function computing value in case of an unknown value.

Use sparse matrix representation to support large N:
https://docs.scipy.org/doc//scipy/reference/generated/scipy.sparse.coo_array.html#scipy.sparse.coo_array
sparse matrices have a default value of 0.0.
Since the angle representation 4.0 and 0.0 are equals (circle). use 0.0 to represent "unknown" and 4.0 instead of "true 0.0".

Attention: for dynamic additional start and end points not included in the static visibility graph one needs to add 2 more vectors of angle representations!

Allow empty boundary polygon

In some scenarios there might not be explicit boundaries. Allow the boundary polygon parameter to be None.

Note: If the boundary polygon is None, there must be holes given

Quick workaround: take a simple recangle containing all given holes.

test invalid input detection

helper_fcts.py now raise Type/ValueError in check_data_requirements() in case of validate=True and invalid input. Test if all the invalid cases are being caught.

Dynamic Visibility Computations

Include a faster approach to compute visibility on the fly while exploring the world with A*. Useful for use cases where the map is changing dynamically and there is no benefit in storing a precomputed and optimized visibility graph.

Point real in polygon edge, but within_map return False

version: 1.2.0

boundary: [(-3573.063493809924, -3397.7041999887488), (1301.0459112892288, -252.2710863523747), (1301.15422849654, -252.28188515495947), (4770.1483895222245, -3628.3511913003213), (2834.943959525434, 3975.1684750915865), (-1867.1813387156046, 2849.7191787712245)]
holes: [[(2060.962499654814, 2402.856442923204), (2061.0991856380538, 2402.7975366689266), (2505.6489788642275, 1262.568360564638), (2505.4964894782643, 1262.374772157754), (67.8754446874187, 1594.3012258568667), (67.82916999672895, 1594.6696523014614)]]
starting to ending point: (2436.2142928243047, 1440.6619196276174)
(2505.6489788642275, 1262.568360564638)

ValueError: start or goal do not lie within the map
environment.within_map(starting point) -> False
environment.within_map(ending point) -> True

So how to deal with precision?

Assume vertices lie within the boundary polygon

as a solution to issue #33

when a query point is equal to a vertex of the boundary polygon it should be assumed that this point lies within the polygon in order to enable paths.
ATTENTION: for hole polygons it must be assumed that vertices lie outside!

Comparing generator to generator

Hi, sometimes I can see the following error:

  File "/home/adam/projects/github/ga-path-planning/src/ExtremityPathFinder.py", line 165, in checkDistance
    _, length = self.env.find_shortest_path(p_start, p_goal)
  File "/home/adam/.local/lib/python3.8/site-packages/extremitypathfinder/extremitypathfinder.py", line 381, in find_shortest_path
    vertex_path, distance = self.temp_graph.modified_a_star(start_vertex, goal_vertex)
  File "/home/adam/.local/lib/python3.8/site-packages/extremitypathfinder/helper_classes.py", line 450, in modified_a_star
    current, distance, neighbour_gen, path, cost_so_far = priority_queue.get()
  File "/home/adam/.local/lib/python3.8/site-packages/extremitypathfinder/helper_classes.py", line 263, in get
    return heapq.heappop(self.elements)[1]  # return only the item without the priority
TypeError: '<' not supported between instances of 'generator' and 'generator'

It happens when I try to calculate distance many times, but it's not repetitive so when I restart process then everything works fine.

I don't know what can I say to you about that because I don't know how to go over this.
Optionally I can share with you the source code, but I would avoid this.

Thank you,
Adam

Broken command in documentation

In the documentation, there is a part of how to store/load model

environment.export_pickle(path='./pickle_file.pickle')

from extremitypathfinder import load_pickle
environment = load_pickle(path='./pickle_file.pickle')

this should be

environment.export_pickle(path='./pickle_file.pickle')

from extremitypathfinder.extremitypathfinder import load_pickle #<<<---- change 
environment = load_pickle(path='./pickle_file.pickle')

Solution failed

polygon [(0.0, 0.0), (-2.136, -1.71), (1.273, -7.556), (2.713, -6.518)]
start (2.713, -6.518)
end (-2.136, -1.71)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.