Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a simple Python version of the Atlas #140

Merged
merged 120 commits into from
Jun 20, 2018
Merged

Added a simple Python version of the Atlas #140

merged 120 commits into from
Jun 20, 2018

Conversation

lucaspcram
Copy link
Contributor

@lucaspcram lucaspcram commented Jun 18, 2018

See the readme under pyatlas/README.md for more information.

This PR modifies the Travis release process to publish a pyatlas distribution to PyPI.
Currently, the distribution is not GPG signed. It is possible to change this, if we feel it is necessary. However I do not believe PyPI requires it.

Copy link
Collaborator

@matthieun matthieun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice addition! A few questions included. Also Is the pyatlas/pyatlas/autogen/__init__.py empty file included on purpose?
Also: Maybe add a short description + link in the main readme?

# GNU and BSD sed have different "in-place" flag syntax
if [ "$(uname)" == "Darwin" ];
then
sed -i "" "s/version=.*/version=\"$atlas_version\",/" "$pyatlas_root_dir/setup.py"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of a hack but it works! Maintaining it is my problem anyway :)

load_method_to_call()


class _PackedTagStore(object):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those included in the same file to make them "private"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not the fact that they are included in the same package, rather the leading _ character is what makes them private. You can see in the geometry module that there are many classes defined there, none of which are private.

The reason I placed _PackedTagStore and _IntegerDictionary in the atlas module is because they are only called from within that module, and they don't need to be exposed to the user for import or documentation.

return frozenset(relation_set)


class Point(AtlasEntity):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool flyweight implementation!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take no credit for this design. Copied straight from your PackedAtlas :)


def _urshift32(to_shift, shift_amount):
"""
Perform a 32 bit unsigned right shift (drag in leading 0s).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

immutable, so this method forces a rebuild of the entire tree.
"""
self.contents.append(entity)
self._construct_tree_from_contents()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have two methods, one called "add" and another one called "finishedAdding"? That way you do not build the rtree at every call?

Copy link
Contributor Author

@lucaspcram lucaspcram Jun 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I could change the name to something different, just to make it more clear. I included an implementation of add in RTree to make the interface consistent across tree implementations, since the SpatialIndex.add method simply calls add on whatever its backing tree is. In the case that you have a SpatialIndex backed by a QuadTree, there is no forced tree rebuild.

The reason I implemented the RTree this way (with the forced rebuild) is because the STR packed R-tree provided by GEOS (thru Shapely) is effectively immutable once it has been "built" (you can see what I mean by "built" by checking out this GEOS class, "building" occurs as a side effect of queries to the tree, see the query method in the linked code ).
So effectively, the only way to add elements to the tree once it has been "built" is to rebuild the tree from scratch. However, this limitation does not apply when constructing the tree for the first time, which is why I provided the initial_entities parameter in the constructor of the RTree class.

Ideally, users will not be building their own RTrees anyway. The Atlas lazily constructs the spatial indices it needs at query time. However, I made the SpatialIndex interface public in case someone really wants to extend the spatial capabilities of an Atlas without necessarily modifying the Atlas class itself. Using the provided interface, one could attach a custom SpatialIndex with a custom subset of features to an arbitrary Atlas.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be awesome to add all this as a comment in that method!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha sounds good, I'll clean this up to be a bit more concise and to the point.

from pyatlas.geometry import Rectangle


class AtlasTest(unittest.TestCase):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those tests fail, are they build breaking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, failing unit tests cause the build to abort with a diagnostic detailing the failed test and the cause of the failure. It's very similar to the JUnit setup we have in Java Atlas.

@lucaspcram
Copy link
Contributor Author

Glad to get this thing started!

Yes the __init__.py is a pythonism that allows the interpreter to detect a directory as a python package. Here is some info on the subject

@matthieun
Copy link
Collaborator

I was wondering if it was ok to have it in the autogen folder. Thanks!

@lucaspcram
Copy link
Contributor Author

lucaspcram commented Jun 19, 2018

You have to, since the autogen folder is included as a subpackage by other modules in the main pyatlas package (eg. You can see at the top of atlas.py that it has lots of import autogen.FOO_pb2 statements). Hope that clears everything up! :-)

README.md Outdated
@@ -160,6 +160,10 @@ final Atlas atlas2;
new PackedAtlasCloner().cloneFrom(new MultiAtlas(atlas1, atlas2)).save(new File("/path/to/file.atlas"));
```

# PyAtlas

`Atlas` features a lightweight Python version of the core functionality. The `pyatlas` is available as a Python package from PyPI, but can also be built and installed locally. Check out the `pyatlas` subfolder and the `pyatlas/README.md` file for more information!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could link it directly here using ... [Readme file](pyatlas/README.md) ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok let me add that. I did not realize you could do relative URLs.

Copy link
Contributor

@jwpgage jwpgage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questions, but looks really good! Thanks for the thorough tests.

"""
Print a welcome message!
"""
print "Hello Atlas!"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just a default global function to be replaced in future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question :) It's pretty much just there for the Getting Started section of the README, which has the user call it to test that the package built correctly. In the future this module may have additional function definitions.

#!/usr/bin/env bash

# THIS ENTIRE SCRIPT IS A MASSIVE HACK
# THIS SHOULD REALLY BE DONE WITH GRADLE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really need to remove this comment... :P

self.assertEquals({atlas.point(3)}, test_results)

test_results = atlas.points_at(geometry.location_with_degrees(38, -118))
self.assertEquals({atlas.point(1)}, test_results)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have these been tested and verified against the Java version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no automatic verification, but I have tested it manually against the Java version with the help of a script not included in this PR. Based on my tests the query results are consistent. But more tests are always welcome!

Copy link
Contributor

@MikeGost MikeGost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work!

return len(self.word_to_index)

def word(self, index):
# TODO this could throw a KeyError, how to handle?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to address or ignore for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the way that class is used internally, so I believe that comment is outdated now. Good catch.

@jwpgage jwpgage merged commit 9529b5d into osmlab:dev Jun 20, 2018
@lucaspcram lucaspcram deleted the pyatlas branch June 20, 2018 15:30
@matthieun matthieun modified the milestone: 5.1.3 Jul 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants